Merge pull request #1296 from morluto/feat/website-seo-metadata

feat(website): add search metadata and automatic sitemap
This commit is contained in:
Ysopwnable 2026-10-09 22:43:41 +08:00 committed by GitHub
commit 99f257bbf0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 1230 additions and 46 deletions

View File

@ -5,8 +5,7 @@ on:
branches: [main, "release/*"]
paths:
- "website/**"
- "scripts/verify-website.py"
- "scripts/prepare-website.py"
- "scripts/*website*.py"
- ".github/workflows/**"
workflow_dispatch:
@ -21,11 +20,24 @@ jobs:
check:
name: Website checks
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 10
steps:
- name: Check out the repository
uses: actions/checkout@v4
- name: Prepare the example download
- name: Set up Python for website assets
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: website/requirements.txt
- name: Install sharing-image dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y libcairo2 fonts-dejavu-core
python3 -m pip install -r website/requirements.txt
- name: Prepare website assets
run: python3 scripts/prepare-website.py
- name: Check website links and publishing configuration
- name: Check website links, metadata and publishing configuration
run: python3 scripts/verify-website.py
- name: Check sitemap discovery and metadata regressions
run: python3 scripts/test-website.py

View File

@ -23,7 +23,7 @@ jobs:
prepare:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 10
permissions:
contents: read
pages: read
@ -48,9 +48,20 @@ jobs:
fi
- name: Check out the repository
uses: actions/checkout@v4
- name: Prepare the example download
- name: Set up Python for website assets
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: website/requirements.txt
- name: Install sharing-image dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y libcairo2 fonts-dejavu-core
python3 -m pip install -r website/requirements.txt
- name: Prepare website assets
run: python3 scripts/prepare-website.py
- name: Check website links and publishing configuration
- name: Check website links, metadata and publishing configuration
run: python3 scripts/verify-website.py
- name: Record the published commit
run: |

3
.gitignore vendored
View File

@ -50,6 +50,9 @@ Thumbs.db
# Generated website download
/website/public/examples/notes-example.zip
/website/public/deployment.json
/website/public/sitemap.xml
/website/public/assets/social-card.png
/website/.venv/
# Local Cloudflare development state
/website/.wrangler/

View File

@ -1,10 +1,16 @@
"""Package the public Notes example for the static website."""
"""Prepare downloads, the sitemap and the sharing image for the static website."""
from html.parser import HTMLParser
from io import BytesIO
from pathlib import Path
import re
import sys
from urllib.parse import quote
import xml.etree.ElementTree as ET
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
SITE_ORIGIN = "https://rea.tools"
EXAMPLE_FILES = (
"package.json",
"main.js",
@ -15,6 +21,70 @@ EXAMPLE_FILES = (
)
class IndexPolicy(HTMLParser):
"""Read indexing directives from the authored HTML head."""
def __init__(self):
super().__init__()
self.in_head = False
self.noindex = False
def handle_starttag(self, tag, attrs):
if tag == "head":
self.in_head = True
if tag == "meta" and self.in_head:
attributes = dict(attrs)
if (attributes.get("name") or "").lower() in ("robots", "googlebot"):
directives = re.split(
r"[\s,]+", (attributes.get("content") or "").lower()
)
self.noindex |= bool(set(directives) & {"noindex", "none"})
def handle_endtag(self, tag):
if tag == "head":
self.in_head = False
def prepare_sitemap(site):
"""Discover HTML routes automatically, excluding pages marked noindex."""
namespace = "http://www.sitemaps.org/schemas/sitemap/0.9"
ET.register_namespace("", namespace)
sitemap = ET.Element(f"{{{namespace}}}urlset")
for path in sorted(site.rglob("*.html")):
policy = IndexPolicy()
policy.feed(path.read_text(encoding="utf-8"))
if policy.noindex:
continue
route = path.relative_to(site).as_posix()
if path.name == "index.html":
route = route.removesuffix("index.html")
entry = ET.SubElement(sitemap, f"{{{namespace}}}url")
ET.SubElement(
entry, f"{{{namespace}}}loc"
).text = f"{SITE_ORIGIN}/{quote(route, safe='/')}"
ET.indent(sitemap, space=" ")
ET.ElementTree(sitemap).write(
site / "sitemap.xml", encoding="utf-8", xml_declaration=True
)
print(f"Prepared sitemap.xml ({len(sitemap)} indexable pages).")
def prepare_sharing_image(site):
"""Rasterize the maintained SVG for clients that need a PNG preview."""
try:
import cairosvg
except (ImportError, OSError) as error:
sys.exit(
f"Cannot prepare sharing image: {error}. Install website/requirements.txt and native Cairo; see website/README.md."
)
source = site / "assets/social-card.svg"
destination = site / "assets/social-card.png"
cairosvg.svg2png(
url=str(source), write_to=str(destination), output_width=1200, output_height=630
)
print("Prepared assets/social-card.png (1200 × 630).")
def prepare_notes_example(site):
"""Create a ZIP with public source only and reproducible metadata."""
source = site / "examples/notes-electron"
@ -37,4 +107,7 @@ def prepare_notes_example(site):
if __name__ == "__main__":
root = Path(__file__).resolve().parent.parent
prepare_notes_example(root / "website/public")
site = root / "website/public"
prepare_sharing_image(site)
prepare_sitemap(site)
prepare_notes_example(site)

130
scripts/test-website.py Normal file
View File

@ -0,0 +1,130 @@
"""Exercise automatic route discovery and mistakes that break search metadata."""
import contextlib
import importlib.util
import io
from pathlib import Path
import shutil
import tempfile
import unittest
import xml.etree.ElementTree as ET
ROOT = Path(__file__).resolve().parent.parent
def load_script(name):
"""Load a repository script without executing its command-line entrypoint."""
spec = importlib.util.spec_from_file_location(name, ROOT / "scripts" / f"{name}.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
PREPARE = load_script("prepare-website")
VERIFY = load_script("verify-website")
class WebsiteSearchChecks(unittest.TestCase):
"""Use authored pages and temporary fixtures to check the publishing boundary."""
def test_sitemap_tracks_added_moved_removed_and_nonindexable_pages(self):
with tempfile.TemporaryDirectory() as directory:
site = Path(directory)
def page(name, head="", body=""):
path = site / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(f"<html><head>{head}</head><body>{body}</body></html>")
return path
page("index.html")
nested = page("guides/new/index.html")
page(
"examples/fixture/index.html",
'<meta name="robots" content="NOINDEX, follow">',
)
page("draft.html", '<meta name="googlebot" content="none">')
page("code & notes.html")
page("visible/index.html", body='<meta name="robots" content="noindex">')
(site / "asset.json").write_text("{}")
def urls():
with contextlib.redirect_stdout(io.StringIO()):
PREPARE.prepare_sitemap(site)
sitemap = ET.parse(site / "sitemap.xml")
return {node.text for node in sitemap.findall(".//{*}loc")}
self.assertEqual(
urls(),
{
"https://rea.tools/",
"https://rea.tools/guides/new/",
"https://rea.tools/code%20%26%20notes.html",
"https://rea.tools/visible/",
},
)
nested.rename(site / "moved.html")
self.assertIn("https://rea.tools/moved.html", urls())
self.assertNotIn("https://rea.tools/guides/new/", urls())
(site / "moved.html").unlink()
self.assertNotIn("https://rea.tools/moved.html", urls())
def test_verifier_rejects_wrong_canonicals_stale_sitemaps_and_missing_previews(
self,
):
cases = (
(
"index.html",
'rel="canonical" href="https://rea.tools/"',
'rel="canonical" href="https://morluto.github.io/rea/"',
"head canonical",
),
(
"showcase/aegis/index.html",
'rel="canonical" href="https://rea.tools/showcase/aegis/"',
'rel="canonical" href="https://rea.tools/"',
"head canonical",
),
(
"examples/notes-electron/index.html",
'content="noindex"',
'content="index"',
"must have a head noindex",
),
(
"sitemap.xml",
"https://rea.tools/faq/",
"https://rea.tools/deleted/",
"each indexable page once",
),
(
"faq/index.html",
'name="twitter:card" content="summary_large_image"',
'name="twitter:card" content="summary"',
"twitter:card",
),
)
for name, old, new, message in cases:
with self.subTest(name=name), tempfile.TemporaryDirectory() as directory:
site = Path(directory) / "public"
shutil.copytree(ROOT / "website/public", site)
path = site / name
source = path.read_text()
self.assertIn(old, source)
path.write_text(source.replace(old, new, 1))
self.assertTrue(
any(message in error for error in VERIFY.check_site(site)[3])
)
with tempfile.TemporaryDirectory() as directory:
site = Path(directory) / "public"
shutil.copytree(ROOT / "website/public", site)
(site / "assets/social-card.png").unlink()
self.assertIn(
"Social preview PNG is missing; run scripts/prepare-website.py",
VERIFY.check_site(site)[3],
)
if __name__ == "__main__":
unittest.main()

View File

@ -1,14 +1,19 @@
"""Check local website references and the sole GitHub Pages publisher."""
"""Check website references, search metadata and the sole production publisher."""
from html.parser import HTMLParser
from pathlib import Path
import re
import struct
import sys
from urllib.parse import unquote, urlsplit
from urllib.parse import quote, unquote, urlsplit
import xml.etree.ElementTree as ET
from zipfile import BadZipFile, ZipFile
SITE_ORIGIN = "https://rea.tools"
NOTES_FIXTURES = {"examples/notes-electron/index.html", "examples/notes-web/index.html"}
class HtmlReferences(HTMLParser):
"""Collect document IDs and references without fetching external URLs."""
@ -17,9 +22,42 @@ class HtmlReferences(HTMLParser):
self.ids = set()
self.references = []
self.duplicate_ids = []
self.in_head = False
self.in_title = False
self.titles = []
self.meta = {}
self.properties = {}
self.canonicals = []
self.lang = None
self.h1_count = 0
def handle_starttag(self, tag, attrs):
attributes = dict(attrs)
if tag == "html":
self.lang = attributes.get("lang")
if tag == "h1":
self.h1_count += 1
if tag == "head":
self.in_head = True
if self.in_head:
if tag == "title":
self.titles.append("")
self.in_title = True
if (
tag == "link"
and "canonical" in (attributes.get("rel") or "").lower().split()
):
self.canonicals.append(attributes.get("href") or "")
if tag == "meta":
for attribute, collection in (
("name", self.meta),
("property", self.properties),
):
key = attributes.get(attribute)
if key:
collection.setdefault(key.lower(), []).append(
attributes.get("content") or ""
)
identifier = attributes.get("id")
if identifier is not None:
if identifier in self.ids:
@ -30,6 +68,154 @@ class HtmlReferences(HTMLParser):
if reference is not None:
self.references.append(reference)
def handle_endtag(self, tag):
if tag == "head":
self.in_head = False
if tag == "title":
self.in_title = False
def handle_data(self, data):
if self.in_title:
self.titles[-1] += data
def noindex(self):
"""Recognize directives that exclude this page from Google's index."""
directives = ",".join(
self.meta.get("robots", []) + self.meta.get("googlebot", [])
).lower()
return bool(set(re.split(r"[\s,]+", directives)) & {"noindex", "none"})
def check_search_metadata(site, pages):
"""Verify route-specific canonicals, sharing assets and the generated sitemap."""
errors = []
indexable = set()
titles = set()
for path, page in pages.items():
label = path.relative_to(site).as_posix()
title = page.titles[0].strip() if len(page.titles) == 1 else ""
if not title:
errors.append(f"{label}: needs exactly one nonempty head title")
if page.lang != "en" or page.h1_count != 1:
errors.append(f"{label}: needs lang=en and exactly one h1")
if label in NOTES_FIXTURES and not page.noindex():
errors.append(
f"{label}: example application must have a head noindex directive"
)
if page.noindex():
continue
route = (
path.relative_to(site).parent.as_posix()
if path.name == "index.html"
else label
)
route = "" if route == "." else route
url = f"{SITE_ORIGIN}/{quote(route, safe='/')}" + (
"/" if route and path.name == "index.html" else ""
)
indexable.add(url)
if page.canonicals != [url]:
errors.append(f"{label}: needs exactly one head canonical for {url}")
if title in titles:
errors.append(f"{label}: duplicate search title {title!r}")
titles.add(title)
descriptions = page.meta.get("description", [])
description = descriptions[0].strip() if len(descriptions) == 1 else ""
if not description:
errors.append(f"{label}: needs exactly one nonempty head description")
expected_og = {
"og:type": "website",
"og:site_name": "REA",
"og:title": title,
"og:description": description,
"og:url": url,
"og:image": f"{SITE_ORIGIN}/assets/social-card.png",
"og:image:type": "image/png",
"og:image:width": "1200",
"og:image:height": "630",
}
expected_twitter = {
"twitter:card": "summary_large_image",
"twitter:title": title,
"twitter:description": description,
"twitter:image": expected_og["og:image"],
}
for collection, expected in (
(page.properties, expected_og),
(page.meta, expected_twitter),
):
for key, value in expected.items():
if collection.get(key) != [value]:
errors.append(
f"{label}: {key} must appear once and match {value!r}"
)
for collection, key in (
(page.properties, "og:image:alt"),
(page.meta, "twitter:image:alt"),
):
values = collection.get(key, [])
if len(values) != 1 or not values[0].strip():
errors.append(f"{label}: needs exactly one nonempty {key}")
sitemap_path = site / "sitemap.xml"
try:
sitemap = ET.parse(sitemap_path).getroot()
namespace = "{http://www.sitemaps.org/schemas/sitemap/0.9}"
urls = [entry.findtext(f"{namespace}loc") for entry in sitemap]
if sitemap.tag != f"{namespace}urlset" or any(
entry.tag != f"{namespace}url" for entry in sitemap
):
errors.append("sitemap.xml: needs a sitemap-protocol urlset")
if len(urls) != len(set(urls)) or set(urls) != indexable:
errors.append(
"sitemap.xml: must contain each indexable page once, using its rea.tools URL"
)
if any(
len(entry) != 1 or entry[0].tag != f"{namespace}loc" for entry in sitemap
):
errors.append(
"sitemap.xml: keep entries to loc; do not invent lastmod or add priority/changefreq"
)
except (OSError, ET.ParseError) as error:
errors.append(
f"sitemap.xml: cannot read sitemap ({error}); run scripts/prepare-website.py"
)
robots_path = site / "robots.txt"
if not robots_path.is_file():
errors.append("robots.txt: authored crawl rules are missing")
else:
directives = []
for line in robots_path.read_text(encoding="utf-8").splitlines():
value = line.split("#", 1)[0].strip()
if value:
key, separator, content = value.partition(":")
directives.append(
(key.lower(), content.strip()) if separator else (value, "")
)
expected = [
("user-agent", "*"),
("allow", "/"),
("sitemap", f"{SITE_ORIGIN}/sitemap.xml"),
]
if directives != expected:
errors.append(
"robots.txt: expected crawl access and one absolute rea.tools sitemap directive"
)
image = site / "assets/social-card.png"
if not image.is_file():
errors.append("Social preview PNG is missing; run scripts/prepare-website.py")
else:
header = image.read_bytes()[:24]
if (
len(header) != 24
or header[:16] != b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"
or struct.unpack(">II", header[16:24]) != (1200, 630)
):
errors.append("Social preview must be a 1200 × 630 PNG")
return len(indexable), errors
def check_site(site):
"""Check assets, HTML fragment destinations and SVG syntax."""
@ -63,7 +249,11 @@ def check_site(site):
target /= "index.html"
if not target.is_file():
errors.append(f"{label}: missing local target: {reference}")
elif target in pages and url.fragment and unquote(url.fragment) not in pages[target].ids:
elif (
target in pages
and url.fragment
and unquote(url.fragment) not in pages[target].ids
):
errors.append(f"{label}: missing HTML fragment: {reference}")
for path in sorted(site.rglob("*.svg")):
@ -71,18 +261,23 @@ def check_site(site):
ET.parse(path)
except ET.ParseError as error:
errors.append(f"{path.relative_to(site)}: invalid SVG XML: {error}")
return len(pages), references, errors
indexable, metadata_errors = check_search_metadata(site, pages)
errors.extend(metadata_errors)
return len(pages), references, indexable, errors
def check_publisher(root):
"""Detect a second Pages action that could replace the website."""
expected = root / ".github/workflows/website-pages.yml"
workflows = root / ".github/workflows"
deploy_action = re.compile(r"^\s*-?\s*uses:\s*['\"]?actions/deploy-pages@", re.MULTILINE)
deploy_action = re.compile(
r"^\s*-?\s*uses:\s*['\"]?actions/deploy-pages@", re.MULTILINE
)
publishers = [
path
for path in sorted(workflows.iterdir())
if path.suffix in (".yml", ".yaml") and deploy_action.search(path.read_text(encoding="utf-8"))
if path.suffix in (".yml", ".yaml")
and deploy_action.search(path.read_text(encoding="utf-8"))
]
if publishers != [expected]:
actual = ", ".join(str(path.relative_to(root)) for path in publishers) or "none"
@ -95,7 +290,14 @@ def check_example_archive(site):
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")
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():
@ -103,7 +305,9 @@ def check_example_archive(site):
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 [
"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()
@ -116,13 +320,18 @@ def check_example_archive(site):
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")
pages, references, indexable, 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, SVG XML and the example ZIP.")
print(
f"Verified {pages} HTML pages, {references} local references, SVG XML and the example ZIP."
)
print(
f"Verified search/sharing metadata, robots.txt and the sitemap for {indexable} indexable pages."
)
print("website-pages.yml is the sole GitHub Pages publisher.")
return 0

View File

@ -16,9 +16,15 @@ for asset notes and the source of case-study claims.
target identity and verification scope.
- Keep personal paths, account data, credentials and raw captures outside the
public assets. Use generic inputs and label shortened display paths.
- Follow the same clear writing standard in search titles and descriptions.
Give each content page its own absolute `https://rea.tools/` canonical and
matching sharing metadata; keep navigation relative. The sitemap is generated
from HTML automatically. Mark example applications `noindex` in the head and
keep them crawlable. Maintain the SVG sharing source, not the generated PNG.
- Check both local root and `/rea/` paths, mobile layouts, expanded details,
copying and downloads. Run `scripts/prepare-website.py` followed by
`scripts/verify-website.py`.
`scripts/verify-website.py` and `scripts/test-website.py`, using the website
Python environment described in the README.
- Keep `website-pages.yml` as the sole, manual production publisher for
Cloudflare and Pages. Default to `both`: both hosts receive the same verified
artifact and pass the published-commit check. Use `pages` only when explicitly

View File

@ -7,8 +7,8 @@
An English static website with explanatory figures, worked guides and Aegis, DX-Ball, Notion, TH04 and CTF investigations.
The public files are in `website/public/`. The site uses HTML, CSS and small
scripts for copying code, following code comparisons and playing the dinosaur
speed reconstruction. Python
packages the downloadable example; there is no frontend bundler or npm dependency.
speed reconstruction. Python prepares the example ZIP, sitemap and sharing
image; there is no frontend bundler or npm dependency.
[style-guide.md](style-guide.md) explains the writing, page structure, figures,
visual system and review process. Read it before adding or revising a page.
@ -18,11 +18,23 @@ visual system and review process. Read it before adding or revising a page.
From the repository root:
```sh
python3 -m venv website/.venv
source website/.venv/bin/activate
python3 -m pip install -r website/requirements.txt
python3 scripts/prepare-website.py
python3 -m http.server 4173 --bind 127.0.0.1 --directory website/public
```
Open <http://127.0.0.1:4173/>. Refresh the browser after editing a file.
Keep the virtual environment active when preparing assets. CairoSVG needs the
native Cairo library; the sharing card uses DejaVu Sans. On Debian or Ubuntu,
install them with `sudo apt-get install libcairo2 fonts-dejavu-core`. For other
systems, see [CairoSVG's installation instructions](https://cairosvg.org/documentation/#installation).
CI installs both packages explicitly before rendering the card.
After adding, moving or removing a page, or changing its indexing policy, rerun
`python3 scripts/prepare-website.py`. It regenerates the sitemap from the current
HTML files. No URL list needs to be maintained.
## Pages
@ -44,6 +56,69 @@ Open <http://127.0.0.1:4173/>. Refresh the browser after editing a file.
Navigation and assets use relative paths, so the same files work at the local
root and a GitHub Pages project path such as `/rea/`.
## Search and sharing metadata
`https://rea.tools/` is the preferred public origin. Each content page has one
absolute canonical URL for its own route, including its trailing slash. The
same HTML on GitHub Pages points to the corresponding rea.tools page. Ordinary
navigation and asset references remain relative so both hosts and local previews
keep working.
Maintain a descriptive `<title>` and a short meta description in each page's
`<head>`. The title names the page's subject; visible headings keep the wording
that best explains it to a reader. Open Graph and Twitter titles/descriptions
match those fields. `og:url` matches the canonical, and sharing images use
absolute rea.tools URLs. Search results and link previews should follow the same
clear, concise writing standard as the page.
`scripts/prepare-website.py` scans `public/**/*.html` and generates
`public/sitemap.xml` with absolute rea.tools URLs. An `index.html` maps to its
directory route; other HTML filenames retain their extension. Pages with
`noindex` or `none` in a head `robots` or `googlebot` meta directive are excluded.
The two Notes example applications use `noindex`; their guides and the dinosaur
lab remain indexable. Downloads, scripts and other non-HTML files are not listed.
The sitemap has no guessed modification dates, priority or change frequency.
It is ignored by Git and regenerated before checks and publication.
`public/robots.txt` allows crawling and names the canonical sitemap. Keep example
applications crawlable so search engines can read their `noindex` directives.
Cloudflare may prepend managed rules or comments to the served file; inspect the
live response after publication. The mirrored `/rea/robots.txt` is not the
hostname-root robots file for `morluto.github.io`; canonical HTML tags establish
the mirror's preference.
`public/assets/social-card.svg` is the maintained 1200 × 630 preview source.
CairoSVG generates `social-card.png` for sharing clients. Commit the SVG, not the
PNG. Both hosts receive the generated image in the same publishing artifact.
The preview uses the site's typography, palette and direct headline.
Verification checks route-specific canonicals, unique titles, descriptions,
sharing fields, image dimensions, indexing policy and complete sitemap coverage.
`scripts/test-website.py` exercises automatic route changes and rejects incorrect
canonicals, lost fixture exclusions, stale sitemap entries and missing previews.
Run it after preparing assets:
```sh
python3 scripts/verify-website.py
python3 scripts/test-website.py
```
### After publishing an SEO change
1. Check the served `robots.txt`, `sitemap.xml`, HTML head and preview PNG at
rea.tools. Confirm that both hosts contain the same page-specific canonicals.
2. In the domain's Google Search Console property, submit
`https://rea.tools/sitemap.xml`.
3. Inspect the homepage, a guide and a case study. Check crawl access, indexing
and Google's selected canonical; inspect a GitHub Pages URL as well if that
property is available. Track indexing and search performance there afterward.
These Search Console checks require the property owner's access. Local checks
verify the website inputs; Google's indexing reports show how they were used.
See Google's [canonical guidance](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls)
and [sitemap guidance](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap),
and Cloudflare's [managed robots behavior](https://developers.cloudflare.com/bots/additional-configurations/managed-robots-txt/).
## Content
Keep the copy direct and specific. Explain the task and the result before listing
@ -293,16 +368,18 @@ a release. Cloudflare deploys first; GitHub Pages deploys after that step succee
Preparation adds `deployment.json` with the workflow's commit SHA. Cloudflare
receives the extracted Pages artifact through Wrangler's explicit assets path;
the local custom-build output is not the uploaded directory. The workflow then
the production environment disables Wrangler's preview custom build, so deployment
uses the prepared artifact directly. The workflow then
checks the selected public version markers and reports success only when they
match that commit. A failed publication remains failed; fix its cause and rerun the
workflow to complete both deployments. The two hosts can update at different
times while a run is in progress.
Website checks run only for pull requests that change `website/`, the verification
script or workflow definitions. They check local links, HTML fragments, SVG XML
and the single Pages publisher without installing npm dependencies. The same
checks run before each manual deployment. You can also run them locally:
Website checks run only for pull requests that change `website/`, the website
scripts or workflow definitions. They check local links, HTML fragments, SVG XML,
search/sharing metadata and the single Pages publisher without installing npm
dependencies. The same asset preparation and verification run before each manual
deployment. With the preview environment active, run them locally:
```sh
python3 scripts/prepare-website.py

View File

@ -33,8 +33,8 @@ 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.
rejects extra entries. The recorded digests below identify the original analysis
checkpoint; subsequent metadata changes and checks are recorded separately.
## First-investigation formatter check
@ -82,6 +82,23 @@ The guide's follow-up CLI commands were also exercised against the real page:
source. Discovery Evidence: `ev_31abb33e7707d47625ac909ab9256c705d03a31ee6bcd643fa96730e72469162`. Script capture
Evidence: `ev_aeb777df405c09dc1bbdd4c185a5af2452abaa3cb493683c296c5afa97efbc11`.
## SEO metadata check — 9 October 2026
Both Notes HTML heads gained descriptions and `noindex` directives. The Electron
fixture was analyzed again with `rea-agents` 4.1.0 and Node.js 24.18.0 on Linux
x64. REA still reports four parsed JavaScript files, zero parse failures, one
exposed API member, one literal IPC channel, one renderer transmission, one main
handler and one paired transmission, with zero ambiguous transmissions.
- Evidence ID: `ev_3958d870ecd27c202e5decc97161f2ea19db8cc88f9f39ed0449bf1581df2f2a`.
- Root artifact SHA-256: `0ee325d4eaf7f94b62b2962044995fe1efe9f1d31411eb035118b19b1f56f7bb`.
- Current Electron HTML SHA-256: `457b22543aa956fe2cd005fc6bda42d2798eb05040b75c4cb4957e3b66d0ea09`.
- Current browser HTML SHA-256: `3b24b3307ded48055ce5a48f907544195bfde3285f82df919c54a6415ac11070`.
The JavaScript and data files retain their original digests. The generated ZIP
contains the updated HTML and the same JavaScript. Local browser checks exercise
the Notes CSV download at both root and `/rea/` paths.
Full local outputs and the browser verification harness are under
`/tmp/rea-website-guides/` in the development environment, not website assets.

View File

@ -43,6 +43,15 @@ branches when changing these figures. Match progress claims to the pinned
reconstruction checkpoint. Validate the SVG as XML and inspect its browser
rendering at desktop and mobile widths.
## Sharing preview
`public/assets/social-card.svg` is the 1200 × 630 preview source. It uses the
homepage's direct headline, the site's white/charcoal/slate/blue palette, and a
short agent → REA → evidence flow. CairoSVG renders the ignored PNG during asset
preparation. Check the rendered image for readable text and spacing before
publishing. The build uses DejaVu Sans for consistent typography; only the SVG
source is committed. Metadata and build instructions are in the website README.
## Beginner homepage and dinosaur reconstruction
The manual/agent comparison and Calculator percentage controls are semantic

View File

@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-labelledby="title desc">
<title id="title">REA — Find out how software works.</title>
<desc id="desc">Reverse engineering with your coding agent. REA connects your agent to analysis tools and returns evidence.</desc>
<rect width="1200" height="630" fill="#ffffff"/>
<g font-family="DejaVu Sans, sans-serif" fill="#20252b">
<text x="72" y="110" font-size="44" font-weight="700">REA</text>
<text x="1128" y="106" font-size="24" text-anchor="end" fill="#5b6570">rea.tools</text>
<path d="M72 142 H1128" stroke="#dce2e8" stroke-width="2"/>
<text x="72" y="252" font-size="64" font-weight="700">Find out how</text>
<text x="72" y="334" font-size="64" font-weight="700">software works.</text>
<text x="72" y="393" font-size="27" fill="#5b6570">Reverse engineering with your coding agent.</text>
<g stroke="#dce2e8" stroke-width="2">
<rect x="72" y="469" width="260" height="90" rx="10" fill="#ffffff"/>
<rect x="398" y="469" width="324" height="90" rx="10" fill="#f0f5fa" stroke="#294f82"/>
<rect x="788" y="469" width="340" height="90" rx="10" fill="#ffffff"/>
</g>
<g text-anchor="middle" font-size="27">
<text x="202" y="525">Your agent</text>
<text x="560" y="525" fill="#294f82" font-weight="700">REA + analysis tools</text>
<text x="958" y="525">Code &amp; evidence</text>
</g>
<g stroke="#5b6570" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path d="M350 514 H380 M372 506 L380 514 L372 522"/>
<path d="M740 514 H770 M762 506 L770 514 L762 522"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Dinosaur speed lab — REA</title>
<title>Chrome Dinosaur Game: Rebuild Its Speed Rule — REA</title>
<meta
name="description"
content="Read the dinosaur game’s speed rule with REA, rebuild it, then change the speed and play."
/>
<link rel="canonical" href="https://rea.tools/examples/dino-lab/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="Chrome Dinosaur Game: Rebuild Its Speed Rule — REA"
/>
<meta
property="og:description"
content="Read the dinosaur game’s speed rule with REA, rebuild it, then change the speed and play."
/>
<meta property="og:url" content="https://rea.tools/examples/dino-lab/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="Chrome Dinosaur Game: Rebuild Its Speed Rule — REA"
/>
<meta
name="twitter:description"
content="Read the dinosaur game’s speed rule with REA, rebuild it, then change the speed and play."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />

View File

@ -2,6 +2,11 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="robots" content="noindex" />
<meta
name="description"
content="A small Electron Notes app for the REA JavaScript analysis guide."
/>
<title>Notes — Electron analysis example</title>
<script src="renderer.js" defer></script>
</head>

View File

@ -3,6 +3,11 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex" />
<meta
name="description"
content="A small Notes app for the REA browser and network analysis guide."
/>
<title>Notes — REA browser example</title>
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>FAQ — REA</title>
<title>REA FAQ: Setup, Updates and Troubleshooting</title>
<meta
name="description"
content="Answers to common REA questions: installation, updates, analysis tools, coding agents, troubleshooting and sharing examples."
/>
<link rel="canonical" href="https://rea.tools/faq/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="REA FAQ: Setup, Updates and Troubleshooting"
/>
<meta
property="og:description"
content="Answers to common REA questions: installation, updates, analysis tools, coding agents, troubleshooting and sharing examples."
/>
<meta property="og:url" content="https://rea.tools/faq/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="REA FAQ: Setup, Updates and Troubleshooting"
/>
<meta
name="twitter:description"
content="Answers to common REA questions: installation, updates, analysis tools, coding agents, troubleshooting and sharing examples."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../assets/styles.css" />

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Your first investigation — REA</title>
<title>Your First Reverse Engineering Investigation — REA</title>
<meta
name="description"
content="Try REA on a small Notes app. Set up your agent, ask where exported notes go, and check the CSV formatter with a sample title."
/>
<link rel="canonical" href="https://rea.tools/first-investigation/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="Your First Reverse Engineering Investigation — REA"
/>
<meta
property="og:description"
content="Try REA on a small Notes app. Set up your agent, ask where exported notes go, and check the CSV formatter with a sample title."
/>
<meta property="og:url" content="https://rea.tools/first-investigation/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="Your First Reverse Engineering Investigation — REA"
/>
<meta
name="twitter:description"
content="Try REA on a small Notes app. Set up your agent, ask where exported notes go, and check the CSV formatter with a sample title."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../assets/styles.css" />

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Get started — REA</title>
<title>Install REA and Set Up Your Coding Agent</title>
<meta
name="description"
content="Connect REA to your coding agent or inspect a local JavaScript or Electron app from the terminal. Setup, first questions and native provider guides."
/>
<link rel="canonical" href="https://rea.tools/get-started/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="Install REA and Set Up Your Coding Agent"
/>
<meta
property="og:description"
content="Connect REA to your coding agent or inspect a local JavaScript or Electron app from the terminal. Setup, first questions and native provider guides."
/>
<meta property="og:url" content="https://rea.tools/get-started/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="Install REA and Set Up Your Coding Agent"
/>
<meta
name="twitter:description"
content="Connect REA to your coding agent or inspect a local JavaScript or Electron app from the terminal. Setup, first questions and native provider guides."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../assets/styles.css" />

View File

@ -3,11 +3,45 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Browser observation — REA</title>
<title>Browser and Network Analysis — REA</title>
<meta
name="description"
content="Observe a Notes export with REA: select a browser page, capture a click, inspect the request and follow the export script."
/>
<link rel="canonical" href="https://rea.tools/guides/browser/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta property="og:title" content="Browser and Network Analysis — REA" />
<meta
property="og:description"
content="Observe a Notes export with REA: select a browser page, capture a click, inspect the request and follow the export script."
/>
<meta property="og:url" content="https://rea.tools/guides/browser/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Browser and Network Analysis — REA" />
<meta
name="twitter:description"
content="Observe a Notes export with REA: select a browser page, capture a click, inspect the request and follow the export script."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />

View File

@ -3,11 +3,45 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Guides — REA</title>
<title>Reverse Engineering Guides — REA</title>
<meta
name="description"
content="Learn REA through short worked examples: a native calculation, an Electron CSV export and a browser request."
/>
<link rel="canonical" href="https://rea.tools/guides/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta property="og:title" content="Reverse Engineering Guides — REA" />
<meta
property="og:description"
content="Learn REA through short worked examples: a native calculation, an Electron CSV export and a browser request."
/>
<meta property="og:url" content="https://rea.tools/guides/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Reverse Engineering Guides — REA" />
<meta
name="twitter:description"
content="Learn REA through short worked examples: a native calculation, an Electron CSV export and a browser request."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../assets/styles.css" />

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>JavaScript &amp; Electron — REA</title>
<title>JavaScript and Electron Analysis — REA</title>
<meta
name="description"
content="Trace a Notes CSV export through a renderer API, preload and IPC handler with REA. Includes downloadable example files and a verified analysis result."
/>
<link rel="canonical" href="https://rea.tools/guides/javascript/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="JavaScript and Electron Analysis — REA"
/>
<meta
property="og:description"
content="Trace a Notes CSV export through a renderer API, preload and IPC handler with REA. Includes downloadable example files and a verified analysis result."
/>
<meta property="og:url" content="https://rea.tools/guides/javascript/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="JavaScript and Electron Analysis — REA"
/>
<meta
name="twitter:description"
content="Trace a Notes CSV export through a renderer API, preload and IPC handler with REA. Includes downloadable example files and a verified analysis result."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />

View File

@ -3,11 +3,45 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Native analysis — REA</title>
<title>Native Binary Analysis — REA</title>
<meta
name="description"
content="Recover a native calculation with REA: inspect instructions, follow a caller, read constants and check reconstructed C."
/>
<link rel="canonical" href="https://rea.tools/guides/native/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta property="og:title" content="Native Binary Analysis — REA" />
<meta
property="og:description"
content="Recover a native calculation with REA: inspect instructions, follow a caller, read constants and check reconstructed C."
/>
<meta property="og:url" content="https://rea.tools/guides/native/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Native Binary Analysis — REA" />
<meta
name="twitter:description"
content="Recover a native calculation with REA: inspect instructions, follow a caller, read constants and check reconstructed C."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />

View File

@ -3,10 +3,50 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>REA — Reverse Engineer Anything</title>
<title>REA — Reverse Engineering for Your Coding Agent</title>
<meta
name="description"
content="Inspect apps, binaries and runtime behavior with your coding agent or terminal. REA connects local analysis tools through CLI and MCP."
content="Reverse engineer apps, binaries and browser behavior with your coding agent. Follow clear guides and try interactive examples with REA."
/>
<link rel="canonical" href="https://rea.tools/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="REA — Reverse Engineering for Your Coding Agent"
/>
<meta
property="og:description"
content="Reverse engineer apps, binaries and browser behavior with your coding agent. Follow clear guides and try interactive examples with REA."
/>
<meta property="og:url" content="https://rea.tools/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="REA — Reverse Engineering for Your Coding Agent"
/>
<meta
name="twitter:description"
content="Reverse engineer apps, binaries and browser behavior with your coding agent. Follow clear guides and try interactive examples with REA."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="assets/favicon.svg" type="image/svg+xml" />

View File

@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://rea.tools/sitemap.xml

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>How an authenticator generates a login code — REA</title>
<title>Aegis APK Reverse Engineering: Login Codes — REA</title>
<meta
name="description"
content="Inspect the Aegis Android APK with REA, recover its six-digit login-code calculation and try a reconstruction with an adjustable clock."
/>
<link rel="canonical" href="https://rea.tools/showcase/aegis/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="Aegis APK Reverse Engineering: Login Codes — REA"
/>
<meta
property="og:description"
content="Inspect the Aegis Android APK with REA, recover its six-digit login-code calculation and try a reconstruction with an adjustable clock."
/>
<meta property="og:url" content="https://rea.tools/showcase/aegis/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="Aegis APK Reverse Engineering: Login Codes — REA"
/>
<meta
name="twitter:description"
content="Inspect the Aegis Android APK with REA, recover its six-digit login-code calculation and try a reconstruction with an adjustable clock."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Solve a CTF flag checker — REA</title>
<title>CTF Reverse Engineering: DownUnderCTF Flag Checker — REA</title>
<meta
name="description"
content="Use REA to solve DownUnderCTF's masked squares flag checker. Find the checking functions, extract the masks and compare a recovered flag with the original program."
/>
<link rel="canonical" href="https://rea.tools/showcase/ctf/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="CTF Reverse Engineering: DownUnderCTF Flag Checker — REA"
/>
<meta
property="og:description"
content="Use REA to solve DownUnderCTF's masked squares flag checker. Find the checking functions, extract the masks and compare a recovered flag with the original program."
/>
<meta property="og:url" content="https://rea.tools/showcase/ctf/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="CTF Reverse Engineering: DownUnderCTF Flag Checker — REA"
/>
<meta
name="twitter:description"
content="Use REA to solve DownUnderCTF's masked squares flag checker. Find the checking functions, extract the masks and compare a recovered flag with the original program."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Rebuilding DX-Ball — REA</title>
<title>DX-Ball Reverse Engineering: Sound Panning — REA</title>
<meta
name="description"
content="Follow a DX-Ball reconstruction from binary analysis through REA to maintained C, original-x86 tests and matching compiled function bytes."
/>
<link rel="canonical" href="https://rea.tools/showcase/dx-ball/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="DX-Ball Reverse Engineering: Sound Panning — REA"
/>
<meta
property="og:description"
content="Follow a DX-Ball reconstruction from binary analysis through REA to maintained C, original-x86 tests and matching compiled function bytes."
/>
<meta property="og:url" content="https://rea.tools/showcase/dx-ball/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="DX-Ball Reverse Engineering: Sound Panning — REA"
/>
<meta
name="twitter:description"
content="Follow a DX-Ball reconstruction from binary analysis through REA to maintained C, original-x86 tests and matching compiled function bytes."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Case studies — REA</title>
<title>Reverse Engineering Case Studies — REA</title>
<meta
name="description"
content="Follow REA investigations through code, figures and demos: an Android authenticator, DX-Ball, Notion, TH04 and a DownUnderCTF flag checker."
/>
<link rel="canonical" href="https://rea.tools/showcase/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="Reverse Engineering Case Studies — REA"
/>
<meta
property="og:description"
content="Follow REA investigations through code, figures and demos: an Android authenticator, DX-Ball, Notion, TH04 and a DownUnderCTF flag checker."
/>
<meta property="og:url" content="https://rea.tools/showcase/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="Reverse Engineering Case Studies — REA"
/>
<meta
name="twitter:description"
content="Follow REA investigations through code, figures and demos: an Android authenticator, DX-Ball, Notion, TH04 and a DownUnderCTF flag checker."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../assets/styles.css" />

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Following Notion's clipboard through Electron — REA</title>
<title>Notion Electron Reverse Engineering: Clipboard — 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."
/>
<link rel="canonical" href="https://rea.tools/showcase/notion/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="Notion Electron Reverse Engineering: Clipboard — REA"
/>
<meta
property="og: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 property="og:url" content="https://rea.tools/showcase/notion/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="Notion Electron Reverse Engineering: Clipboard — REA"
/>
<meta
name="twitter: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="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />

View File

@ -3,11 +3,51 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>TH04 bullet patterns — REA</title>
<title>TH04 Reverse Engineering: DOS Bullet Patterns — REA</title>
<meta
name="description"
content="Inspect TH04's original 16-bit DOS code with REA. Follow the assembly and recovered C++ to see how a ring of bullets is spaced and aimed at the player."
/>
<link rel="canonical" href="https://rea.tools/showcase/th04/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="REA" />
<meta
property="og:title"
content="TH04 Reverse Engineering: DOS Bullet Patterns — REA"
/>
<meta
property="og:description"
content="Inspect TH04's original 16-bit DOS code with REA. Follow the assembly and recovered C++ to see how a ring of bullets is spaced and aimed at the player."
/>
<meta property="og:url" content="https://rea.tools/showcase/th04/" />
<meta
property="og:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="TH04 Reverse Engineering: DOS Bullet Patterns — REA"
/>
<meta
name="twitter:description"
content="Inspect TH04's original 16-bit DOS code with REA. Follow the assembly and recovered C++ to see how a ring of bullets is spaced and aimed at the player."
/>
<meta
name="twitter:image"
content="https://rea.tools/assets/social-card.png"
/>
<meta
name="twitter:image:alt"
content="REA — Find out how software works. Reverse engineering with your coding agent."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />

2
website/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
# SVG-to-PNG conversion for the generated social preview.
CairoSVG==2.9.1

View File

@ -55,6 +55,13 @@ comes from, or what a check covers. Scope a claim positively and precisely:
“The compiled function matches 63 bytes” is more useful than a broad claim
followed by several qualifications.
Search titles and sharing previews follow this standard too. Name the software
or task so someone can understand the page before opening it. Keep titles and
descriptions accurate and concise; use technical terms when they identify the
actual topic. Preserve a clear visible headline rather than repeating search
terms throughout the page. See [README.md](README.md#search-and-sharing-metadata)
for canonical URLs, automatic sitemap generation and sharing assets.
## Build a page around one question
### Introductions and first exercises

View File

@ -20,3 +20,8 @@ name = "rea-website"
workers_dev = true
preview_urls = false
routes = [{ pattern = "rea.tools", custom_domain = true }]
# Production receives the already prepared and verified shared Pages artifact.
# Override the preview build so deployment does not regenerate local assets.
[env.production.build]
command = ""