Give ParaSend a product page and put the buyer's questions first on ParaSign - #339
Conversation
390px, no sessionPlaywright, Chromium, 390x844, dpr 2, served from /parasign, first screen/parasign, section 01: the split/parasend, first screen/parasend, section 01: the split/sign, the copy above the toolThe three mono facts and the link to the product page are new. So is a CTA that looks like a button: |
Review of #339 read both product pages as a buyer and found the same defect twice: the first paragraph explains the mechanism to someone who already knows what it is. "ML-DSA-65", "transparency log", "ciphertext", "RAM" and above all "the relay" appear before the page has said what the reader gets. The relay is our word for our own server and it is not defined anywhere on either page. So both ledes now say what happens in words a reader already has, and the mechanism keeps its place under "06 / The proof", where the /about sentences ui-truthfulness pins are still quoted verbatim. /parasign ParaSign signs your PDF on your own computer. The document and your signing key never leave your browser. Whoever opens the file later can check for themselves that nothing in it was changed, without asking us and without an account. /parasend Your file is scrambled on your own device before anything is sent. We never write it to disk, and once the person you sent it to has downloaded it, it is gone. Servers in Germany, under EU law. Order. Section 01 ran the full tier tables, so on a 390px screen the reader scrolled roughly 1,850px of prices, "Business 299 euro/month" among them, before a word about what the product does. The split stays at 01 because it is the thing that has to be clear first, but only as the two paragraphs /pricing already carries plus a link. The tables moved to 05, after "what it does" and "how to begin". Measured at 390x844: tiers now start at 4,449px on /parasign and 4,315px on /parasend instead of 725px and 720px. eIDAS, on the first screen. The h1 promises "prove it years later" and the scope note sat at 6,386px of a 7,922px page, screen 8 of 9, while for a law or advisory firm it is the first question. A line under the buttons now says it is an ordinary electronic signature (SES) and not a qualified eIDAS signature, and links to the full note. The note itself is unchanged and still pinned. Who it is for, on the first screen: an audience line under each lede, at 487px and 392px. And the section label a reader meets first is no longer "post-quantum e-signing" but "document signing with proof that lasts". Three claims that read as more than they are: - SLA 99.9% on /parasend contradicted the published 99.95% on /pricing and /sla. This is the drift the PR exists to prevent, and nothing pinned it, so pricing-page.test.js now reads the Enterprise target out of sla.html and fails any page that states a different SLA figure. It reads the Enterprise cell by name because /sla also publishes 99.5% for Community. - "IEC 62443 / NIS2 / NEN 7510 documentation" stood on screen 3 while the "no third-party certification" limit was 4,000px lower. The limit is in the bullet now, and pinned there. - "Post-quantum, zero-knowledge" opened the honest paragraph on /parasign with a term nothing on the page supports. Replaced by what actually happens: we never see your document. ParaShare was a third product name with no explanation, in a tier bullet and in step 2. It is named as what it is, the send screen of ParaSend, in both places and in the ParaShare card under "what it does". Tier cards now carry the names /pricing sells (Free, Pro, Business, Enterprise) instead of product-prefixed variants, which is what the tier-name check from #325 asks for, and the Community wording gets its bridge sentence to the tier /pricing calls Free. /sign: "the private key never reaches the relay" is now "never reaches our servers". The rest of that page was read as fine. Rebased onto main, which now carries #325's own /parasign. Conflicts in bron-seo/apply_seo_head.py, bron-seo/build_sitemap.py, frontend/sitemap.xml, relay/test/pricing-page.test.js and frontend/parasign.html resolved by taking this branch's page and keeping every assertion #325 added, including the ones that then failed against it. Green locally: seo-contract (9), ui-truthfulness, links, frontend-loading- contract (7), navigation-shell (24), pricing-page (44), check-csp-inline.sh, check-cache-bust.sh, eslint (exit 0). No horizontal overflow at 390px on any of the three pages. index.html, apply-nav.py and js/nav-auth.js untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJk2nCLCLmi3F71qkCUn7N
22ca883 to
e990d86
Compare
Screenshots, 390x844, deviceScaleFactor 2Captured with Playwright against a static server on the head of this branch.
The y-positions those shots were measured from are in the PR body. The three the
On /parasign that is a move from 6,386px to 737px for the eIDAS note: from |
Round two, re-measuredThe static server the earlier shots were taken against was stopped by another
The /parasend hero now reads "10 transfers a month, 5 MB a file, links that last |
One more instance of the same defect, out of scope hereWhile checking that the new pointer is honest, the same problem turned up one <p class="rule-verify"><a href="/security">verify: the jurisdiction table →</a></p>
Not touched in this PR: /pararules and index.html are outside its three pages, |
f0fdb10 to
5bdb03f
Compare
…araSign Three pages, one shape, taken from docs/brand/messaging.md: what you can do here, who it is for, the split with its real number, who is behind it, then the proof and the honest limits. The algorithm names moved to the bottom of every page, where they are the reason a plain sentence above is true. /parasend is new. ParaSign has had a page since #325; ParaSend had nothing, so the send half of the product could not be found by name. It follows the /parasign pattern the guide asks for. What changed against the review, per page. /parasign - H1 is the one the guide specifies: "Sign documents in your browser, prove it years later." The old H1 was the product name, which does not say what the page is for. - The opening paragraph says what a buyer gets, in two sentences and 160 characters, and no longer opens on ML-DSA-65 and a transparency log. The guide's own tone rule keeps cryptography below the fold; the sentence it specifies for this page contradicted that rule, so the sentence now sits verbatim in the proof block at 04 instead of in the hero. - The buyer is named in the first screen: legal, finance and healthcare practices in the EU, the same audience index.html states. - The eIDAS status moved from 6,386px to 737px. A lawyer asks it first; the page now answers it before the tiers instead of on screen 8 of 9. - The free promise carries its number. "Free forever" became "Free on the Community plan: 2 signatures a month, no card, forever." - "Zero-knowledge" is gone from the scope note. It described nothing this page can point at, and it sat next to the most honest paragraph on the page. - The proof block quotes /about word for word and carries the EU claim in the form #328 settled: no US provider in the data path, with Resend named in the same breath. /parasend - Section 02, the section that has to convince, said AES-256-GCM, ciphertext, RAM, ML-KEM-768, tenant and RAM budget. It now says what the buyer gets. The algorithm names are in 06, each next to the page that checks it. - The free promise carries its limits: links that last an hour, gone after one read, 10 uploads an hour. - The bridge sentence explaining that Community is listed as Free is gone, and the tier card is named Community. #328 renamed the tier on /pricing, so there is nothing left to explain away. - The founder block quotes /about instead of composing a sentence about US subscriptions. #332 put that sentence on /about, so it is now sourced. /sign - "Private key" was the last jargon in the first screenful. Same fact, words a practice manager already uses. One layout fix, page-local. At 390px the house .sec-head keeps the section number in its own grid column, which left the H1 about 160px to wrap in: six lines of headline on /parasign, and the buttons and the eIDAS note off the first screen. On a narrow screen the number now stacks above the headline. design-system.css is untouched, because it is shared with pages this PR does not open. Measured at 390px with Playwright, per page: no horizontal overflow anywhere (scrollWidth 390), and on /parasign the H1, the audience, both buttons, the free promise and the eIDAS note all land inside the first 844px.
Two gates that looked like gates and were not, both found by sabotage rather
than by reading.
1. The monthly price on both product pages was pinned by nothing.
relay/test/pricing-page.test.js asserted with a bare substring, so
includes('€49') was satisfied by the annual "€499 excl." standing on
the same card, and includes('€15') by "€150". Verified: €49 to
€59 on /parasign and €15 to €19 on /parasend both stayed green.
Those are the two numbers a buyer clicks. An amount now has to end where it
says it ends and sit on the card of the plan it belongs to; the opening line
"business plans from €49 a month" can no longer stand in for the tier.
Both sabotages go red after this, and so do the annual amounts.
2. A claim that has to be read in the first screen was asserted against the
whole file, which is satisfied by screen 8 of 9. The eIDAS scope note, the
audience line and the free promise with its number are now asserted against the
hero section of the page, not against the file.
Also pinned, each one a sentence that had drifted or had no source:
- The SLA figure is read off /sla by name (the Enterprise cell, not the first
.uptime on the page) and every page repeating it has to match. This is how
"SLA 99.9%" reached a product page while /pricing and /sla both published
99.95%: the quota lines were pinned, the service lines were not.
- The EU claim in the form #328 settled: "No US provider in the data path"
with the Resend exception in the same breath, on /, /parasign and /parasend.
The broader "no US company" wording is refused on the product pages;
section 9 of the guide holds it open, and it stays on /security where the
table qualifies it.
- The give-back sentence, now that #332 has put it on /about, quoted rather
than composed.
- The free plan is called Community on /sign too, and "Free accounts" is
refused there.
The stale string is gone as well: the suite pinned "The community tier stays
free", a sentence #328 removed from /pricing. It now pins what /pricing
actually ships.
…rints Four corrections on /parasend and /parasign, each one a claim that was contradicted by the source the page itself points at. 1. The free number was the wrong number. Both pages said "10 uploads an hour". That figure is real, but it is ANON_RATE_PER_HOUR on POST /v2/anon-inbound (relay/relay.js), an endpoint deprecated on 2026-05-28 that answers with Deprecation and Sunset headers and retires on 2026-12-31. It is not what a Community account gets. relay/lib/tiers.js gives the Community row 10 transfers a month, 5 MB a file, a one hour link and one read, and relay.js enforces those with a 402 (monthly_transfer_quota_reached) and a 413 (Max 5MB). The hero and the Community card now state those. ParaSend Pro gains its real transfer ceiling, 500 a month, in place of "No IP rate limit", which was the same anon-endpoint artefact. The same applies where /parasign quotes a ParaSend limit: the ParaSign Pro card said "Unlimited transfers". A ParaSign Pro account derives plan_parasend 'pro' (relay/lib/entitlements.js derivePlanParasend), which is 500 a month, and that file has a hard rule that no metered tier is unbounded: even enterprise gets a finite ceiling, never Infinity. The card now says 500. /pricing and index.html carry the same upload figure. They are corrected in their own PRs and are deliberately not touched here. 2. ParaShare has no signature, and the register the card links to says so. /parasend promised "ML-DSA-65 signed receipts" for ParaShare and cited the algorithm register in the same sentence. That register lists ParaShare (webapp) with SIG n/a on the pre-v1 hybrid wire, migrating to v1; ML-DSA-65 on the v1 wire is sdk-py 3.0.0 and sdk-js 3.0.0. The card now states what the register states. The buyer-facing half of the same claim went with it: section 02 promised the recipient "proof that the file came from you", which is a signature claim on a path that has no signature. It now says what the path does deliver, that the file opens on the recipient's registered device and nowhere else, and that an altered file does not open at all. 3. The jurisdiction table does not carry Bunny. Both pages named Bunny DNS (Slovenia) and then sent the reader to the Jurisdiction and privacy table on /security to check it. That table lists Hetzner Nuremberg, the legal jurisdiction, the CLOUD Act row, retention, IP logging and analytics. DNS is not a row in it. The sentence stays exactly as index.html carries it; the pointer now goes to /privacy and to ParaRule 5, which do carry it. 4. "Fixed-size ciphertext" is a ParaShare property, not a property of every transfer. /security records "approximate encrypted size (up to 5 MB; exact 5 MB for ParaShare)". Bounded to the path it holds on. And the Pro list was missing "Email notifications via Resend", which /pricing carries and which names a subprocessor, so its absence quietly dropped a disclosure. The pins moved with the claims. The limit lines are read from relay/lib/tiers.js instead of compared against the wording on /pricing: comparing two pages only proves they agree, which is exactly the failure here, since /pricing and index.html were wrong in the same way. The uploads-per-hour figure and "unlimited transfers" are now refused on both product pages under any wording. The register is parsed and the page held to the row it cites. The EU sentence is pinned to index.html, where it ships, and an assertion fails if the /security table ever starts naming Bunny, at which point the pointer may come back. Seventeen sabotages, all red, including edits to relay/lib/tiers.js itself and to the register row on /crypto-agility.
#334 tightened the head contract: the JSON-LD WebPage name has to be the page title character for character, and every public page has to carry an Organization node naming the company and the founder. /parasend was written before that landed, so on the merged result tests/seo-contract.test.mjs failed twice: the WebPage name used a hyphen where the title uses a middot, and there was no Organization node at all. The block comes from bron-seo/apply_seo_head.py, not from hand editing, so it is the same graph the other 39 pages carry and it stays that way the next time the script runs. `--check` is clean afterwards. Found by rebasing on main and running the suite against the merged tree. A green run on the branch head only describes the branch head, which is how this would have reached main red.
5bdb03f to
5e4e518
Compare
#354 fixed three of the four pages, and its guard does not cover the fourth#354 landed while this PR was rebasing, and it corrects the same defect reported
<li>10 uploads per hour per IP</li>That is the ParaSend Community card on the page that sells the plan. Verified on So the state after #354 is: /, /docs and /docs/api.md say 10 transfers a month Not fixed here on purpose. /pricing is outside this PR's three pages, and the |
The three pages a buyer reaches after the homepage were written for someone
who already believes the product. /about opened on the mission and put the
founder in section 03, below the cryptography. /security opened on defence in
depth and kept the one row a buyer came for, the jurisdiction table, seven
screens down. /trust never said who it was for.
They now run the order docs/brand/messaging.md fixes: what this is, who it is
for, who is behind it, a next step, then the proof including the honest
limits. Copy, hierarchy and order only. No restyle. index.html, apply-nav.py
and js/nav-auth.js are untouched.
/about is the founder page.
The lede is plain language: sign and send documents so only you and the
recipient can read them, so anyone can check later that the document is
genuine, on servers in Germany under EU law. Under it, in the first phone
screen, who it is for, then Mick Beer with the title the guide fixes, then
two buttons. Measured at 390x844: the founder sits at y=463 and the second
button at y=608. The section number "00" is hidden in the mobile override
instead of landing under the H1 as a stray number.
Two sentences are gone. "The cryptography is post-quantum, which is the
proof that it still holds up in ten years" was not a proof and not
checkable, on the page whose argument is that everything on it is
checkable. The founder paragraph explained the free plan with a
jurisdiction claim ("should not depend on a US subscription"), which the
guide forbids beside his name. In its place stands the paragraph #332
landed on main while this branch was in review: the Community plan is his
way of giving something back to society, the business plans pay for it,
that is the whole arrangement, and it is why the Community plan is not a
trial and has no end date. Taken from main verbatim, moved with the section
into the top half, and pinned so the two copies cannot drift.
The tier block names the free plan Community, which is what /pricing prints
on the card since #328.
/security answers "why would I trust you" before it answers "how it works".
The promise carries its own scope. It read "even if our own server is
broken into, nobody can read your documents" flat out, while ten screens
lower the page says the Chromium and Outlook extensions take a server-side
encryption path. For an extension user the flat version is untrue today, so
the exception now travels with the promise, in the hero.
It also says what that exception costs the reader, in words rather than in
ours: the extensions encrypt on our server, which means we can read what
you upload through them until that is changed. "Treat those uploads as
relay-side" was the internal phrasing, and relay is exactly the word this
branch removed from the /trust hero. Neither hero uses it now, and a test
says so.
The first screen also carries who is behind the page (Paramantis Solutions
B.V., Harderwijk, KvK 42115132, and the founder line) and the next step.
The buttons used to sit at roughly y=11400 on a phone; "See pricing" is now
at y=656 and "Verify a document" at y=716.
The free plan is Community here too. A first version of this branch left
"ParaSign Free and ParaSend Free cost EUR 0" standing at r.448, because the
only check that forbade the old name was scoped to /about. That check now
covers all three pages, and the sitewide sweep in ui-truthfulness gained the
shape it was missing, so any page using it fails. The page description no
longer sells "relay architecture" either.
The audit block says what /docs#audits actually adds up to: three external
audits in April 2026, two by R. Zwarts and one by Ryan Williams of Smart
Cyber Solutions, forty findings, four of them critical, with the resolving
commits in the table. The previous round claimed "the audit reports
themselves are not published" and pinned it. That was false:
docs/security-audit-2026-04.md is the full Smart Cyber Solutions writeup
and it ships in the site tree. Only the raw pentest output is missing,
which is what that document itself calls the raw report. Both pages now say
that, and link the report.
The CLOUD Act row and the EU-law card move to the data-path wording, as
section 9.2 of the guide requires: no US provider in the data path, with
Resend named as the one exception in the same breath. The old row read "not
applicable: no US infrastructure, no US company", which is broader than
/privacy allows.
/trust names its reader, then gives that reader somewhere to go.
The hero addresses organisations running their own relay and anyone
checking a supplier, and now offers them two buttons instead of one text
link mid-paragraph (y=416 and y=476). The first sentence under the hero no
longer says "the operator who runs the relay": relay is not a word a
supplier reviewer knows.
The page called itself Trust & Verification in its title and H1 while the
social card and the structured data still said Trust & Transparency. #334
then rewrote every head on the site, so the literal strings belong there and
tests/seo-contract.test.mjs pins them. What this branch pins is the
relation: the title, og:title, twitter:title and the JSON-LD name must name
the page the same, and that name must contain the words the H1 uses.
Punctuation and case are not the point; Transparency versus Verification
was. Its plan sentence named two of the three paid
ParaSign tiers; it names all three, and both free tiers as Community.
Tests
tests/ui-truthfulness.test.mjs pins each of the above, and each one was
sabotaged in place to confirm it goes red: the ten-year promise, the
give-back sentence, the US-subscription framing, the Community rename, the
CLOUD Act row, the Resend exception dropped from the card, the finding
counts, the "reports not published" sentence, the auditor names on
/security as well as /trust, the two hero buttons on each page, the
who-is-behind line, the bounded promise, the /trust og:title and JSON-LD
name, the jargon in the first screen, and the paid tiers. Twenty-six
sabotages, twenty-six red, no gaps.
tests/site-claims.test.mjs block 11 reads the numbers out of the /pricing
tier card that owns them and requires /about to repeat them; it follows the
Community rename and now also forbids "ParaSign Free" on /about.
node --test over the CI glob: 162 pass, 0 fail. check-csp-inline,
check-cache-bust and eslint exit 0. At 390px all three pages have
scrollWidth === clientWidth === 390.
Two things this branch had to touch on main's side
tests/ui-truthfulness.test.mjs did not parse on main: #336 and #339 each
landed a const named pricingVisible in the same module, for two different
values. The one #339 added is renamed to pricingText, with a comment saying
why. Without it nothing in this file runs, this branch included.
tests/site-claims.test.mjs block 12 read the /pricing tier cards by their
section heading. #336 renamed both headings and put ParaSign first, so the
split now finds them by product prefix and orders them by position.
Not touched, and still broken on main: relay/test/pricing-page.test.js r.498
declares tiers twice, so eslint cannot parse it. It is outside these three
pages and outside this PR.
Still open, deliberately
The eIDAS level (SES on /about, advanced (AES) in the /pricing FAQ) is a
real contradiction and needs a decision, not a copy edit. This branch stops
it spreading and pins that it stays on the one page that has always carried
it.
The three pages a buyer reaches after the homepage were written for someone
who already believes the product. /about opened on the mission and put the
founder in section 03, below the cryptography. /security opened on defence in
depth and kept the one row a buyer came for, the jurisdiction table, seven
screens down. /trust never said who it was for.
They now run the order docs/brand/messaging.md fixes: what this is, who it is
for, who is behind it, a next step, then the proof including the honest
limits. Copy, hierarchy and order only. No restyle. index.html, apply-nav.py
and js/nav-auth.js are untouched.
/about is the founder page.
The lede is plain language: sign and send documents so only you and the
recipient can read them, so anyone can check later that the document is
genuine, on servers in Germany under EU law. Under it, in the first phone
screen, who it is for, then Mick Beer with the title the guide fixes, then
two buttons. Measured at 390x844: the founder sits at y=463 and the second
button at y=608. The section number "00" is hidden in the mobile override
instead of landing under the H1 as a stray number.
Two sentences are gone. "The cryptography is post-quantum, which is the
proof that it still holds up in ten years" was not a proof and not
checkable, on the page whose argument is that everything on it is
checkable. The founder paragraph explained the free plan with a
jurisdiction claim ("should not depend on a US subscription"), which the
guide forbids beside his name. In its place stands the paragraph #332
landed on main while this branch was in review: the Community plan is his
way of giving something back to society, the business plans pay for it,
that is the whole arrangement, and it is why the Community plan is not a
trial and has no end date. Taken from main verbatim, moved with the section
into the top half, and pinned so the two copies cannot drift.
The tier block names the free plan Community, which is what /pricing prints
on the card since #328.
/security answers "why would I trust you" before it answers "how it works".
The promise carries its own scope. It read "even if our own server is
broken into, nobody can read your documents" flat out, while ten screens
lower the page says the Chromium and Outlook extensions take a server-side
encryption path. For an extension user the flat version is untrue today, so
the exception now travels with the promise, in the hero.
It also says what that exception costs the reader, in words rather than in
ours: the extensions encrypt on our server, which means we can read what
you upload through them until that is changed. "Treat those uploads as
relay-side" was the internal phrasing, and relay is exactly the word this
branch removed from the /trust hero. Neither hero uses it now, and a test
says so.
The first screen also carries who is behind the page (Paramantis Solutions
B.V., Harderwijk, KvK 42115132, and the founder line) and the next step.
The buttons used to sit at roughly y=11400 on a phone; "See pricing" is now
at y=656 and "Verify a document" at y=716.
The free plan is Community here too. A first version of this branch left
"ParaSign Free and ParaSend Free cost EUR 0" standing at r.448, because the
only check that forbade the old name was scoped to /about. That check now
covers all three pages, and the sitewide sweep in ui-truthfulness gained the
shape it was missing, so any page using it fails. The page description no
longer sells "relay architecture" either.
The audit block says what /docs#audits actually adds up to: three external
audits in April 2026, two by R. Zwarts and one by Ryan Williams of Smart
Cyber Solutions, forty findings, four of them critical, with the resolving
commits in the table. The previous round claimed "the audit reports
themselves are not published" and pinned it. That was false:
docs/security-audit-2026-04.md is the full Smart Cyber Solutions writeup
and it ships in the site tree. Only the raw pentest output is missing,
which is what that document itself calls the raw report. Both pages now say
that, and link the report.
The CLOUD Act row and the EU-law card move to the data-path wording, as
section 9.2 of the guide requires: no US provider in the data path, with
Resend named as the one exception in the same breath. The old row read "not
applicable: no US infrastructure, no US company", which is broader than
/privacy allows.
/trust names its reader, then gives that reader somewhere to go.
The hero addresses organisations running their own relay and anyone
checking a supplier, and now offers them two buttons instead of one text
link mid-paragraph (y=416 and y=476). The first sentence under the hero no
longer says "the operator who runs the relay": relay is not a word a
supplier reviewer knows.
The page called itself Trust & Verification in its title and H1 while the
social card and the structured data still said Trust & Transparency. #334
then rewrote every head on the site, so the literal strings belong there and
tests/seo-contract.test.mjs pins them. What this branch pins is the
relation: the title, og:title, twitter:title and the JSON-LD name must name
the page the same, and that name must contain the words the H1 uses.
Punctuation and case are not the point; Transparency versus Verification
was. Its plan sentence named two of the three paid
ParaSign tiers; it names all three, and both free tiers as Community.
Tests
tests/ui-truthfulness.test.mjs pins each of the above, and each one was
sabotaged in place to confirm it goes red: the ten-year promise, the
give-back sentence, the US-subscription framing, the Community rename, the
CLOUD Act row, the Resend exception dropped from the card, the finding
counts, the "reports not published" sentence, the auditor names on
/security as well as /trust, the two hero buttons on each page, the
who-is-behind line, the bounded promise, the /trust og:title and JSON-LD
name, the jargon in the first screen, and the paid tiers. Twenty-six
sabotages, twenty-six red, no gaps.
tests/site-claims.test.mjs block 11 reads the numbers out of the /pricing
tier card that owns them and requires /about to repeat them; it follows the
Community rename and now also forbids "ParaSign Free" on /about.
node --test over the CI glob: 162 pass, 0 fail. check-csp-inline,
check-cache-bust and eslint exit 0. At 390px all three pages have
scrollWidth === clientWidth === 390.
Three things this branch had to touch on main's side
tests/ui-truthfulness.test.mjs did not parse on main: #336 and #339 each
landed a const named pricingVisible in the same module, for two different
values. The one #339 added is renamed to pricingText, with a comment saying
why. Without it nothing in this file runs, this branch included.
tests/site-claims.test.mjs block 12 read the /pricing tier cards by their
section heading. #336 renamed both headings and put ParaSign first, so the
split now finds them by product prefix and orders them by position.
relay/test/pricing-page.test.js r.498 required ../lib/tiers a second time,
under a name the file already binds at r.18. That is a SyntaxError, and it
took down two CI checks on every PR: "static, every name must exist" (eslint
cannot parse the file) and "relay - unit suite". The duplicate line is
removed; the binding at the top of the file is the same module. This was
outside the three pages, but no PR can go green while it stands.
Still open, deliberately
The eIDAS level (SES on /about, advanced (AES) in the /pricing FAQ) is a
real contradiction and needs a decision, not a copy edit. This branch stops
it spreading and pins that it stays on the one page that has always carried
it.
…ly form /parasend had no inbound link anywhere on the site and /parasign had one, from /sign. Both product cards on the homepage now lead with the page that explains the product and keep the app as the second action. tests/links.test.mjs gains a reachability check (a page nothing links to is indexed and unreachable at the same time) and tests/navigation-shell.test.mjs pins the order per card. outbound_per_hour has been enforced since the rate-limit finding (relay.js, outboundRateOk on GET /v2/outbound/:hash) and appeared on no page: a Community account scripting its own downloads hit a 429 it was never told about. It is now on /parasend per tier and on the /parasign Pro card, pinned to tiers.js like the other limits. It is stated as retrievals, not sends: it counts the account's own fetches with its own key, and the browser recipient path (GET /v2/dl/:token/get) has no rate limit at all, so a recipient never spends the sender's hour. "Unlimited receiving" pointed at no field in tiers.js or entitlements.js. It is true: nothing meters receiving, and being invited, opening an invitation and fetching the document back are not counted on any plan. The line now reads "No limit on receiving", /parasign says what it rests on and that signing what you receive still counts, and pricing-page.test.js pins it negatively: the day a receiving dimension appears in tiers.js or in the entitlement quotas, the test goes red and the pages have to name the real ceiling. The same Community limit shipped as "10 transfers a month" on / and /docs and "10 transfers per month" on /pricing, /parasend and /parasign. The site's form is "a month" everywhere now. The number checks accept both spellings on purpose, so a tiers.js change fails on the number; the wording is pinned once, so a page that drifts back fails on wording. Two test files could not parse on main. #336 and #339 each added a "const tiers = require('../lib/tiers')" to relay/test/pricing-page.test.js and a "const pricingVisible" to tests/ui-truthfulness.test.mjs; both merged, both files threw SyntaxError, and neither suite ran. Deduplicated as a one-line change per file, so a hotfix that does the same rebases cleanly on top. So that this cannot recur through this branch: every block added here sits in a function scope and reaches the top level with nothing. The two helpers that are used from two distant places are function declarations, which tolerate being declared twice where a const throws. Also in ui-truthfulness: the ParaShare register assertions sat inside "if (shareSig === 'n/a')", so moving the webapp to ML-DSA-65 in the register switched the check off instead of failing it. They now run on whatever the SIG column holds, in both directions, and an unmapped value fails loudly.





What this is
Three commercial pages, one shape, taken from
docs/brand/messaging.md(#331):what you can do here, who it is for, the split with its real number, who is
behind it, then the proof and the honest limits.
/parasendis new;/parasignhas existed since #325 and is rebuilt around the buyer;
/signloses the lastpiece of jargon in its first screenful.
Rebased on
mainat 6dded85. Conflicts resolved in favour ofmainforeverything outside these three pages, including the Community naming from #328
and the /about give-back sentence from #332.
The review points, and what each one got
ParaSign · document signing by Paramant.relay/lib/tiers.js.Two things beyond the list, both from the same review:
nothing the page can point at, and it stood next to the most honest paragraph
on the page.
no source. Signed in, the free plan is called Community, and the page answers what a buyer asks #332 has since put the guide's sentence on /about, so the page now
quotes it: "The Community plan is his way of giving something back to society;
the business plans pay for it."
The two gates that were not gates
Both found by sabotage, not by reading.
The monthly price on both product pages was pinned by nothing.
relay/test/pricing-page.test.jsasserted with a bare substring, soincludes('€49')was satisfied by the annual€499 excl.on the samecard, and
includes('€15')by€150. Verified:€49to€59on /parasign and€15to€19on /parasend both stayedgreen. Those are the two numbers a buyer clicks. An amount now has to end where
it says it ends and sit on the card of its own plan, so the opening line
"business plans from €49 a month" cannot stand in for the tier.
A claim that has to be read first was asserted against the whole file, which
is satisfied by screen 8 of 9. The eIDAS scope note, the audience line and the
free promise with its number are now asserted against the hero section, not the
file.
Also pinned: the EU claim in the form #328 settled ("No US provider in the data
path", with the Resend exception in the same breath) on
/,/parasignand/parasend, with the broader "no US company" wording refused on the productpages, since section 9 of the guide holds it open and it stays on /security
where the table qualifies it. And the stale string is gone: the suite pinned
"The community tier stays free", a sentence #328 removed from /pricing.
Second round: four claims contradicted by their own source
Each of these was a claim with a checkpoint attached, where the checkpoint said
something else. That is worse than no checkpoint, so the pins moved with the
claims.
1. The free number was the wrong number. Both pages said "10 uploads an
hour". The figure is real, but it is
ANON_RATE_PER_HOURonPOST /v2/anon-inbound(relay/relay.js), deprecated on 2026-05-28, answeringwith
DeprecationandSunsetheaders, retiring 2026-12-31. It is not what aCommunity account gets.
relay/lib/tiers.jsgives the Community row 10transfers a month, 5 MB a file, a one hour link and one read;
relay.jsenforces them with a 402 (
monthly_transfer_quota_reached) and a 413(
Max 5MB). Hero and Community card now state those. ParaSend Pro gains itsreal ceiling, 500 a month, in place of "No IP rate limit", the same artefact.
On /parasign the ParaSign Pro card said "Unlimited transfers". A ParaSign Pro
account derives
plan_parasend: 'pro'(entitlements.js derivePlanParasend),which is 500 a month, and that file has a hard rule that no metered tier is
unbounded: even enterprise gets a finite ceiling, never
Infinity. Now 500.2. ParaShare has no signature, and the register the card cites says so.
/parasend promised "ML-DSA-65 signed receipts" for ParaShare and linked to the
algorithm register in the same sentence. That register
(
crypto-agility.html:429-434) lists ParaShare (webapp) with SIG n/a on thepre-v1 hybrid wire, migrating to v1; ML-DSA-65 on the v1 wire is sdk-py
3.0.0 and sdk-js 3.0.0. The card now states what the register states. The
buyer-facing half went with it: section 02 promised the recipient "proof that
the file came from you", a signature claim on a path with no signature. It now
says what the path does deliver: the file opens on the recipient's registered
device and nowhere else, and a file altered on the way does not open at all.
3. The jurisdiction table does not carry Bunny. Both pages named Bunny DNS
(Slovenia) and then sent the reader to the Jurisdiction and privacy table on
/security. That table (
security.html:351-362) lists Hetzner Nuremberg, thelegal jurisdiction, the CLOUD Act row, retention, IP logging and analytics. DNS
is not a row in it. The sentence stays exactly as
index.html:382carries it;the pointer now goes to /privacy and ParaRule 5, which do carry it.
4. Two smaller ones. "Fixed-size ciphertext" is a ParaShare property, not a
property of every transfer (
security.html:358: "approximate encrypted size (upto 5 MB; exact 5 MB for ParaShare)"), so it is bounded to that path. And the Pro
list was missing "Email notifications via Resend" (
pricing.html:267), whichnames a subprocessor, so its absence quietly dropped a disclosure.
What the pins do now
The ParaSend limit lines are read from
relay/lib/tiers.jsinstead of comparedagainst the wording on /pricing. Comparing two pages only proves they agree, and
that is exactly the failure here: /pricing and index.html are wrong in the same
way, so a page-to-page pin was green throughout. The uploads-per-hour figure and
"unlimited transfers" are refused on both product pages under any wording. The
algorithm register is parsed and the page held to the row it cites. The EU
sentence is pinned to index.html, where it ships, and an assertion fails if the
/security table ever starts naming Bunny, at which point the pointer may return.
Sabotage log
Each string mutated on its own, suite run, reverted.
€49to€59€499to€599€15to€19SLA 99.95%toSLA 99.9%tiers.jscommunitytransfers_month10 to 20tiers.jsprotransfers_month500 to 900tiers.jscommunityfile_mb5 to 8tiers.jscommunitysigns_month2 to 3Measured at 390px, Playwright, y from the top of the document
No horizontal overflow anywhere:
scrollWidthis exactly 390 on all three.On /parasign the H1, the audience, both buttons, the free promise and the eIDAS
note all land inside the first 844px.
One layout fix, page-local
At 390px the house
.sec-headkeeps the section number in its own grid column,which left the H1 about 160px to wrap in: six lines of headline on /parasign,
with the buttons and the eIDAS note pushed off the first screen. On a narrow
screen the number now stacks above the headline. The rule lives in each page's
own
<style>;design-system.cssis untouched, because it is shared with pagesthis PR does not open.
Checks run locally
tests/links,tests/seo-contract,tests/ui-truthfulness,tests/site-claims,tests/navigation-shell,tests/frontend-loading-contract,relay/test/pricing-page.test.js,scripts/check-csp-inline.sh,scripts/check-cache-bust.sh,eslint, andtests/static-sanity.sh(10/10, including the commit-style guard). All pass.
frontend/apply-nav.pywas run and changed nothing: nav and footer were alreadystamped correctly.
bron-seo/build_sitemap.pyregenerated the sitemap, whichadds the one
/parasendentry.Not in scope
The pages are English while the audience named on them is a Dutch office. Every
reviewer raised it; it is a sitewide decision and belongs in its own round. The
SES versus AES contradiction between /about and the /pricing FAQ is section 9 of
the guide and is likewise left where it is.