The signed-in shell: support a phone can reach, a homepage that stops selling, and one apology - #396
Merged
Merged
Conversation
Apolloccrypt
force-pushed
the
koper/ingelogde-schil
branch
from
September 3, 2026 07:37
3767ef7 to
d0b8d1f
Compare
…hat stops selling, and one apology Four things a buyer review found on the ingelogde reis, all of them on the surfaces you only see after you have paid. Support. The signed-in navigation is Documents, Send, Sign, Verify, Settings, and js/nav-auth.js DELETED the strip under the mobile drawer once you signed in, on the reasoning that the account menu carries Help from then on. On a 390px screen that left no Help at all: nav.css sheds the .nav-help text link below 700px, the drawer is pinned to the five workspace links, and the menu behind the email address is where you go to sign out, not where you go when a signature is stuck. A paying customer had to type the url. The strip stays now and carries Help alone (Sign in is not an action you still need), and the text link is back in the bar from 700px up. tests/navigation-shell counts it in TAPS: menu button, then Help, and it follows the second tap to /help so a 44px target on a dead link cannot pass. The four public nav pins are untouched. The signed-in homepage. It swapped the hero and left the entire sales page under it: why half of it is free, the two products, the price table, the founder letter. Three actions and then eight screens of pitch, for someone who had already bought. js/home-auth.js now stamps data-session="in" on <html> and index.html hides every section after the hero off that one attribute, in CSS, so a section added tomorrow is covered the day it lands and no inline script is involved. Signed out the page is byte-for-byte what it was, and the suite checks that too. Error messages. enrolEphemeralSigningKeyWithTotp translated three TOTP failures and rethrew everything else, so what reached the customer was the wire's own words: "http_502" from _postJSON, or whatever a browser calls a lost connection on a TypeError. Neither names a problem, let alone a next step. frontend/js/error-message.js owns one sentence for the unplanned case, with what to do and an address that reaches a human, and the technical detail goes to the console instead of the screen. js/parashare.page.js printed "Error: " + e.message in three places and now prints the same sentence from the same file. tests/error-message.test.mjs runs a real TypeError through the translator; four sabotages were verified red. The price question. /dashboard answered "what does it cost?" with "Paid plans start at 15 euro a month, which is ParaSend Pro" while /help answered it with "ParaSign Pro 49 euro". One customer, two of our pages, two numbers, and the cheaper one on the surface that exists for signing. The answer names both products with both prices, and relay/test/pricing-page.test.js pins both to relay/lib/billing-catalog.js the way the pricing cards are pinned, plus the plan name to relay/lib/tiers.js. No amount in it that is not on /pricing. nav-auth.js and home-auth.js changed, so their cache-busts move with them. Gates: first-screen, ui-truthfulness, site-claims, seo-contract, links, navigation-shell (51 checks), frontend-loading-contract, pricing-fold, cache-bust, csp-inline, static-sanity, check-test-declarations, eslint, app-contrast, app-theme, apply-nav-idempotent, user-dashboard-documents, apply_seo_head --check, sign-full (33/33), pricing-page (48 checks).
…cut in half Review of the first commit found the one thing it broke. Signed in at 1440 the hero shrank from 654px to 472px, and .hp-doc inside .hp-art did not shrink with it: that element is absolutely positioned and draws about 120px BELOW the 380px box .hp-art reserves for it. Signed out the overhang lands on the section underneath and nobody ever sees it. Signed in the hero is the last thing in <main>, main carries overflow:hidden, and the document, the receipt and the signature were sliced through where main ends. Making the hero taller does not fix that, and this is worth being precise about: with the padding rule removed entirely and the art left in, the overhang is still 74px past the clip. The height was never the problem. So the art goes where the rest of the pitch went. It is a document that gets signed, sealed and then burns, on a 16-second loop, and it is there to explain the product to someone who does not have it yet. A phone has hidden it since it shipped, for its own reason; signed in, every width does now. With it gone the second grid column is empty, so the hero is one column, keeping the 1120px wrap that lines it up with the nav and the footer. The check measures against the edge that actually cuts. Every box the art draws, at 1440, against main's own rectangle, which IS the clip. One exclusion, named and argued in the test: .hp-art-glow is a blurred radial gradient with inset:-40% -20%, built to bleed, with no edge that can read as cut, and it has hung 13px over the top of main since the art shipped. The signed-out control runs the same measurement and requires the art to be there and whole, so the signed-in pass cannot be a pass over an empty list. Sabotage, both red: the merged state (art visible, hero at 48px bottom padding) reports hp-doc cut, and so does the art with no padding change at all. navigation-shell is 53 checks. first-screen, ui-truthfulness, site-claims, seo-contract, links, pricing-fold, frontend-loading-contract, cache-bust, csp-inline, static-sanity, check-test-declarations and apply_seo_head --check all green.
Apolloccrypt
force-pushed
the
koper/ingelogde-schil
branch
from
September 3, 2026 07:51
d0b8d1f to
6d58f4d
Compare
Apolloccrypt
pushed a commit
that referenced
this pull request
Sep 3, 2026
Follow-up to the security review, on top of #396. The failure sites this branch added now go through failureText(), the helper #396 put at the top of this file, so /parashare has one voice for what we did not plan for instead of three. The account-key failure reports through it and keeps its own banner sentence, because a key that will not load is a case we DID plan for and "sign in again" is a better next step than "try again in a minute". Same for the relay-sector sentence; what used to be a second wording invented at the call site is now the shared one. parashare.page.js goes to ?v=5. #396 shipped a v4 of that file and the commit below this one shipped a different v4. Both sides agree on the number, so check-cache-bust cannot see it: one immutable url, two contents, and whichever a browser cached first is the one it keeps. The order of the two script tags is pinned in the suite as well, because error-message.js is a plain script and failureText reads window.paramantErrors at call time: put parashare.page.js first and the page quietly falls back to its own copy of the sentence. Verified by sabotage. The vm suite now loads the real frontend/js/error-message.js into the context, so what it measures is the shipped sentence and not the fallback.
Apolloccrypt
pushed a commit
that referenced
this pull request
Sep 3, 2026
Follow-up to the security review, on top of #396. The failure sites this branch added now go through failureText(), the helper #396 put at the top of this file, so /parashare has one voice for what we did not plan for instead of three. The account-key failure reports through it and keeps its own banner sentence, because a key that will not load is a case we DID plan for and "sign in again" is a better next step than "try again in a minute". Same for the relay-sector sentence; what used to be a second wording invented at the call site is now the shared one. parashare.page.js goes to ?v=5. #396 shipped a v4 of that file and the commit below this one shipped a different v4. Both sides agree on the number, so check-cache-bust cannot see it: one immutable url, two contents, and whichever a browser cached first is the one it keeps. The order of the two script tags is pinned in the suite as well, because error-message.js is a plain script and failureText reads window.paramantErrors at call time: put parashare.page.js first and the page quietly falls back to its own copy of the sentence. Verified by sabotage. The vm suite now loads the real frontend/js/error-message.js into the context, so what it measures is the shipped sentence and not the fallback.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four findings from a buyer review of the signed-in journey (screenshots in the review set), all on surfaces that only exist after someone has paid.
1. Support was unreachable on a signed-in phone
The signed-in navigation is Documents / Send / Sign / Verify / Settings, and
js/nav-auth.jsdeleted the strip under the mobile drawer once the session check came back, on the reasoning that the account menu carries Help from then on. On 390px that leaves nothing:nav.csshides the.nav-helptext link below 700pxtests/navigation-shellto exactly the five workspace linksA customer with a stuck signature had to type the url.
The strip stays now and carries Help alone. Sign in is dropped from it, because that is not an action you still need. The text link is back in the bar from 700px up, in the same place it sits signed out, with the 44px target
nav.cssalready gives it.tests/navigation-shell.test.mjscounts this in taps, not in "a link exists": the drawer is open (tap one), anything inside the closed account menu is explicitly excluded because it would cost a third, and the test then clicks the link and follows it to/help. A 44px target on a dead link is worse than no target.The four public nav pins and the signed-out phone bar are untouched.
2. The signed-in homepage was still the sales page
It swapped the hero and left everything under it: why half of it is free, the two products, the price table, the founder letter. Three actions and then eight screens of pitch, on a phone, for someone who had already bought.
js/home-auth.jsnow stampsdata-session="in"on<html>;index.htmlhides every section after the hero off that one attribute, in CSS. No inline script (CSPscript-src 'self'), and nothing walks a list of section ids, so a section added tomorrow is covered the day it lands.The hero keeps its three actions and the founder line (
ui-truthfulnesspins the wording). Signed out the page is what it was, and the suite checks that too: same section count, no attribute on<html>.3. The signing error the customer could not act on
enrolEphemeralSigningKeyWithTotptranslated three TOTP failures into their own words and rethrew everything else untouched. What reached the screen was the wire's own message:_postJSONbuilds one out of the relay's error field or'http_' + status, and a dropped connection arrives as aTypeErrorwith whatever the browser calls it.http_502is not an instruction.frontend/js/error-message.jsowns one sentence for the unplanned case, with a next step and an address that reaches a human, and the technical detail goes toconsole.errorinstead of the screen.sign-flow.jsandco-sign.jsrender it rather than falling through to their passkey guess.js/parashare.page.jsprinted'Error: ' + e.messagein three places and now prints the same sentence from the same file (it is a classic script, so it reads the namespace off the global; the file is the same UMD shape asjs/parasign-pdf-ops.js, which is what lets node require it).tests/error-message.test.mjsruns a real thrownTypeErrorthrough the translator. Four sabotages verified red before commit:userFacingMessagereturnserror.messagethrow eback in the signere.messageagain4. Two prices for one account
/dashboardanswered "what does it cost?" with "Paid plans start at 15 euro a month, which is ParaSend Pro";/helpanswered the same question with "ParaSign Pro at 49 euro a month". One customer, two of our own pages, two numbers, and the cheaper one on the surface that exists for signing.One answer now names both products with both prices.
relay/test/pricing-page.test.jspins it the way the pricing cards are pinned: each amount is read back out ofrelay/lib/billing-catalog.js(excl. btw of the monthly variant checkout actually charges), the plan name is pinned torelay/lib/tiers.js, and every amount in the sentence has to stand on/pricingwith a real boundary, so a bare 15 cannot be satisfied by a 150 elsewhere on the page. Sabotages verified red: dropping the signing half, quoting an amount not on/pricing, and moving the catalog price.Cache-busts
nav-auth.jsv6 -> v7 andhome-auth.jsv1 -> v2, across every stamped page andapply-nav.py, because the files changed and nginx serves them immutable.Gates run locally
first-screen, ui-truthfulness, site-claims, seo-contract, links, navigation-shell (51 checks), frontend-loading-contract, frontend-module-scripts, pricing-fold, cache-bust, csp-inline, static-sanity, check-test-declarations, eslint, app-contrast, app-theme, apply-nav-idempotent (
apply-nav.pyupdates 0 files), user-dashboard-documents, code-manifest,apply_seo_head --check(0 pages), sign-full (33/33), relay pricing-page (48 checks), the full browser set (41/41) and the root non-browser set.One pre-existing environment failure, unrelated to this branch:
tests/heartbeat-lib.test.mjscannot resolve@noble/post-quantumin this checkout's sharednode_modules. CI installs it withnpm ci.Follow-up after review: the hero art was being cut in half
The first commit shrank the signed-in hero to 472px at 1440 with
padding-bottom:48px..hp-docinside.hp-artdid not shrink with it: that element is absolutely positioned and draws about 120px below the 380px box.hp-artreserves for it. Signed out that overhang lands on the section underneath and is never seen. Signed in the hero is the last thing in<main>,maincarriesoverflow:hidden, and the document, the receipt and the signature were sliced through wheremainends.Making the hero taller does not fix it, and that is worth stating: with the padding rule removed entirely and the art left in, the overhang is still 74px past the clip. The height was never the cause.
So the art retires with the pitch it illustrates. It is a document that gets signed, sealed and then burns on a 16-second loop, and it is there to explain the product to someone who does not have it yet. A phone has hidden it since it shipped, for its own reason; signed in, every width does now. With it gone the second grid column is empty, so the hero is one column, keeping the 1120px wrap that lines it up with the nav and the footer.
The check measures against the edge that actually cuts: every box the art draws, at 1440, against
main's own rectangle, which is the clip. One exclusion, named and argued in the test:.hp-art-glowis a blurred radial gradient withinset:-40% -20%, built to bleed, with no edge that can read as cut, and it has hung 13px over the top ofmainsince the art shipped. A signed-out control runs the same measurement and requires the art to be present and whole, so the signed-in pass cannot be a pass over an empty list.hp-doc 531..1030 outside main 56..956navigation-shellis 53 checks. Rebased onto currentmain(#394).Known merge overlap
frontend/dashboard.html. Keep both: this branch's price answer, Six things the signed-in journey got wrong on a phone #395's tap-target CSS and itsdashboard.js?v=9bump. They are complementary, and Six things the signed-in journey got wrong on a phone #395's.dh-ask-list a{min-height:var(--tap)}is about the "See the plans" link in exactly this answer.frontend/parashare.html, on one line. Keep both: this branch'serror-message.js?v=1tag andparashare.page.js?v=4.frontend/js/parashare.page.jsandfrontend/js/nav-auth.jsauto-merge clean.parashare.page.jstov=4for different edits. Whoever rebases second must take it tov=5, or the file ships twice under one immutable url.scripts/check-cache-bust.shonly checks that a version is consistent, never that it moved, so nothing turns red on this by itself.