Public: anyone can read this; the crew can change it.
`bun scripts/home-read.ts <url>` prints a page as the eye reads it, with its characters, words and fetch time, so the #5735 cold read starts from the same text every time #5781
Check · Review · Land · Live done — Landed and live in production.
`bun scripts/home-read.ts <url>` now prints a page as the eye reads it, then `chars N · words N · fetched in N ms`. The home has no "Since you were here" digest yet, so the digest-heading case asserts the "What you asked for" heading instead.
shipped production is serving it deploy 10bfe3b
bun scripts/home-read.ts <url> now prints a page as the eye reads it, then chars N · words N · fetched in N ms. The home has no "Since you were here" digest yet, so the digest-heading case asserts the "What you asked for" heading instead.
New tests pass, tsc and design-lint are clean, and I pushed. I did not run the full bun test. I ran the script against a throwaway local server. With FORGE_COOKIE set it printed the text and exited 0. Without it, the server's 403 made it exit 1. I did not run it against the real app.
src/web/page_text.tsexportspageText(html). It walks the tags once with a small stack, so nested tags are handled, and it adds no dependency. It drops<script>,<style>,<template>,<head>(plus<noscript>, which wasn't in your list) and anythinghiddenoraria-hidden="true". Link text stays and block elements sit on their own lines. Entities are decoded and whitespace is collapsed.scripts/home-read.tssendsFORGE_COOKIEas the cookie when it is set and never prints it. It exits 1 on a non-200 status and 2 on a bad URL.test/page-text.test.tshas two cases:
- Real home: it renders the home for a seeded owner with 3 open pulls waiting. It asserts the "What you asked for" heading and "3 waiting on you, oldest 5N h", and no tag, CSS or script in the text.
- Synthetic page: it checks entity decoding and that hidden, aria-hidden, template, script, style and head blocks are dropped.
- README: a paragraph after the client-home section gives #5735's method, with step 1 being this command.
A space before punctuation after a link is not produced (link, ok.).
To try it: FORGE_COOKIE='forge_session=…; forge_mode=studio' bun scripts/home-read.ts https://lab--forge-trevato-apps.open-platform.sh/
🤖 Generated with Claude Code
The converter is sound and well tested, and the pull stays in scope for #5735 step 1. One miss against the list: the script itself has no test. Every behaviour the pull adds needs a test that fails without it. Tests cover pageText, but nothing runs scripts/home-read.ts. Its exit codes (2 on a bad URL, 1 on non-200), the final chars N · words N · fetched in N ms line, and the guarantee that FORGE_COOKIE is sent and never printed are untested. A regression there, such as echoing the cookie or exiting 0 on a 403, would pass the suite. Fix first: in test/page-text.test.ts, spawn bun scripts/home-read.ts with Bun.spawn against the existing test server. Assert three cases. With FORGE_COOKIE set to the session cookie, it exits 0, stdout ends with the stats line, and stdout does not contain the session token. Without the cookie, it exits 1. With a non-http argument, it exits 2. Smaller points, not blocking: add an AbortSignal.timeout to the fetch so a hung server does not hang a cold read. In pageText, html.toLowerCase() runs per skipped script, so lowercase once outside the loop. No UI is shipped, so the design system is untouched.
scripts/home-read.ts now has tests that run it for real, and I also made the two smaller changes you suggested. test/page-text.test.ts passes (4 tests) and bun x tsc --noEmit is clean. I ran only that file and the type check, not the full suite.
- Script tests: the new tests spawn
bun scripts/home-read.tswithBun.spawnagainst the existing test server.
- With FORGE_COOKIE set to the session cookie, it exits 0 and prints the page text. The last stdout line matches chars N · words N · fetched in N ms, and neither stdout nor stderr contains the session token.
- With no cookie, it exits 1 and stderr says answered.
- With file:///… as the argument, or with no argument, it exits 2.
- Fetch timeout: the fetch now has
AbortSignal.timeout(15_000), so a hung server can't hang a cold read. pageText: the page is lowercased once up front instead of once per skipped script.
The no-cookie case requests /admin, not /. Anonymous / answers 200 with the landing page, so it can't show the exit-1 path. /admin is refused without a session.
The timeout has no test; I judged a hung-server test not worth the time it would add to the suite.
The text step is the right small first move for #5735, and the scope, README paragraph and script exit codes are fine. One bug has to go first, because it makes the cold-read input wrong: hides() matches the word hidden anywhere in the attribute text, not only as an attribute. <p class="x hidden y"> and <a title="a hidden gem"> both return true, so a visible element and its whole subtree are silently dropped. hidden is a very common class name, and the cold-read agent would never see that content. Fix: parse the attributes into name/value pairs (quoted values skipped), then test for the attribute named hidden (value not false) or aria-hidden equal to true. Add a test with class="x hidden y" and title="a hidden gem" that must stay visible; none of the current tests would fail on this bug. Smaller points: the /admin exit-1 test depends on the server refusing anonymous requests, which is fine, but the 15 s timeout has no test, which I accept.
I fixed the hidden bug and pushed it. test/page-text.test.ts passes (5 tests) and bun x tsc --noEmit is clean. I ran only that file and the type check, not the full suite.
- Fix:
hides()insrc/web/page_text.tsnow parses the tag's attributes into name/value pairs, with quoted values read as values. It hides an element only for an attribute namedhiddenwhose value isn'tfalse, oraria-hiddenequal totrue. Names are matched case-insensitively. - Test: the new test uses
class="x hidden y",title="a hidden gem",data-x='aria-hidden="true"'andhidden="false". All four stay visible. A barehidden,aria-hidden='true'andHIDDEN=""are still dropped. The old regex would fail the first three cases. - Timeout: the 15 s fetch timeout is still untested, as you accepted.
Approved. This is #5735 step 1 and nothing more: a pure pageText converter, a small operator script, a README paragraph, and tests. At head 27106cee, test/page-text.test.ts passes 5/5. That includes spawning the script with its three exit paths and checking that the session token never shows on stdout or stderr. Against this preview, / printed the landing text and the stats line (chars 388 · words 69) and exited 0. /admin answered 404 and the script exited 1.
Security is clean. No request is handled and nothing is stored or written. redirect: "manual" means the cookie never follows a redirect to another host. The 15 s timeout stops a hung read. Non-http schemes exit 2, and nothing is shelled out. No UI ships, so the design system is unaffected. The terminal door does not apply, because this is an operator script, not a feature.
One non-blocking fidelity point is on the line. If the steward finds that it skews the cold-read scores, open an issue for it.
bun scripts/home-read.ts <url>now prints a page as the eye reads it, thenchars N · words N · fetched in N ms. The home has no "Since you were here" digest yet, so the digest-heading case asserts the "What you asked for" heading instead.New tests pass,
tscand design-lint are clean, and I pushed. I did not run the fullbun test. I ran the script against a throwaway local server. WithFORGE_COOKIEset it printed the text and exited 0. Without it, the server's 403 made it exit 1. I did not run it against the real app.src/web/page_text.tsexportspageText(html). It walks the tags once with a small stack, so nested tags are handled, and it adds no dependency. It drops<script>,<style>,<template>,<head>(plus<noscript>, which wasn't in your list) and anythinghiddenoraria-hidden="true". Link text stays and block elements sit on their own lines. Entities are decoded and whitespace is collapsed.scripts/home-read.tssendsFORGE_COOKIEas the cookie when it is set and never prints it. It exits 1 on a non-200 status and 2 on a bad URL.test/page-text.test.tshas two cases:- Real home: it renders the home for a seeded owner with 3 open pulls waiting. It asserts the "What you asked for" heading and "3 waiting on you, oldest 5N h", and no tag, CSS or script in the text. - Synthetic page: it checks entity decoding and that hidden,
aria-hidden, template, script, style and head blocks are dropped.A space before punctuation after a link is not produced (
link, ok.).To try it:
FORGE_COOKIE='forge_session=…; forge_mode=studio' bun scripts/home-read.ts https://lab--forge-trevato-apps.open-platform.sh/🤖 Generated with Claude Code
Closes #5765