Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/surfaces-web/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Public website copy

The [website family copy contract](../../docs/specs/website-family-copy.md) governs public text and metadata alongside the [layout contract](../../docs/specs/website-family-layout.md). The homepage is a testing-stage decision and guidance workflow; a vision or example does not establish a measured benefit or general availability.

Edit `app/homepage-content.html` for the hero, workload, and workflow copy. `app/page.tsx` imports those sections and owns the footer and website schema; `app/layout.tsx` owns page and social metadata. Keep the same claim scope in each file. The existing `tests/home-page.test.tsx` checks the copy boundary, action destinations, and unchanged image/layout assets at CI/CD time. Review violations as copy corrections, without changing product behavior or granting release authority.

## Getting Started

First, run the development server:
Expand Down
5 changes: 5 additions & 0 deletions apps/surfaces-web/app/adaptive/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { Metadata } from "next";
import { publicPageTitleMetadata } from "@/lib/brand-metadata";

export const runtime = "edge";

export const metadata: Metadata = publicPageTitleMetadata("Adaptive Contract Test | Surfaces");

export default async function AdaptiveTestPage({ params }: { params: Promise<{ slug?: string[] }> }) {
const { slug } = await params;
const path = slug?.join("/") || "(root)";
Expand Down
5 changes: 5 additions & 0 deletions apps/surfaces-web/app/demos/ci/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { Metadata } from "next";
import { publicPageTitleMetadata } from "@/lib/brand-metadata";

export const runtime = "edge";

export const metadata: Metadata = publicPageTitleMetadata("CI/CD Validation Demo | Surfaces");

export default function CiDemoPage() {
return (
<main className="contract-container" style={{ padding: "2rem" }}>
Expand Down
5 changes: 5 additions & 0 deletions apps/surfaces-web/app/demos/generation/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { Metadata } from "next";
import { publicPageTitleMetadata } from "@/lib/brand-metadata";

export const runtime = "edge";

export const metadata: Metadata = publicPageTitleMetadata("Generation-Time Enforcement Demo | Surfaces");

export default function GenerationDemoPage() {
return (
<main className="contract-container" style={{ padding: "2rem" }}>
Expand Down
4 changes: 3 additions & 1 deletion apps/surfaces-web/app/design-system/[section]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export async function generateMetadata({ params }: SectionPageProps): Promise<Me
return {};
}

const title = `${page.label} - Surfaces Design System`;
const title = page.slug === "identity"
? "Identity and Design System | Surfaces"
: `Design System ${page.label} | Surfaces`;
const url = `${designSystemOrigin}${designSystemBasePath}/${page.slug}`;

return {
Expand Down
6 changes: 3 additions & 3 deletions apps/surfaces-web/app/design-system/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ import { designSystemOrigin, pages } from "./referenceContent";
import { designSystemSocialImage } from "@/lib/brand-metadata";

export const metadata: Metadata = {
title: "Surfaces Design System",
title: "Design System | Surfaces",
description:
"Simple Slab identity, public-page tokens, and proof-oriented patterns for reviewable generated UI.",
alternates: {
canonical: `${designSystemOrigin}/design-system`,
},
openGraph: {
type: "website",
title: "Surfaces Design System",
title: "Design System | Surfaces",
description: "Simple Slab identity, public-page tokens, and generated-screen review patterns.",
url: `${designSystemOrigin}/design-system`,
images: [designSystemSocialImage],
},
twitter: {
card: "summary_large_image",
title: "Surfaces Design System",
title: "Design System | Surfaces",
description: "Simple Slab identity, public-page tokens, and generated-screen review patterns.",
images: [designSystemSocialImage],
},
Expand Down
48 changes: 23 additions & 25 deletions apps/surfaces-web/app/homepage-content.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Make Every Release Decision Reusable | Surfaces</title>
<title>UI Review Decisions and Guidance | Surfaces</title>
<meta
name="description"
content="Surfaces is exploring how decisions about agent-produced UI changes can become reusable guidance, so clear repeat violations can be resolved before senior review."
content="Surfaces is testing how prior decisions can guide later UI reviews, with an owner confirming or correcting proposed guidance."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the standalone structured-data description

When homepage-content.html is consumed as the standalone document, its JSON-LD still tells crawlers that reusable guidance can resolve repeat violations before senior review, while this updated description and the visible copy now deliberately frame that workflow as an unvalidated test. Update the WebSite schema description too so the standalone entry point does not retain the stronger retired claim.

Useful? React with 👍 / 👎.

/>
<meta
name="theme-color"
Expand All @@ -22,18 +22,19 @@
<meta property="og:site_name" content="Surfaces" />
<meta
property="og:title"
content="Make every release decision reusable."
content="UI Review Decisions and Guidance | Surfaces"
/>
<meta
property="og:description"
content="Turn decisions about agent-produced UI changes into guidance that can resolve clear repeat violations before senior review."
content="Surfaces is testing how prior decisions can guide later UI reviews, with an owner confirming or correcting proposed guidance."
/>
<meta property="og:url" content="https://surfaces.systems/" />
<meta property="og:image" content="https://surfaces.systems/brand/canonical-8bfdbd59528629c2/surfaces-social.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:type" content="image/png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="UI Review Decisions and Guidance | Surfaces" />
<meta name="twitter:image" content="https://surfaces.systems/brand/canonical-8bfdbd59528629c2/surfaces-social.png" />
<meta name="application-name" content="Surfaces" />
<meta name="apple-mobile-web-app-title" content="Surfaces" />
Expand Down Expand Up @@ -91,18 +92,18 @@
>
<div class="hero-copy-shell shell">
<div class="hero-copy">
<p class="eyebrow">For teams turning UI review into reusable guidance</p>
<h1 id="hero-title">Make every release decision reusable.</h1>
<p class="eyebrow">For teams reviewing agent-produced UI changes</p>
<h1 id="hero-title">Turn UI review decisions into guidance.</h1>
<p class="hero-lead">
Resolve clear, repeated violations sooner, so senior judgment stays focused on
what’s genuinely new or contested.
We’re testing how prior decisions can guide later UI reviews, with an owner
confirming or correcting proposed guidance.
</p>
<div class="action-row">
<a
class="button button-primary"
href="#product-direction"
>
See how it works
See the workflow
<svg viewBox="0 0 24 24" aria-hidden="true" data-lucide-id="arrow-right">
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
Expand All @@ -111,9 +112,9 @@ <h1 id="hero-title">Make every release decision reusable.</h1>
<a
class="button button-secondary"
href="mailto:mike@surfaces.systems?subject=A%20recent%20interface%20release%20decision&amp;body=Hi%20Mike%2C%0A%0AI%E2%80%99d%20like%20to%20compare%20notes%20on%3A%0A%0A%5B%20%5D%20AI%20UX%20when%20interfaces%20can%20act%20or%20decide%0A%5B%20%5D%20What%20Human%20Above%20The%20Loop%20should%20look%20like%20in%20practice%0A%5B%20%5D%20Something%20else%3A"
aria-label="Share a recent release decision by email with Mike"
aria-label="Email a release decision to Mike"
>
Share a recent release decision
Email a release decision
</a>
</div>
</div>
Expand All @@ -136,8 +137,8 @@ <h1 id="hero-title">Make every release decision reusable.</h1>
data-contract-section="landing.workload"
>
<div class="shell problem-heading">
<p class="section-label">The workload to remove</p>
<h2 id="workload-title">The same violation should not require the same review twice.</h2>
<p class="section-label">Repeated review work</p>
<h2 id="workload-title">Keep prior decisions available for the next review.</h2>
</div>

<ol
Expand Down Expand Up @@ -169,8 +170,8 @@ <h2 id="workload-title">The same violation should not require the same review tw
data-contract-section="landing.guidance"
>
<div class="section-intro">
<p class="section-label">How it works</p>
<h2 id="product-direction-title">Each decision should make the next clear repeat easier to resolve.</h2>
<p class="section-label">The proposed workflow</p>
<h2 id="product-direction-title">Review guidance before trying it on a later case.</h2>
</div>

<ol class="cycle-list">
Expand Down Expand Up @@ -209,7 +210,7 @@ <h3>Decide</h3>
</svg>
</div>
<h3>Propose guidance</h3>
<p>Every decision · including no change needed</p>
<p>A proposed change or no change needed</p>
</li>
<li class="cycle-card">
<span class="cycle-index">04</span>
Expand All @@ -231,17 +232,14 @@ <h3>Confirm changes</h3>
<path d="M8 17v-3"></path>
</svg>
</div>
<h3>Reuse</h3>
<p>Accepted guidance checks the next candidate</p>
<h3>Test reuse</h3>
<p>Check accepted guidance against a later candidate</p>
</li>
</ol>
<div class="cycle-return" aria-label="Clear repeats use guidance while new cases reach the owner">
<span>Clear repeats use guidance</span>
<strong>New cases reach the owner</strong>
<div class="cycle-return" aria-label="Try accepted guidance on matching cases; owners review new or contested cases">
<span>Try guidance on matching cases</span>
<strong>Owner review for new or contested cases</strong>
</div>
<p class="loop-note">
Surfaces directs what happens next and delegates the work. Agents execute in the tools your team already uses.
</p>
<p class="loop-note">
Surfaces is an early product hypothesis. We’re testing it with recent interface release decisions.
</p>
Expand All @@ -253,7 +251,7 @@ <h3>Reuse</h3>
<a class="brand footer-brand" href="/" aria-label="Surfaces home">
<img class="brand-lockup" src="/design-system/logo/surfaces-lockup-horizontal-reverse.svg" alt="" />
</a>
<p>Release decisions that become reusable guidance.</p>
<p>UI review decisions and proposed guidance.</p>
<p>© <span data-current-year>2026</span> Surfaces</p>
</div>
</footer>
Expand Down
22 changes: 2 additions & 20 deletions apps/surfaces-web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Metadata, Viewport } from "next";
import { Inter } from "next/font/google";
import { brandAssetBase, brandIcons, brandManifest, homeSocialImage } from "@/lib/brand-metadata";
import { brandAssetBase, brandIcons, brandManifest, publicPageTitleMetadata } from "@/lib/brand-metadata";
import "./globals.css";

const inter = Inter({
Expand All @@ -14,28 +14,10 @@ export const metadata: Metadata = {
appleWebApp: { capable: true, title: "Surfaces", statusBarStyle: "black-translucent" },
icons: brandIcons,
manifest: brandManifest,
title: "Make Every Release Decision Reusable | Surfaces",
description:
"Surfaces is exploring how decisions about agent-produced UI changes can become reusable guidance, so clear repeat violations can be resolved before senior review.",
...publicPageTitleMetadata("UI Review Decisions and Guidance | Surfaces"),
alternates: {
canonical: "/",
},
openGraph: {
type: "website",
siteName: "Surfaces",
title: "Make every release decision reusable.",
description:
"Turn decisions about agent-produced UI changes into guidance that can resolve clear repeat violations before senior review.",
url: "/",
images: [homeSocialImage],
},
twitter: {
card: "summary_large_image",
title: "Make every release decision reusable.",
description:
"Turn decisions about agent-produced UI changes into guidance that can resolve clear repeat violations before senior review.",
images: [homeSocialImage],
},
};

export const viewport: Viewport = {
Expand Down
4 changes: 2 additions & 2 deletions apps/surfaces-web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const websiteSchema = {
name: "Surfaces",
url: "https://surfaces.systems/",
description:
"Surfaces is exploring how decisions about agent-produced UI changes can become reusable guidance, so clear repeat violations can be resolved before senior review.",
"Surfaces is testing how prior decisions can guide later UI reviews, with an owner confirming or correcting proposed guidance.",
};

export default function Home() {
Expand Down Expand Up @@ -91,7 +91,7 @@ export default function Home() {
>
<HomeBrand />
</Link>
<p>Release decisions that become reusable guidance.</p>
<p>UI review decisions and proposed guidance.</p>
<p>© {new Date().getFullYear()} Surfaces</p>
</div>
</footer>
Expand Down
25 changes: 25 additions & 0 deletions apps/surfaces-web/lib/brand-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,28 @@ export const homeSocialImage = {
export const designSystemSocialImage = {
...homeSocialImage,
};

// Keep inherited sharing fields when a public page overrides its title.
export function publicPageTitleMetadata(title: string): Metadata {
const description =
"Surfaces is testing how prior decisions can guide later UI reviews, with an owner confirming or correcting proposed guidance.";

return {
title,
description,
openGraph: {
type: "website",
siteName: "Surfaces",
title,
description,
url: "/",
images: [homeSocialImage],
},
twitter: {
card: "summary_large_image",
title,
description,
images: [homeSocialImage],
},
};
}
48 changes: 48 additions & 0 deletions apps/surfaces-web/tests/brand-entrypoints.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import RootLayout, { metadata as rootMetadata } from "@/app/layout";
import { metadata as referenceMetadata } from "@/app/design-system/page";
import { generateMetadata } from "@/app/design-system/[section]/page";
import { navItems } from "@/app/design-system/referenceContent";
import { metadata as ciMetadata } from "@/app/demos/ci/page";
import { metadata as generationMetadata } from "@/app/demos/generation/page";
import { metadata as adaptiveMetadata } from "@/app/adaptive/[...slug]/page";

vi.mock("next/font/google", () => ({ Inter: () => ({ variable: "font-inter" }) }));

Expand Down Expand Up @@ -234,6 +237,51 @@ describe("browser, install, and sharing brand entrypoints", () => {
expect(layout).toContain('color="#0B0D0E"');
});

it("uses the reviewed title on every public route and preserves inherited sharing fields", async () => {
const sectionTitles = {
identity: "Identity and Design System | Surfaces",
foundations: "Design System Foundations | Surfaces",
tokens: "Design System Tokens | Surfaces",
components: "Design System Components | Surfaces",
patterns: "Design System Patterns | Surfaces",
resources: "Design System Resources | Surfaces",
governance: "Design System Governance | Surfaces",
changelog: "Design System Changelog | Surfaces",
};
const reviewedPages = [
{ metadata: rootMetadata, title: "UI Review Decisions and Guidance | Surfaces" },
{ metadata: referenceMetadata, title: "Design System | Surfaces" },
{ metadata: ciMetadata, title: "CI/CD Validation Demo | Surfaces" },
{ metadata: generationMetadata, title: "Generation-Time Enforcement Demo | Surfaces" },
{ metadata: adaptiveMetadata, title: "Adaptive Contract Test | Surfaces" },
...await Promise.all(Object.entries(sectionTitles).map(async ([section, title]) => ({
metadata: await generateMetadata({ params: Promise.resolve({ section }) }), title,
}))),
];

expect(reviewedPages).toHaveLength(13);
expect(new Set(reviewedPages.map(({ title }) => title)).size).toBe(13);
for (const { metadata, title } of reviewedPages) {
expect(metadata.title).toBe(title);
expect(metadata.openGraph).toMatchObject({ title });
expect(metadata.twitter).toMatchObject({ title });
}
for (const metadata of [ciMetadata, generationMetadata, adaptiveMetadata]) {
// Nested social metadata replaces parent fields in Next: keep the image,
// description, card and existing homepage URL when overriding just a title.
expect(metadata.openGraph).toEqual({ ...rootMetadata.openGraph, title: metadata.title });
expect(metadata.twitter).toEqual({ ...rootMetadata.twitter, title: metadata.title });
expect(metadata.description).toBe(rootMetadata.description);
expect(metadata.alternates).toBeUndefined();
expect(metadata.robots).toBeUndefined();
}
expect(rootMetadata.alternates).toEqual({ canonical: "/" });
const standalone = new DOMParser().parseFromString(read("app/homepage-content.html").toString(), "text/html");
expect(standalone.title).toBe(rootMetadata.title);
expect(standalone.querySelector('meta[property="og:title"]')?.getAttribute("content")).toBe(rootMetadata.title);
expect(standalone.querySelector('meta[name="twitter:title"]')?.getAttribute("content")).toBe(rootMetadata.title);
});

it("preserves complete outlined logos in social sources and refreshes legacy/download aliases", () => {
for (const path of [`public${brandAssetBase}/surfaces-social.svg`, "public/design-system/logo/surfaces-social-card.svg"]) {
const svg = read(path).toString();
Expand Down
Loading
Loading