Local design preview. No checkout or form delivery. No production analytics. Highlighted terms are examples for review.Review notes and open decisions

Private review notes

What is settled, what is not, and where every claim came from.

This page is for Matt and the reviewer. It is not part of the customer site and would not ship.

1 · Containment

What this preview cannot do.

No live checkout

src/components/preview/demo/buy-panel.tsx

No preview component dispatches the site's openCheckout event. The buy panel opens a local order summary instead. The Shopify annual-bundle flow on the live site is untouched.

No form delivery

src/components/preview/demo/demo-form.tsx

The business and trainer enquiry forms have no submit handler beyond local state, and say so on submit rather than showing a false confirmation.

No production analytics

src/components/site-instrumentation.tsx

The Meta Pixel, its noscript pixel and the Google Analytics and Ads tags were lifted out of the root layout into a component that renders nothing under /preview. On a direct load they are not rendered and not serialised, so there is no script to run and no pixel to request. Measured across all fourteen routes scrolled to the bottom: zero requests leave localhost. The live site still serves all of it, unchanged. Review round 3 found the mirror-image bug: a first pass had replaced next/script with raw script tags, which React does not execute when it inserts them, so a client navigation OUT of /preview left the live tags inert until a reload. The fix was to put next/script back, byte for byte, and let its own loader handle the route change. Review round 4 asked the opposite question and found the remaining gap: on a client navigation INTO /preview from the live site, fbq and gtag are already defined and Meta autoConfig can still report from inside the prototype. Not rendering cannot take that back, so the component now stays mounted and uses each vendor's documented off switch on the way in, fbq consent revoke and the ga-disable-ID flag, restoring them on the way out only if it set them. Review round 5 found two holes in that restoration. It wrote ga-disable-ID back to false unconditionally, so a visitor who had already opted out of Google tracking was silently opted back in by looking at a prototype; the flags are now recorded on the way in and put back to the value they actually held, including unset. It also granted Meta consent back unconditionally; consent is now granted only when this component was the one that revoked it and no IAB TCF consent manager is on the page, and where one is the flag is left revoked for the manager to reassert. The same round raised the timing of the switch itself, which runs in a layout effect after the router has already changed history. Two route facts bound it: nothing on the live site links to /preview, so entering is always a document load with no client navigation to observe, and every link back out is now a plain anchor, so leaving is a document load too. Both switches are no-ops on the live site, where the component never enters that branch.

No Clarity session recording

src/components/providers/clarity-provider.tsx

Clarity.init has no teardown, so a mount-only guard could not stop a recording that had already started on the live site: unmounting the provider left the session running into /preview. Review round 3 called this a P0. The provider now stays mounted, watches the pathname, and issues the global stop command on entering /preview, restarting only if it was the one that stopped it. It runs in a layout effect, so the stop lands before the first /preview paint rather than one frame after it, which is what review round 4 asked for. Proved in a headless browser with every vendor domain failed at the network layer: entering /preview by client navigation records the command stop, and a direct load never defines clarity at all.

No referral capture, no support form

src/components/off-preview-only.tsx

The referral capture provider sits behind the pathname guard. The preview footer used to link to /support, whose form posts to the live /api/send-support-email and re-enables normal tracking; that link is gone.

Nothing sent without JavaScript

src/app/preview/layout.tsx

Without hydration the demo form fell back to a native GET, putting a name, email and organisation into the address bar under a panel that says nothing was sent. The form is now hidden when scripts are off and replaced by a noscript panel. The same pass opened the FAQ answers, which were hidden behind a toggle that could not be clicked, so pricing, privacy and policy disclosures were unreachable. Measured with script execution disabled: six answers 74 to 126 pixels tall, the inert plus affordance hidden, the form at zero height.

No lead beacon

src/lib/lead-tracker.ts

src/lib/lead-tracker.ts posts page views, scroll depth and CTA clicks to api.penng.ai. Nothing recorded under /preview is ever queued or sent: track() refuses the event at the door and flush() refuses it again, unconditionally, so containment does not depend on how the server was launched. Review round 4 noted that the flush also threw away events queued on the LIVE site before the visitor navigated in, which is a real visitor losing real events for looking at a prototype. It now holds that queue instead, capped at 200 so an indefinite stay cannot grow it without bound, and sends it when they navigate back out. The environment switch is the second line of defence and still clears, because a launcher that sets it has no destination at all. Both branches are inert on the live site.

Four labelled ways out

src/components/preview/chrome.tsx, start-flow.tsx

The footer's Company column links to /news, /privacy and /app, and the own-wearable flow links to /app a fourth time. All four are live pages outside the prototype. They are pages, not forms, so nothing is submitted by following one, but normal site analytics resume the moment you are there. Review round 4 asked for them to be labelled rather than removed, because a footer with no company links is not the footer being reviewed. Round 5 found the fourth one unlabelled and the ledger claiming there were three. All four now carry a diagonal arrow and a screen-reader phrase saying the link leaves the preview, and the footer column carries a sentence saying what resumes. Round 5 also made every one of them a plain anchor rather than a client-side route change, so leaving is a document load: the live page starts with fresh tags instead of inheriting flags this prototype is still lifting, and Next stops prefetching live routes from inside the preview. Every other link on all fourteen routes stays inside /preview.

Nothing sent before hydration

src/components/preview/demo/demo-form.tsx, start-flow.tsx

The noscript rules only apply when scripting is off. With scripting on but before React hydrates, the demo form was visible and natively submittable, and a native submit is a GET that writes a name, email and organisation into the address bar under a panel saying nothing was sent. Its submit button is now disabled until mount, which blocks the click and the implicit Enter submission together. The three start-flow route buttons were inert in the same window and are disabled until mount for the same reason, so the affordance matches what the page can actually do.

Not indexable

src/app/preview/layout.tsx

The preview layout sets robots index:false, follow:false, nocache:true. The running server is bound to 127.0.0.1:3087 (lsof confirms 127.0.0.1, not 0.0.0.0), started by npm run preview, which carries -H 127.0.0.1 so the binding does not depend on how the command was typed. Plain npm run dev still binds every interface and is the wrong command for this prototype. No tunnel, no hosting.

Nothing else touched

git status

/v2, /v3, /app, the checkout, the pricing hook, the middleware and the corporate dashboard are unmodified. Review round 4 was right that the earlier count of two was wrong. The full list outside the preview tree is four tracked files modified: package.json, which gains an npm run preview script; src/app/layout.tsx, which now calls three extracted components instead of inlining their markup; clarity-provider.tsx; and lead-tracker.ts. Plus three new shared components the layout calls: off-preview-only.tsx, site-instrumentation.tsx and site-structured-data.tsx. The last two carry the root layout's own markup, moved rather than rewritten. Everything else is new and lives under src/app/preview, src/components/preview, src/lib/preview and public/preview-assets. git status is the check.

2 · Open decisions

12 unsettled terms.

Each is rendered on the site with a visible marker and a tooltip, never as a plain promise. Every one comes from a single configuration file, so the site cannot contradict itself and settling a decision is a one-line change.

src/lib/preview/offer.ts
TermShown asWhy it is open
offer.currencyUSDUSD is the brief's reference currency. Country availability, tax and shipping are unresolved.
offer.trial.existsa trial existsA trial is likely but has no approved length, card requirement, start trigger or eligibility.
offer.trial.days14 days14 days was downgraded to a hypothesis, not a settled requirement. Example only.
offer.trial.startsWhenafter your first full night of data syncsRecommended so setup failures do not consume the trial. Not approved.
offer.trial.cardRequiredno cardCard collection rule is unresolved.
offer.v1.shipsIn2-4 working daysFulfilment window not verified for this rebuild.
offer.v2.dispatchWindowno dateRoadmap records Penng 2.0 hardware as blocked until 800 Penng 1.0 bands sell. No dispatch window exists yet.
offer.v2.chargeTimingnot decidedPayment timing for preorders is unresolved.
offer.v2.cancellationterms not writtenPreorder cancellation and refund terms do not exist yet.
offer.tee.priceUsd$25Price, variants, stock and fulfilment all need verification before publishing.
offer.corporate.priceUsdnot shownAmounts, seat rules, minimums and data permissions are all open.
offer.trainer.samplePriceUsd$20 / 5 seats$20/month for five seats is an illustrative proposal, not a price to publish.

3 · Evidence

Every product claim, and the file it came from.

Which ROUTE a device takes, and which features are band-only

apps/penng_app/lib/core/models/data_source_type.dart

Built from the DataSourceType capability flags. Each row on the compatibility page carries the flag name it came from: supportsRealTimeStreaming, supportsAppInitiatedWorkouts, supportsDeviceAlarms, typicallyHasSpO2, typicallyHasHrv, typicallyHasBloodPressure, and isImportMode read in the inverse for the row about data arriving without a sync. What this file does NOT establish is what any individual watch or ring writes to a health store, so every per-brand answer on the site is marked unverified.

Penng shows nothing rather than a guessed recovery score

apps/penng_app/lib/core/providers/recovery_score_provider.dart

The provider returns null when there is no measured baseline, and the code comments record that an earlier version which invented a score from a default 40 ms HRV was removed on purpose.

Employer reporting is aggregate by default, with a minimum cohort of eight

corporate-dashboard/src/components/corp-kit/primitives.tsx, src/components/workforce-score/score.ts, backend/app/models/organization.py, backend/app/api/v1/helpers/org_auth.py, backend/app/api/v1/endpoints/organization_dashboard.py

The dashboard renders the line 'Aggregated · minimum cohort 8 by default · individual data never surfaced', and the scoring code applies privacy_restricted suppression below the floor, distinguishing suppressed from a measured zero. Review round 3 caught the site overstating this as an absolute. The backend does support individual member reads: OrganizationDataMode defaults to AGGREGATE, and individual_reads_enabled returns false in that mode regardless of any stale per-member flag, but an organisation set to INDIVIDUAL can serve one member's stats to an ADMIN, or to a MANAGER for a direct report, and only when that member's own allow_individual_view is set. The site now says aggregate by default, individual only where the organisation is set up for it and the member has switched it on.

How the recovery score is composed

packages/health_data_core/lib/src/calc/recovery_score_algorithm.dart

v4 sums a sleep component capped at 51 points, an HRV component capped at 6, and an RHR component capped at 51 scored by exponential decay from the user's own baseline, rescales to 0-100 and applies a penalty factor for yesterday's strain. Review round 3 caught the site describing the score as built from HRV and resting heart rate, which omits the largest component. There is also a no-HRV fallback weighting sleep 60 and RHR 40, but review round 5 caught the site describing that 40 as resting heart rate itself. It is RHR proximity to the user's own measured baseline, and when no baseline has been computed from their own overnight history the algorithm returns null rather than a score, so a new device owner without HRV is not promised a number they will not get. The copy now names sleep, HRV and resting heart rate against the user's own baseline, adjusted for the day before, and the compatibility page says the fallback needs that baseline first.

Band imagery provenance

penng-vault/12-Brand/3D/penng-band/V6-Hero.png, V6-Fabric.png, V6-NOTES.md

V6 is a historical render iteration name, not the Penng 2.0 product name. The current hardware chapter and band pages use the approved transparent Penng 1.0 and Penng 2.0 product-view sets.

Communities imagery is redacted, not staged

public/product-deck/assets/shots/challenges-leaderboard.webp, challenges-detail.webp

Both captures carry real member names and profile photographs. Rather than ship them or fabricate a leaderboard, a gaussian blur is baked over the 700x866 avatar and name column in the derived assets, and the caption says the names are blurred. The ranking, the metric and the layout are the real UI. A capture from a demo account would remove the need for this and is on the missing-shot list.

App screenshots and every figure quoted from them

public/product-deck/assets/shots/*.webp

Real captures of the shipping app. Numbers quoted in copy (642 cal with 33 g protein, 2682 against a 2552 goal, a 02:25 session at 220 kg load, 44 ms HRV) are read off those captures rather than invented.

Penng 1.0 hardware specification

penng-vault/10-Product/hardware/band-hardware.md

40 g, 21+ days, 1 ATM, magnetic pogo-pin charging, no screen, one-year limited warranty. The vault's 'medical-grade / clinical-grade accuracy' line was deliberately not used: there is no evidence for it and it is a regulated claim.

Penng 2.0 is described as a target, not a shipping specification

hardware/v2/SUPPLIER_BRIEF.md, hardware/v2/DESIGN_DECISIONS.md, penng-vault/10-Product/roadmap.md

Penng 2.0 is an open change request to the manufacturer, and the roadmap records it as blocked until 800 Penng 1.0 bands sell. Customer-facing pages label it Coming soon while the review ledger keeps the unresolved dispatch and commercial terms visible.

The company score is an organisation trend, not a per-employee one

corporate-dashboard/src/components/workforce-score/score.ts

Penng 1.0 has no per-member baselines. Direction is the organisation's own daily wellness score, first day against last day, over a pinned thirty-day window, mapped so fifty is flat. Below two data points there is no direction at all rather than a zero. The corporate page says exactly this.

Penng 1.0 samples; Penng 2.0 senses continuously

hardware/v2/SUPPLIER_BRIEF.md, penng-vault/10-Product/hardware/band-hardware.md

Penng 1.0 is rated 21+ days at its sampling rate. Penng 2.0 is specified for 5–7 days of continuous sensing, which is a SHORTER battery life, not a longer one. Nothing on this site calls Penng 1.0 continuous or calls Penng 2.0's battery longer.

Trainer portal capability

public/product-deck/assets/shots/trainer-*.webp

Clients, per-client profile, activity, sleep, heart and nutrition tabs, and a weekly schedule with conflict detection. Nothing beyond what those captures show is claimed.

The compatibility page marks 3 capabilities as band-only on the strength of those flags, not on marketing preference.

4 · Deliberately excluded

Things a normal marketing site would have said.

  • ×Testimonials, review counts, star ratings and user numbers. None exist that can be verified for this rebuild.
  • ×'Medical-grade' or 'clinical-grade' accuracy, from the hardware vault. Unsupported and regulated.
  • ×Any health outcome claim: absenteeism, healthcare cost, weight loss, sleep improvement.
  • ×The individual-employee dashboard view. It is badged INTERNAL · DEMO ONLY in its own capture and is not the standard employer experience, so showing it on a marketing page would misrepresent the product.
  • ×V6-Underside.png. V6-NOTES.md describes its optical geometry as inferred, an appearance reconstruction rather than a manufacturing-accurate optical design, so it is not used anywhere a customer could read it as the real sensor layout. V6-Hero.png and V6-Fabric.png are used, as form and material imagery only.
  • ×Any claim that the rendered band is dimensionally accurate. The renders show shape, weave and finish; nothing on the site quotes a measurement taken from them.
  • ×Corporate and trainer prices. Not settled, so not published anywhere on the site.

5 · Asset and decision gaps

What is missing before this could ship.

T-shirt photography
No garment shot exists in the repository. The shop shows a labelled placeholder.
Penng 2.0 imagery
The page uses product renders based on the approved R32 design model; final manufacturing details may still change.
Founder or team photography
Not present, so there is no about section pretending to one.
Verified customer evidence
No testimonials, case studies or usage statistics that can be substantiated. This is the largest remaining conversion gap after the second pass: the site carries product demonstrations as its only proof. Three assets would close it, and each needs a real person: one verified customer story, one authorised trainer perspective, and one recorded demo of the product delivering an insight.
Missing app captures
Five shots would replace composed crops with purpose-made frames. 1. Camera viewfinder over the same plate as foodai-breakdown. The only capture of the Scan Food screen is of a coffee cup, which does not match the eggs, toast and avocado the estimate describes, so it is not used. Step one of the meal sequence shows the photograph the estimate was made from instead, cropped clear of the screen chrome, and says so. That photograph exists only at 482x222 inside the saved-meal header, which is why it is soft above about 300px wide. 2. A Plans page in one frame showing a training day and its habit rows together, at 1170x2532 or better. 3. A workout summary whose calorie tile is populated; training-summary.webp reads 9 kcal for a 2h25 session, so the load crop excludes that row. 4. A Communities view captured on a demo account, which would remove the need for the redaction below. 5. A coach exchange whose recovery figure matches readiness-home.webp, so the morning chapter can quote one number throughout.
Geography, tax and shipping
Unresolved. The site quotes USD and does not promise delivery anywhere specific.
Trial and cancellation policy
Unresolved, and the single largest gap for a page that asks for money.

The visual direction, the option that was rejected and the reasoning behind the choice are on a separate page.

Design directions and recommendation