Three layers: a short note at the top, the key lines with our take in the middle, the full source at the bottom.
Source
demo/page.tsx
The /demo route. Renders the guided tour over hand-authored fixtures — no upload surface, no dropzone, no way for a cold visitor to send a real invoice.
Repo path apps/web/app/(marketing)/demo/page.tsxLanguage TypeScript
What this is
The source code for the /demo route. It is the page a visitor lands on when they click any 'see the demo' link. It renders a guided tour over hand-authored sample data and has no upload form, no file input, and no way to send a real invoice.
What it proves
Backs the promise that the /demo page never asks for your invoice. The shape of the file is the proof — there is no upload surface here to take one. Read the promise →
What to look for in the source below
- The page imports a tour component, not a dropzone.
- No file-input element, no drag-and-drop handlers, no storage writes.
- Sample data is loaded from a fixtures file that lives in the repo, not from any visitor's device.
Show the full file (126 lines)
125 lines
import type { Metadata } from "next";
import { cookies, headers } from "next/headers";
import { MarketingLayout } from "../_components/MarketingLayout";
import { canonicalMetadata } from "@/lib/canonical";
import { getCopy, resolveLocale, LOCALE_COOKIE } from "@/lib/i18n";
import { breadcrumbList, howTo } from "@/lib/seo-schema";
import DemoTour from "./DemoTour";
import { isPreLaunch } from "@/lib/launch";
export const metadata: Metadata = {
title: "Try it",
description:
"Walk through a vendor invoice from scan to filed in five steps. No upload needed; the tour uses hand-authored fixtures so you can see how Muntin Ledger reads, catches, and files an invoice before signing in.",
...canonicalMetadata({ path: "/demo", hasES: true }),
};
/**
* /demo — the guided product tour.
*
* Previously this page carried a URL-state mode toggle (?mode=
* tour|live) that mounted either the pre-canned tour OR a live
* dropzone that round-tripped a real upload through the engine.
* The live mode was removed on 2026-05-25 — every cold visitor
* could anonymously consume engine budget by dragging anything
* in. That was a real abuse vector and the "no upload needed"
* primary CTA implied we had already closed it.
*
* Now the page is just the guided tour. The hand-authored
* fixtures in tour-data.ts walk the visitor from read to filed
* with zero bytes moved on either side. When a visitor wants to
* sign in and try the engine on a real invoice, the sign-in path
* lands them in /inbox where the upload flow is rate-limited per
* authenticated account.
*/
export default async function DemoPage() {
const [jar, hdrs] = await Promise.all([cookies(), headers()]);
const locale = resolveLocale({
cookieValue: jar.get(LOCALE_COOKIE)?.value,
acceptLanguage: hdrs.get("accept-language"),
});
const c = getCopy(locale);
const t = c.landing.tour;
const g = c.landing.glossary.confidenceRing;
const u = c.ui.confidenceRing;
return (
<MarketingLayout width="wide">
<script
type="application/ld+json"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: JSON.stringify(
breadcrumbList([
{ name: "Home", path: "/" },
{ name: "Try it", path: "/demo" },
]),
),
}}
/>
<script
type="application/ld+json"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: JSON.stringify(
howTo({
name: "How to file a vendor invoice with Muntin Ledger",
description:
"Walk a vendor invoice from a scan or photo to a filed ledger row in five steps, then post the bill to QuickBooks Online or Xero.",
totalTime: "PT2M",
steps: [
{
name: "Pick or snap a vendor invoice",
text: "Drag a PDF into the drop zone or snap a photo of a paper receipt with the iPhone app.",
},
{
name: "Watch us read it",
text: "We extract vendor, totals, line items, and posting hints with templates and rules. Confidence rings show how sure each field is.",
},
{
name: "Review the catch",
text: "We highlight any anomaly: a vendor total that does not match the line-item sum, a unit price that jumped, a missing tax field.",
},
{
name: "File it to your ledger",
text: "Confirm and the row lands in your searchable ledger with the original PDF attached and the audit chain updated.",
},
{
name: "Post the bill when ready",
text: "One-click post to QuickBooks Online or Xero. Dry-run by default so you can review before anything writes to your books.",
},
],
}),
),
}}
/>
<div className="py-[var(--mun-section-pad-y)]" data-tier="expressive">
<p className="font-body text-eyebrow uppercase tracking-[0.06em] text-rust-text">
{t.eyebrow}
</p>
<h1 className="mt-3 max-w-[28ch] font-display text-h1 leading-[1.05] tracking-[-0.01em]">
{t.headline}
</h1>
<p className="mt-5 max-w-[62ch] font-body text-body leading-[1.6] text-ink-soft">
{t.intro}
</p>
<div className="mt-10 max-w-[var(--mun-measure-prose)]">
<DemoTour
copy={t}
locale={locale}
preLaunch={isPreLaunch()}
ringExplain={{
openLabel: u.openLabel,
title: g.label,
high: g.high,
medium: g.medium,
low: g.low,
bands: g.bands,
}}
/>
</div>
</div>
</MarketingLayout>
);
}See also
This is the file as it lives at the moment of this build. The canonical history lives in git. If you want the full history or a specific commit, write to hello@muntin.digital.