Module doc for apps/website, scripts/templates/install.html, and scripts/gen-sitemap.mts — part of the ExcelRewind architecture documentation suite.
apps/website is the public marketing site for ExcelRewind, served at excelrewind.com.
Unlike apps/addin-excel and apps/viewer it is not Vite-built — it is a
plain folder of static HTML/JS/binary files deployed as-is:
wrangler pages deploy apps/website --project-name excelrewind-website --branch master
(the deploy:website script in root package.json, one leg of npm run deploy).
Because there is no build step, none of its pages can read import.meta.env — the Supabase URL and anon
key are hard-coded directly into each script (see §7).
The module has five jobs: (1) the landing page (index.html) that pitches the product and links out to
the add-in install page and viewer; (2) a self-serve account dashboard (account.html) that signs a user
in with Supabase email-OTP and reads their profile/devices/org/referral/tier data directly via PostgREST + RPC —
bare fetch, no Supabase SDK; (3) a static privacy policy and a troubleshooting page for the most common
add-in support issue (Excel's add-in cache); (4) first-touch marketing attribution and click telemetry
(js/attribution.js) that posts anonymous events to a Supabase table; and (5) hosting the two installer
binaries themselves — ExcelRewind-Setup.exe and ExcelRewind-Installer.pkg are committed
into apps/website/ and served directly from this origin (not through any build pipeline).
A sixth, related artifact lives outside apps/website: scripts/templates/install.html is a
template (placeholders __ADDIN_ORIGIN__ / __VIEWER_ORIGIN__) that
scripts/make-manifest.mts fills in and writes to apps/addin-excel/public/install.html
at build time — so the actual install page is served from addin.excelrewind.com, not from this module's
origin, even though its template lives in the repo next to the website's other pages and its markup links back to
excelrewind.com for the installer downloads and js/attribution.js. scripts/gen-sitemap.mts
is a small codegen script (npm run sitemap) that regenerates apps/website/sitemap.xml from a
hard-coded route list — indexable marketing routes only; app subdomains and /account are deliberately excluded.
Cloudflare Pages headers (apps/website/_headers) apply site-wide: HTML pages are no-cache
(always revalidate — a prior incident served a stale account.html from browser cache after a deploy),
/account.html additionally gets no-store, and a CSP restricts connect-src to
'self' plus the Supabase project origin.
flowchart TB
subgraph PAGES["apps/website (static, deployed as-is to Cloudflare Pages)"]
IDX["index.html
(landing page)"]
ACC["account.html
(self-serve dashboard, bare fetch)"]
PRIV["privacy.html
(static policy)"]
TS["troubleshoot/index.html
(add-in cache repair guide)"]
COMP["companion/index.html
(redirect stub → install.html)"]
NF["404.html
(hard-404, disables SPA fallback)"]
HDR["_headers
(Cloudflare Pages headers: CSP, cache)"]
ROBOTS["robots.txt"]
SMAP["sitemap.xml
(generated)"]
EXE["ExcelRewind-Setup.exe
(committed binary)"]
PKG["ExcelRewind-Installer.pkg
(committed binary)"]
OG["og.png
(shared social-card image)"]
subgraph JS["js/"]
ATTR["attribution.js
(first-touch + telemetry)"]
PRICE["pricing.js
(progressive-enhancement pricing)"]
end
end
subgraph GEN["Codegen (build time, outside apps/website)"]
TPL["scripts/templates/install.html
(placeholder template)"]
MKMAN["scripts/make-manifest.mts
(npm run manifest)"]
GENSMAP["scripts/gen-sitemap.mts
(npm run sitemap)"]
end
subgraph ADDINPUB["apps/addin-excel/public/ (deployed to addin.excelrewind.com)"]
INSTALLOUT["install.html
(filled template)"]
MANIFEST["manifest.prod.xml"]
end
IDX -->|"cross-links"| INSTALLOUT
IDX -->|"cross-links"| VIEWERAPP["viewer.excelrewind.com"]
IDX -->|"footer link"| ACC
IDX -->|"footer link"| TS
ACC -->|"cross-links"| TS
TS -->|"download link"| EXE
TS -->|"'Excel on the web' link"| INSTALLOUT
INSTALLOUT -->|"download links"| EXE
INSTALLOUT -->|"download link"| PKG
INSTALLOUT -->|"script src (cross-origin)"| ATTR
IDX --> ATTR
ACC --> ATTR
PRIV --> ATTR
TS --> ATTR
NF --> ATTR
IDX --> PRICE
ACC --> HDR
PRIV --> HDR
COMP -->|"meta-refresh + location.replace()"| INSTALLOUT
TPL --> MKMAN --> INSTALLOUT
MKMAN --> MANIFEST
GENSMAP --> SMAP
classDef file fill:#f7f8fa,stroke:#c9cede,color:#1a2333;
classDef script fill:#f2edfc,stroke:#7C3AED,color:#3d1a7a;
classDef ext fill:#eef7f0,stroke:#3f9e5c,color:#1a4d2b;
class IDX,ACC,PRIV,TS,COMP,NF,HDR,ROBOTS,SMAP,EXE,PKG,OG,TPL,INSTALLOUT,MANIFEST file;
class ATTR,PRICE,MKMAN,GENSMAP script;
class VIEWERAPP ext;
Two independent data flows live in this module: outbound telemetry (nobody reads it back on the page) and inbound account data (read/write through PostgREST + RPC under a user JWT).
flowchart LR V["Visitor's browser"] -->|"URL query: utm_source, utm_medium, utm_campaign, ref, referrer header"| ATTR["js/attribution.js"] ATTR -->|"classify() → source enum
(share/directory/paid/ai/organic/referral/direct)"| LS1["localStorage: rw_attr
{source, utm_campaign} (first-touch, once)"] ATTR -->|"validated ?ref (regex A-Za-z0-9_-, 1-32 chars)"| LS2["localStorage: rw_ref"] ATTR -->|"crypto.randomUUID() fallback Math.random"| LS3["localStorage: rewind.viewer-id
(shared key with viewer app)"] ATTR -->|"HTTPS POST JSON: {viewer_id, event, props, source?, utm_campaign?}
keepalive:true, Prefer: return=minimal"| SB["Supabase REST
/rest/v1/viewer_events"] ATTR -.->|"navigator.doNotTrack / globalPrivacyControl"| OPT["Opt-out check
(no capture, no storage, no send)"] PRICE["js/pricing.js"] -->|"HTTPS POST /rest/v1/rpc/public_tiers"| SB2["Supabase RPC
public_tiers()"] SB2 -->|"JSON: [{key, name, monthly_price, sort_order}]"| PRICE PRICE -->|"innerHTML (esc()'d) replaces #pricing-tiers"| DOM1["index.html DOM"]
flowchart LR
U["User"] -->|"email"| ACC["account.html"]
ACC -->|"POST /auth/v1/otp {email, create_user:true}"| AUTH["Supabase GoTrue"]
AUTH -->|"6-digit code (email, out of band)"| U
U -->|"code"| ACC
ACC -->|"POST /auth/v1/verify {type:email, email, token}"| AUTH
AUTH -->|"JSON: {access_token, ...}"| ACC
ACC -->|"localStorage: rewind.account-token"| LS["Browser localStorage"]
ACC -->|"GET /rest/v1/profiles?select=email,tier,status,created_at
Authorization: Bearer <token>"| PG["Supabase PostgREST"]
ACC -->|"GET /rest/v1/devices?select=label,last_seen&order=last_seen.desc"| PG
ACC -->|"POST /rest/v1/rpc/get_my_org {}"| PG
ACC -->|"POST /rest/v1/rpc/my_referral, public_promo_config, my_referrals"| PG
ACC -->|"POST /rest/v1/rpc/public_tiers {}"| PG
ACC -->|"POST /rest/v1/rpc/redeem_coupon {p_code}"| PG
PG -->|"JSON rows (RLS: caller sees only own profile/devices/org)"| ACC
ACC -->|"esc() then innerHTML"| DOM2["account.html DOM
(dl/ul fields, referral list, tier cards)"]
sequenceDiagram
participant Browser
participant AttrJS as "attribution.js"
participant LocalStorage
participant Supabase as "Supabase REST (viewer_events)"
Browser->>AttrJS: page load (index.html / account.html / privacy.html / troubleshoot / 404 / install.html)
AttrJS->>AttrJS: optedOut() check (DNT / GPC)
alt opted out
AttrJS-->>Browser: return immediately, no storage, no network
else not opted out
AttrJS->>LocalStorage: read rw_attr
alt first visit (no rw_attr)
AttrJS->>AttrJS: classify() from utm_*/referrer host
AttrJS->>LocalStorage: write rw_attr = {source, utm_campaign}
end
AttrJS->>LocalStorage: read/write rw_ref (validated ?ref, first wins)
AttrJS->>LocalStorage: read/write rewind.viewer-id (random UUID)
AttrJS->>Supabase: POST /rest/v1/viewer_events {viewer_id, event:"page_view", source, utm_campaign, props:{path}}
Note over Supabase: fire-and-forget — .catch(()=>{}) swallows failure
Browser->>AttrJS: click on an anchor (delegated, capture phase)
alt href contains "install.html"
AttrJS->>Supabase: POST viewer_events {event:"cta_install_click"}
else href matches .exe/.msi/.pkg/.dmg
AttrJS->>Supabase: POST viewer_events {event:"download_started", props:{platform}}
end
end
sequenceDiagram
participant User
participant AccountHtml as "account.html"
participant GoTrue as "Supabase Auth (/auth/v1)"
participant PostgREST as "Supabase PostgREST (/rest/v1)"
User->>AccountHtml: enter email, click "Send code"
AccountHtml->>GoTrue: POST /auth/v1/otp {email, create_user:true}
alt request fails
GoTrue-->>AccountHtml: non-2xx
AccountHtml-->>User: "Could not send the code. Check the email and try again."
else success
GoTrue-->>AccountHtml: 2xx (email sent out of band)
AccountHtml-->>User: shows code field, "Code sent - check your email."
User->>AccountHtml: enter 6-digit code, click "Verify"
AccountHtml->>GoTrue: POST /auth/v1/verify {type:"email", email, token:code}
alt code invalid
GoTrue-->>AccountHtml: non-2xx / no access_token
AccountHtml-->>User: "That code did not match. Try again."
else code valid
GoTrue-->>AccountHtml: {access_token, ...}
AccountHtml->>AccountHtml: localStorage.setItem(rewind.account-token, access_token)
AccountHtml->>PostgREST: GET /rest/v1/profiles?select=... (Bearer token)
alt 401 (expired/invalid token)
PostgREST-->>AccountHtml: 401
AccountHtml->>AccountHtml: clear token, "Your session expired - sign in again."
else 2xx
PostgREST-->>AccountHtml: [{email, tier, status, created_at}]
AccountHtml->>PostgREST: GET /rest/v1/devices?select=label,last_seen
AccountHtml->>PostgREST: POST /rest/v1/rpc/get_my_org {}
Note over AccountHtml,PostgREST: get_my_org failure is swallowed (try/catch) — org card just stays hidden
AccountHtml->>PostgREST: POST /rest/v1/rpc/my_referral, public_promo_config
AccountHtml->>PostgREST: POST /rest/v1/rpc/public_tiers {}
PostgREST-->>AccountHtml: JSON rows
AccountHtml-->>User: renders Account, Organization, Active devices, Refer & earn, Plans cards
end
end
end
sequenceDiagram
participant User
participant AccountHtml as "account.html"
participant PostgREST as "Supabase PostgREST"
User->>AccountHtml: enter code, click "Redeem"
AccountHtml->>PostgREST: POST /rest/v1/rpc/redeem_coupon {p_code: code}
alt success
PostgREST-->>AccountHtml: {tier, value}
AccountHtml-->>User: "Code applied - N month(s) of <Tier>."
AccountHtml->>AccountHtml: showAccount() re-fetch (refresh tier + tier cards)
else known error code (invalid_coupon / expired / used_up / already_redeemed / not_authenticated)
PostgREST-->>AccountHtml: error message containing one of the known codes
AccountHtml-->>User: friendly mapped message (REDEEM_ERR table)
else unknown error
PostgREST-->>AccountHtml: other error
AccountHtml-->>User: "Could not redeem that code."
end
sequenceDiagram participant User participant InstallPage as "install.html (addin.excelrewind.com)" participant Website as "excelrewind.com" participant Excel User->>InstallPage: visit (from index.html "Install the add-in" link) InstallPage->>InstallPage: OS auto-detect (navigator.platform/userAgent) → scroll to Windows or Mac section User->>InstallPage: click "Download for Windows" InstallPage->>Website: GET /ExcelRewind-Setup.exe (download) Note over Website: attribution.js (loaded cross-origin from excelrewind.com)
fires download_started {platform:"windows"} on the click User->>User: double-click ExcelRewind-Setup.exe alt SmartScreen warning (unsigned binary) User->>User: "More info" → "Run anyway" (documented with SVG callouts on both install.html and troubleshoot/) end User->>Excel: close + reopen Excel Excel-->>User: ExcelRewind on Home tab (installer registered the shared-folder catalog) opt add-in cache broken (post-update) User->>Website: visit /troubleshoot/ alt easiest path User->>Website: GET /ExcelRewind-Setup.exe again User->>User: re-run installer — resets %LOCALAPPDATA%\...\Wef cache, re-registers else manual path User->>User: close Excel, delete %LOCALAPPDATA%\Microsoft\Office\16.0\Wef contents User->>Excel: Insert → My Add-ins → Shared Folder → ExcelRewind → Add end end
flowchart TB WEB["apps/website
(excelrewind.com)"] SB["Supabase project
tohvepsjfeecjycspocg.supabase.co"] ADDIN["addin-excel
(addin.excelrewind.com)"] VIEWER["viewer
(viewer.excelrewind.com)"] CF["Cloudflare Pages"] STRIPE["Stripe
(billing, referenced not wired here)"] WEB -->|"HTTPS: auth OTP, PostgREST, RPC"| SB WEB -->|"HTTPS: anonymous event insert"| SB ADDIN -->|"install.html script src (cross-origin)"| WEB ADDIN -->|"install.html download links"| WEB WEB -->|"cross-links (Install CTA)"| ADDIN WEB -->|"cross-links (Open the viewer CTA)"| VIEWER CF -->|"static hosting, wrangler pages deploy"| WEB WEB -.->|"pricing copy mentions Stripe/Razorpay Payment Links (not implemented in this module)"| STRIPE
| Target | From | Protocol / endpoint | Purpose |
|---|---|---|---|
| Supabase Auth (GoTrue) | account.html | POST /auth/v1/otp, POST /auth/v1/verify | Email OTP sign-in; issues an access token stored in localStorage['rewind.account-token'] |
| Supabase PostgREST | account.html | GET /rest/v1/profiles?select=email,tier,status,created_at | RLS read of the caller's own profile row (401 handled → session-expired UX) |
| Supabase PostgREST | account.html | GET /rest/v1/devices?select=label,last_seen&order=last_seen.desc | RLS read of the caller's active devices (2-device limit is enforced server-side) |
| Supabase RPC | account.html | POST /rest/v1/rpc/get_my_org | Org name + seat usage; empty array/failure hides the Organization card |
| Supabase RPC | account.html | POST /rest/v1/rpc/my_referral, public_promo_config, my_referrals | Referral code, program config (gates the Refer & earn card), and the referee list (lazy-loaded on expand) |
| Supabase RPC | account.html, js/pricing.js | POST /rest/v1/rpc/public_tiers | Live tier list (key, name, monthly_price, sort_order, has_members, seat_limit) — single source of truth is public.tiers; js/pricing.js fails silently to the static cards already in index.html, but account.html's renderTiers() has no static fallback — on failure it just clears #tiers to blank |
| Supabase RPC | account.html | POST /rest/v1/rpc/redeem_coupon {p_code} | Applies a coupon to the caller's licence; code passed verbatim, never a tier (server validates) |
| Supabase PostgREST | js/attribution.js | POST /rest/v1/viewer_events (anon key, Prefer: return=minimal, keepalive:true) | Anonymous telemetry sink: page_view, cta_install_click, download_started; shares the rewind.viewer-id key with the viewer app's own telemetry |
| addin.excelrewind.com | apps/website/index.html, troubleshoot/index.html, account.html | Plain link (<a href>) | "Install the add-in" / "Add-in help" CTAs point at install.html on the add-in origin |
| viewer.excelrewind.com | apps/website/index.html | Plain link | "Open the viewer" CTA |
| excelrewind.com (this module) | apps/addin-excel/public/install.html (built from scripts/templates/install.html) | Cross-origin <script defer src> + download links | The add-in's install page is hosted on addin.excelrewind.com but loads https://excelrewind.com/js/attribution.js and links to https://excelrewind.com/ExcelRewind-Setup.exe / -Installer.pkg — requires excelrewind.com in the add-in's own CSP script-src |
| Cloudflare Pages | root package.json (deploy:website) | wrangler pages deploy apps/website --project-name excelrewind-website --branch master | Deploys the folder verbatim — no build step for this module |
| Stripe | privacy.html (documented, not called from this module's code) | n/a here | Payment processing is described in the privacy policy and referenced in an index.html comment ("STRIPE/RAZORPAY: replace the install-page hrefs... once Payment Links exist") but no billing call originates from this module |
| File | Function / export | What it does | Why it matters |
|---|---|---|---|
apps/website/index.html | — | Landing page: hero, "why not video" comparison, how-it-works, trust section, pricing cards (#pricing-tiers), FAQ, JSON-LD (SoftwareApplication + FAQPage) | Pricing cards and JSON-LD offers are a static fallback that must stay in sync with public.tiers by hand if js/pricing.js's live fetch fails or JS is disabled |
apps/website/account.html | api(path, opts) | Thin fetch wrapper: attaches apikey + (if signed in) Authorization: Bearer to every Supabase REST call | All auth/PostgREST/RPC calls in the page route through this one function — the single point where the bearer token is attached |
apps/website/account.html | rpc(fn, args) | POSTs to /rest/v1/rpc/<fn>, normalizes the result to {ok, data} / {ok:false, code} | code is the raw Postgres error message; redeem_coupon's error branch does a substring match against it (REDEEM_ERR) — a Postgres message-wording change silently breaks the friendly-error mapping |
apps/website/account.html | esc(v) | HTML-escapes a value before it's interpolated into innerHTML | Load-bearing XSS guard — every server-controlled string rendered on this page (device labels, referral emails, tier names, redeem messages) must pass through it; refer-link is the one exception, set via .value not innerHTML |
apps/website/account.html | showAccount() | Orchestrates the post-auth render: profile → devices → org (best-effort) → renderRefer() + renderTiers() | Explicitly sets #app's display:'block' inline — the file comments this: a stylesheet rule hides #app by default, and an inline empty string would fall back to that hidden rule (a real field bug they hit and left a comment about) |
apps/website/account.html | renderRefer() | Fetches my_referral + public_promo_config in parallel; hides the whole Refer & earn card unless referrals_enabled and a referral code exist; lazy-loads my_referrals() only when the referee list is first expanded | Feature-flags an entire UI section off a live RPC result rather than a build-time flag |
apps/website/js/attribution.js | classify() | Maps utm_source/utm_medium/referrer hostname into one of: share, directory, paid, ai, organic, referral, direct | Order-sensitive: explicit UTM/ref signals are checked before referrer-hostname heuristics; the AI/SEARCH hostname lists are the only place these are enumerated |
apps/website/js/attribution.js | captureFirstTouch() | Writes rw_attr to localStorage exactly once (first touch wins); validates and stores ?ref with a strict regex ^[A-Za-z0-9_-]{1,32}$ | First-touch semantics are enforced client-side only — a user clearing localStorage resets their attribution |
apps/website/js/attribution.js | track(event, row, props) | Fire-and-forget POST to viewer_events with keepalive:true; every call wrapped so it never throws | Explicitly never blocks page load or navigation, including on the download/install click path |
apps/website/js/attribution.js | delegated click listener | Capture-phase listener on document: fires cta_install_click for links containing install.html, download_started for links matching .exe/.msi/.pkg/.dmg | Comment notes download_started "was never wired" before this — the funnel's Download-started stage previously always read 0 |
apps/website/js/pricing.js | IIFE, card(t, i) | Re-renders #pricing-tiers from live public_tiers RPC results; index 1 (2nd tier by sort_order) is always styled "Popular" | Fails silently on any network/parse error — the static cards in index.html are the guaranteed fallback, "never blanked" per the file's own comment |
apps/website/js/pricing.js | DESCS | Hard-coded per-tier description strings keyed by tier key; unknown/new tiers fall back to "Custom plan." | The RPC returns whitelisted columns only (no description column) — copy for new tiers must be added here manually or they show a generic line |
apps/website/privacy.html | — | Static privacy policy: what's collected (account email, two-tier usage metadata, anonymous viewer telemetry, opt-in bug-report attachments), lawful bases (GDPR), retention (24-month metadata purge), sub-processors (Supabase, Stripe, Cloudflare), and region-specific sections (GDPR/India DPDPA/CCPA) | Explicitly states .rewind files and workbook content are never sent to any listed processor — the same never-upload claim the rest of the site repeats; still marked "Draft - pending legal review" in a visible on-page note |
apps/website/troubleshoot/index.html | — | Documents the #1 add-in support issue: Excel's sideload cache (%LOCALAPPDATA%\Microsoft\Office\16.0\Wef) going stale after an Office update; offers a "re-run the installer" repair path and a manual Explorer path | Explicitly frames re-running the installer as "the same one-click setup... and it doubles as the repair tool" — the installer is not just first-run, it's the support tool |
apps/website/companion/index.html | — | Dead-URL redirect: meta-refresh + location.replace() to install.html, noindex | Kept alive only for old bookmarks/SEO after the standalone Companion download was folded into the unified installer — has both a no-JS (meta refresh) and JS path |
apps/website/404.html | — | Static not-found page | Comment notes its mere presence makes Cloudflare Pages hard-404 unknown paths instead of SPA-fallback-serving index.html |
apps/website/_headers | — | Cloudflare Pages header rules: no-store for /account+/account.html, no-cache + CSP + security headers for /* | CSP connect-src must keep the Supabase origin listed or auth/telemetry/RPC calls break site-wide; script-src/style-src allow 'unsafe-inline' because every page relies on inline <script>/<style> |
scripts/gen-sitemap.mts | ROUTES (const array) | Hard-coded list of indexable marketing routes; writes apps/website/sitemap.xml | Manual step — a new indexable page does not appear in the sitemap until someone adds it here and reruns npm run sitemap |
scripts/templates/install.html | — | Template with __ADDIN_ORIGIN__ / __VIEWER_ORIGIN__ placeholders; OS auto-detect script scrolls to the Windows or Mac section; documents the Windows SmartScreen bypass with inline SVG screenshot mockups (Mac Gatekeeper bypass is text-only, no SVG) | Not deployed from this module — scripts/make-manifest.mts fills it and writes the result into apps/addin-excel/public/install.html, so it ships on the add-in's origin |
scripts/make-manifest.mts | fill(text) | replaceAll of the two placeholders using VITE_EXCELREWIND_ADDIN_ORIGIN / VITE_EXCELREWIND_VIEWER_ORIGIN from .env | Only runs for the prod path (!DEV) — dev testers get manifest.dev.xml directly and never see a generated install.html |
FAQPage, privacy policy "short version") repeats this claim — any code change here that contradicts it (e.g. adding a workbook-content upload path) breaks a load-bearing marketing/legal claim, not just a UI detail.deploy:website runs wrangler pages deploy apps/website directly on the source tree). Every script that needs the Supabase URL/anon key (account.html, js/attribution.js, js/pricing.js) has them hard-coded inline because import.meta.env is unavailable — a rotated anon key means editing three files, not one env var.account.html contains a live FOLLOWUP (supervisor) comment: the anon key is described as needing to be "filled from VITE_SUPABASE_ANON_KEY in .env at deploy" but is currently baked in directly — worth re-verifying the key in the deployed page matches the current Supabase project before treating auth as fully wired.SECURITY DEFINER read (public_tiers, public_promo_config) — an auditor should confirm RLS policies on profiles, devices, and viewer_events rather than trusting the key's presence alone.js/attribution.js respects navigator.doNotTrack and navigator.globalPrivacyControl by skipping all storage and network calls — this check runs before captureFirstTouch(), so an opted-out visitor leaves zero localStorage trace, not just zero network trace. Telemetry payloads are explicitly primitive-only (no PII, no free text) per the file's own header comment and the privacy policy's "Minimal usage metadata" table.account.html and js/pricing.js route every server-derived string through a local esc() before innerHTML — tier names and descriptions are superadmin-editable (per public.tiers), so this is a real trust boundary, not defensive boilerplate. The one deliberate exception (#refer-link set via .value) is safe because .value is not an HTML sink._headers forces no-cache/no-store site-wide specifically because a stale account.html was once served from browser cache after a deploy (see the file's own comment) — do not relax these without addressing that regression risk.sitemap.xml/robots.txt deliberately exclude /account (authed) and all app subdomains (addin., viewer., team., admin.) — gen-sitemap.mts's route list is the single source of truth for what should be indexable; a new public page silently won't be crawled until someone updates it.ExcelRewind-Setup.exe, ExcelRewind-Installer.pkg) are unsigned/unnotarized, so Windows SmartScreen and macOS Gatekeeper warnings are expected, not bugs — install.html and troubleshoot/index.html both carry near-duplicate inline-SVG screenshot walkthroughs for bypassing them (kept in sync by hand, not shared markup).make-manifest.mts's own comment states this is deliberate: hosting a .ps1 repair script on the public website would let attackers fetch and analyze it; repair logic instead ships compiled inside the installer .exe (apps/installer/troubleshoot/).addin.excelrewind.com but depends on excelrewind.com for its telemetry script and both installer downloads — changing either origin's CSP or removing ExcelRewind-Setup.exe/-Installer.pkg from apps/website/ breaks the add-in's install flow, not just this module.#pricing-tiers cards in index.html, the JSON-LD offers array in the same file, and DESCS in js/pricing.js — none of these are generated from public.tiers; only the live re-render (when it succeeds) reflects the canonical table.attribution.js's platformOf() infers OS purely from the clicked link's file extension (.exe/.msi → windows, .pkg/.dmg → mac) because it's the only signal available on static marketing pages — it does not use navigator.platform (that's only used by install.html's separate OS-detect banner script).privacy.html is explicitly marked "Draft - pending legal review... not yet a final legal instrument" in a visible on-page note — do not treat its claims as finalized without checking whether that note has been removed.