# Customer portal and private documents — v0.12.2

This is an independent local demonstration using invented records. No email is sent, no address is verified, no payment is collected and nothing is published to a real domain. The original SHS application is unchanged.

## Invite the exact customer

1. In **Customers**, check the customer's full name, contact details and short record ID. Choose **Manage portal access**. Identical names or email addresses still represent separate customer records.
2. Use an invented email ending in `.example`. Choose **Capture setup invitation**, then **Open protected setup capture**. This is visible only to the owner in that business. The message supplies the exact Portal ID and a one-use code; it is not an email delivery.
3. Open the customer portal and choose **Set up / recover**. Paste the code, enter a password of at least 12 characters twice, and save. Sign in with that Portal ID and password.
4. The customer sees only their jobs/progress, explicitly shared photos and quote revisions, and issued invoice history. Drafts, internal job notes, labour costs, source reservations and staff details remain private.

Setup codes expire after 48 hours. Reissuing setup immediately ends the old password, unused codes and sessions. **Revoke portal access** ends both password and temporary-code access. Changing customer contact details also revokes access; review the corrected record before inviting again.

Open portal and document pages now recheck access after owner access/contact changes, when returning to a tab and every 30 seconds while visible. A valid check leaves unfinished response fields intact. Lost or changed access clears private content and image leases; failed validation also clears content, with a 10-second deadline for a hung check. Same-origin tabs receive immediate access-change notices. Other browsers rely on focus/visibility and periodic checks, which browsers may delay in the background. This does not erase an already downloaded PDF or recall previously viewed information.

The older local API workflow still supports temporary demo codes. New codes are single-use and expire in 30 minutes. It cannot downgrade an enabled or pending password account. Permanent codes created by older versions are disabled on the first v0.11 upgrade; owners must issue new access deliberately.

## Recover a portal password

Choose **Request recovery**, enter the exact Portal ID and the captured email address, then submit. The response is deliberately the same whether details match or not. If they match an enabled account, the owner can use **Refresh captures** to view the protected recovery message. Its code expires in 30 minutes. Paste it under **Set up / recover** and set a new password. Completion consumes all outstanding codes and ends earlier customer sessions. Requesting recovery alone does not end a working session.

Automated tests cover local account routing and recovery behaviour. The password-recovery browser journey is still pending. This does not establish email ownership or production identity assurance. Owner/staff login and customer login are separate.

## Review and respond to a quote

Open **Review and respond** on the current shared quote. Read the saved work, quantities, prices, total, terms and validity date. Optional owner-supplied acknowledgement wording appears exactly as saved. You may add a typed name and response note. Check the review box, then deliberately choose **Accept this demo quote** or **Decline this demo quote**.

The response records the exact snapshot, reference, version, wording, actor, time and optional acknowledgement. A typed name is not a certified signature. No legal terms are generated. Expired, superseded, cancelled or already answered quotes reject new responses. Concurrent or stale submissions cannot silently accept different content.

A declined quote retains its work-source reservations until the owner creates a revision or cancels it. Revision transfers reservations; cancellation releases them. This prevents declined work from being billed again without an explicit owner decision. Earlier accepted documents keep their existing history; upgrading does not invent past customer acknowledgement evidence.

## Download a private PDF

Open a quote or invoice and choose **Download private PDF**. The server checks the active business, role, exact document/customer and lifecycle for every download. Owner drafts are labelled DRAFT. Customers can download only previously shared quote revisions and issued invoice history. No predictable public PDF file or external storage link is created.

The saved section uses the original customer, business identity, logo, lines, exact pence and wording. Rebranding and changing settings do not rewrite it. A separately labelled **Current status summary** shows present status, payment records, stage balances and the time of that summary. A historical download can therefore show a changed current balance without altering the original invoice amount.

For a deposit or stage, use **Request locally** under Quotes & invoices before downloading its request. The first request seals the invoice and stage details and the amount remaining at that time. A request is part of one invoice; it is not a second invoice, receipt or new revenue. Later payments change only the current summary. An older request without a saved request snapshot needs an explicit owner **Request locally** action to freeze its retained details. It is labelled as an owner-reviewed legacy request with the new recording time; no original missing evidence is fabricated.

The PDF renderer uses escaped plain text and owned historical PNGs, not arbitrary HTML or URLs. Bundled licensed DejaVu fonts support the tested Welsh and European characters. Unsupported font/control characters cause an explicit export error. Four invented examples were independently extracted and all 11 rendered pages inspected: a one-page fractional/Unicode quote, a six-page invoice, a two-page deposit request and a two-page historical invoice. Browser print, phone download and screen-reader behaviour are still unverified.

## Configure new document defaults

Under **Document settings**, choose distinct quote/invoice prefixes, optional Europe/London creation year and minimum sequence digits. Counters increase across years and prefix changes; they never reset or reuse a saved reference. These settings apply to newly created documents. Existing records and revisions retain their original identity and wording.

Payment instructions and acknowledgement wording start blank. Enter only invented details in this prototype. Registration identifiers, footer and general payment terms remain in the existing Website/document defaults. There are no real bank details, mandatory legal phrases or compliance claims supplied by the app.

## Recovery and the remaining checks

Keep the matching v0.10 code archive and format-7 backup before upgrading. v0.11 writes format 8 and validates pinned formats 1–7. Password hashes, setup/reset tokens, captures, sessions and usable portal grants are excluded. Restoring preserves account identities and access history but revokes every customer account; the recovered owner must invite again. Customer passwords do not appear in RESTORED-LOGIN.txt. The four-business drill preserves eight exact customer accounts, four quote responses, four sealed requests and field/photo/recurrence/document history, then verifies a second backup.

The earlier booking/Materials dialogs and PDF permission wait are resolved. The bounded browser journey verified exact customer setup/sign-in, reissued setup after revocation, quote share/accept/decline, stale revision rejection, a permitted customer PDF, a matching-contact other-customer denial and automatic clearing after revocation. Valid revalidation preserved an unfinished response. These are local fictional-data checks only. Pending: recovery request/capture/password completion, active-customer contact-change revocation, expiry, wider staff/customer switching, shared photos, invoice/stage downloads, keyboard/focus/accessibility, phone layouts, browser print, installed PWA/offline and fresh install/recovery on another machine. Read the [portal checkpoint](PORTAL-SESSION-CHECKPOINT.md) and full parity register before treating this as a finished product.
