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
1 change: 1 addition & 0 deletions apps/web/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# Guidelines

- Keep the site static and useful without JavaScript. Browser code may load only fingerprinted local assets. The sole runtime request is the reviewed PostHog pageview boundary below.
- Use the framework-neutral `@hraness/design-kit` appearance menu as the final action in every ordinary HTML header. Keep exactly one Light, Dark, and System icon-menu control per page, default to System, and never place it inside navigation or a footer.
- Keep `apps/web` independently installable from its Vercel Root Directory: pin every dependency exactly in this package and commit its local `bun.lock`. Verify an isolated `bun install --frozen-lockfile --ignore-scripts`; do not depend on the parent workspace catalog or lockfile.
- Describe the released SDK, local host, and desktop capture shell as one Atet system. Do not introduce a hosted account, billing, authentication, or generation service.
- Keep generation credentials in local SDK or CLI processes. The browser must never accept, store, forward, or render an AI Gateway credential.
Expand Down
127 changes: 127 additions & 0 deletions apps/web/bun.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"type": "module",
"packageManager": "bun@1.3.14",
"dependencies": {
"@hraness/design-kit": "github:hraness/design-kit#v0.1.8",
"posthog-js": "1.413.2"
},
"scripts": {
Expand Down
57 changes: 54 additions & 3 deletions apps/web/scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const sourceDirectory = join(appDirectory, "src")
const defaultOutputDirectory = join(appDirectory, "dist")
const posthogIngestOrigin = "https://us.i.posthog.com"
const posthogPackageDirectory = dirname(fileURLToPath(import.meta.resolve("posthog-js/package.json")))
const appearanceMenuStylesPath = fileURLToPath(
import.meta.resolve("@hraness/design-kit/appearance-menu.css"),
)

const copiedFiles = [
"apple-touch-icon.png",
Expand Down Expand Up @@ -39,6 +42,35 @@ function renderDocument(template: string, assets: Readonly<Record<string, string
return rendered
}

function renderAppearanceMenu(): string {
return `<div aria-busy="true" class="hraness-design-theme-toggle"
data-display="icons" data-hraness-appearance-menu data-presentation="menu"
data-ready="false" data-theme-value="system">
<button aria-controls="appearance-menu" aria-expanded="false" aria-haspopup="menu"
aria-label="Appearance: System" class="hraness-design-theme-toggle__trigger"
disabled type="button">
<span aria-hidden="true" data-current-appearance-icon="system"></span>
</button>
<div class="hraness-design-theme-toggle__popover" hidden>
<div aria-label="Appearance" class="hraness-design-theme-toggle__menu"
id="appearance-menu" role="menu">
<div aria-checked="false" class="hraness-design-theme-toggle__item"
data-theme-value="light" role="menuitemradio" tabindex="-1">
<span aria-hidden="true" data-appearance-icon="light"></span><span>Light</span>
</div>
<div aria-checked="false" class="hraness-design-theme-toggle__item"
data-theme-value="dark" role="menuitemradio" tabindex="-1">
<span aria-hidden="true" data-appearance-icon="dark"></span><span>Dark</span>
</div>
<div aria-checked="true" class="hraness-design-theme-toggle__item"
data-selected="true" data-theme-value="system" role="menuitemradio" tabindex="-1">
<span aria-hidden="true" data-appearance-icon="system"></span><span>System</span>
</div>
</div>
</div>
</div>`
}

type BuildEnvironment = Readonly<Record<string, string | undefined>>

type BuildOptions = Readonly<{
Expand Down Expand Up @@ -100,6 +132,22 @@ async function bundleAnalytics(config: Readonly<{ host: string; key: string }>):
return new Uint8Array(await result.outputs[0].arrayBuffer())
}

async function bundleTheme(): Promise<Uint8Array> {
const result = await Bun.build({
entrypoints: [join(sourceDirectory, "theme.ts")],
env: "disable",
format: "iife",
minify: true,
sourcemap: "none",
target: "browser",
})
if (!result.success || result.outputs.length !== 1) {
const details = result.logs.map(log => log.message).join("\n")
throw new Error(`Could not bundle the appearance client${details === "" ? "" : `: ${details}`}`)
}
return new Uint8Array(await result.outputs[0].arrayBuffer())
}

export async function buildWebsite(options: BuildOptions = {}): Promise<Readonly<{
analyticsPath: string | null
stylesPath: string
Expand All @@ -108,16 +156,19 @@ export async function buildWebsite(options: BuildOptions = {}): Promise<Readonly
const environment = options.environment ?? process.env
const outputDirectory = options.outputDirectory ?? defaultOutputDirectory
const analyticsConfig = productionAnalyticsConfig(environment)
const [indexTemplate, notFoundTemplate, styles, theme] = await Promise.all([
const [indexTemplate, notFoundTemplate, productStyles, appearanceStyles, theme] = await Promise.all([
readFile(join(sourceDirectory, "index.html"), "utf8"),
readFile(join(sourceDirectory, "404.html"), "utf8"),
readFile(join(sourceDirectory, "styles.css")),
readFile(join(sourceDirectory, "theme.js")),
readFile(join(sourceDirectory, "styles.css"), "utf8"),
readFile(appearanceMenuStylesPath, "utf8"),
bundleTheme(),
])
const styles = new TextEncoder().encode(`${productStyles}\n${appearanceStyles}`)

const stylesPath = assetPath("styles.css", styles)
const themePath = assetPath("theme.js", theme)
const commonAssets = {
"{{APPEARANCE_MENU}}": renderAppearanceMenu(),
"{{CSS_ASSET}}": stylesPath,
"{{THEME_ASSET}}": themePath,
} as const
Expand Down
68 changes: 63 additions & 5 deletions apps/web/site.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ async function readSource(path: string): Promise<string> {
return await readFile(join(appDirectory, "src", path), "utf8")
}

async function readBuilt(path: string): Promise<string> {
return await readFile(join(appDirectory, "dist", path), "utf8")
}

describe("static Atet site", () => {
test("makes the README a detailed agent guide with natural GitHub discovery terms", async () => {
const readme = await readFile(join(repositoryDirectory, "README.md"), "utf8")
Expand Down Expand Up @@ -281,6 +285,7 @@ describe("static Atet site", () => {
expect(html.match(/<h1\b/gu)).toHaveLength(1)
expect(html).toContain('<a class="skip-link" href="#main">')
expect(html).toContain('<nav aria-label="Primary">')
expect(html).toContain('<div class="topbar-actions">')
expect(html).toContain('<main id="main" tabindex="-1">')
expect(html).not.toMatch(/<section(?![^>]*aria-labelledby)/)
expect(fragmentLinks.every(fragment => ids.has(fragment))).toBe(true)
Expand All @@ -294,6 +299,35 @@ describe("static Atet site", () => {
expect(css).toContain("@media (forced-colors: active)")
})

test("owns one shared appearance menu as the final action in every header", async () => {
const [html, notFound] = await Promise.all([
readBuilt("index.html"),
readBuilt("404.html"),
])

for (const document of [html, notFound]) {
expect(document.match(/data-hraness-appearance-menu/gu)).toHaveLength(1)
expect(document).toMatch(
/<header class="topbar">[\s\S]*?<div class="topbar-actions">[\s\S]*?<nav aria-label="Primary">[\s\S]*?<\/nav>\s*<div[^>]*data-hraness-appearance-menu[^>]*>[\s\S]*?<\/div>\s*<\/div>\s*<\/header>/u,
)
expect(document.slice(document.indexOf('<footer class="site-footer">')))
.not.toContain("data-hraness-appearance-menu")
expect(document).not.toContain('class="appearance"')
expect(document).not.toContain("data-theme-choice")
expect(document).toContain('aria-label="Appearance: System"')
expect(document).toContain('aria-haspopup="menu"')
expect(document).toContain('aria-label="Appearance"')
expect(document.match(/role="menuitemradio"/gu)).toHaveLength(3)
expect([...document.matchAll(/data-theme-value="(light|dark|system)" role="menuitemradio"/gu)]
.map(match => match[1])).toEqual(["light", "dark", "system"])
}

expect(notFound).toContain('<a class="skip-link" href="#main">')
expect(notFound).toContain('<main class="route-state" id="main" tabindex="-1">')
expect(notFound).toContain('<meta name="theme-color" content="#f7f3ea" media="(prefers-color-scheme: light)">')
expect(notFound).toContain('<meta name="theme-color" content="#0b0b0e" media="(prefers-color-scheme: dark)">')
})

test("uses a restrained editorial visual system", async () => {
const css = await readSource("styles.css")

Expand Down Expand Up @@ -333,7 +367,7 @@ describe("static Atet site", () => {
test("keeps the static shell fingerprinted and analytics explicit", async () => {
const html = await readSource("index.html")
const css = await readSource("styles.css")
const theme = await readSource("theme.js")
const theme = await readSource("theme.ts")
const analytics = await readSource("analytics.ts")
const build = await readFile(join(appDirectory, "scripts/build.ts"), "utf8")
const manifest = JSON.parse(
Expand All @@ -344,19 +378,29 @@ describe("static Atet site", () => {
) as { workspaces?: { catalog?: Record<string, string> } }
const localLockfile = await readFile(join(appDirectory, "bun.lock"), "utf8")

expect(manifest.dependencies).toEqual({ "posthog-js": "1.413.2" })
expect(manifest.dependencies).toEqual({
"@hraness/design-kit": "github:hraness/design-kit#v0.1.8",
"posthog-js": "1.413.2",
})
expect(manifest.devDependencies).toBeUndefined()
expect(rootManifest.workspaces?.catalog?.["posthog-js"]).toBeUndefined()
expect(rootManifest.workspaces?.catalog?.["@hraness/design-kit"]).toBeUndefined()
expect(localLockfile).toContain('"@hraness/design-kit": "github:hraness/design-kit#v0.1.8"')
expect(localLockfile).toContain('"posthog-js": "1.413.2"')
expect(localLockfile).not.toContain("catalog:")
expect(new TextEncoder().encode(html).byteLength).toBeLessThan(20_000)
expect(new TextEncoder().encode(css).byteLength).toBeLessThan(28_000)
expect(new TextEncoder().encode(theme).byteLength).toBeLessThan(3_000)
expect(html).not.toMatch(/https:\/\/[^"']+\.(?:css|js)/)
expect(html).toContain('<link rel="stylesheet" href="{{CSS_ASSET}}">')
expect(html).toContain('<script src="{{THEME_ASSET}}" defer></script>')
expect(html).toContain('<script src="{{THEME_ASSET}}"></script>')
expect(html.indexOf('<script src="{{THEME_ASSET}}"></script>'))
.toBeLessThan(html.indexOf('<link rel="stylesheet" href="{{CSS_ASSET}}">'))
expect(html).toContain("{{APPEARANCE_MENU}}")
expect(html).toContain("{{ANALYTICS_SCRIPT}}")
expect(html.match(/<script\b/gu)).toHaveLength(2)
expect(theme).toContain('from "@hraness/design-kit/browser"')
expect(theme).toContain('storageKey: "atet.appearance"')
expect(theme).not.toMatch(/fetch\(|XMLHttpRequest|WebSocket|EventSource|sendBeacon/)
expect(analytics).toContain('cookieless_mode: "always"')
expect(analytics).toContain('person_profiles: "never"')
Expand All @@ -369,6 +413,9 @@ describe("static Atet site", () => {
expect(analytics).not.toMatch(/identify\(|autocapture:\s*true|capture_pageleave:\s*true/)
expect(build).toContain('createHash("sha256")')
expect(build).toContain("Bun.build")
expect(build).toContain('format: "iife"')
expect(build).toContain('import.meta.resolve("@hraness/design-kit/appearance-menu.css")')
expect(build).toContain("renderAppearanceMenu()")
expect(build).toContain('environment.VERCEL_ENV !== "production"')
expect(build).not.toContain("docsTemplate")
expect(build).not.toContain('outputDirectory, "docs"')
Expand Down Expand Up @@ -509,11 +556,11 @@ describe("static Atet site", () => {
])

expect(html).toContain(`<link rel="stylesheet" href="${builtAssets.stylesPath}">`)
expect(html).toContain(`<script src="${builtAssets.themePath}" defer></script>`)
expect(html).toContain(`<script src="${builtAssets.themePath}"></script>`)
expect(builtAssets.analyticsPath).toBeNull()
expect(html).not.toMatch(/analytics-/)
expect(notFound).toContain(`<link rel="stylesheet" href="${builtAssets.stylesPath}">`)
expect(notFound).toContain(`<script src="${builtAssets.themePath}" defer></script>`)
expect(notFound).toContain(`<script src="${builtAssets.themePath}"></script>`)
expect(`${html}\n${notFound}`).not.toContain("{{")
expect(rootFiles.sort()).toEqual([
"404.html",
Expand All @@ -529,6 +576,17 @@ describe("static Atet site", () => {
builtAssets.stylesPath.split("/").at(-1)!,
builtAssets.themePath.split("/").at(-1)!,
].sort())

const [stylesAsset, themeAsset] = await Promise.all([
readFile(join(appDirectory, "dist", builtAssets.stylesPath.slice(1)), "utf8"),
readFile(join(appDirectory, "dist", builtAssets.themePath.slice(1)), "utf8"),
])
expect(stylesAsset).toContain(".hraness-design-theme-toggle__trigger")
expect(stylesAsset).toContain("@media (pointer: coarse)")
expect(new TextEncoder().encode(stylesAsset).byteLength).toBeLessThan(36_000)
expect(new TextEncoder().encode(themeAsset).byteLength).toBeLessThan(24_000)
expect(themeAsset).not.toMatch(/react|next-themes|react-aria/i)
expect(themeAsset).not.toMatch(/fetch\(|XMLHttpRequest|WebSocket|EventSource|sendBeacon/)
})

test("publishes only the canonical page to crawler discovery", async () => {
Expand Down
23 changes: 15 additions & 8 deletions apps/web/src/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,28 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#f7f3ea" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0b0b0e" media="(prefers-color-scheme: dark)">
<script src="{{THEME_ASSET}}"></script>
<meta name="robots" content="noindex, nofollow">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="stylesheet" href="{{CSS_ASSET}}">
<script src="{{THEME_ASSET}}" defer></script>
<title>Not found · Atet</title>
</head>
<body>
<main class="route-state" id="main">
<a class="skip-link" href="#main">Skip to content</a>
<header class="topbar">
<a aria-label="Atet home" class="wordmark" href="/">Atet</a>
<div class="topbar-actions">
<nav aria-label="Primary">
<a href="/">Home</a>
<a class="nav-github" href="https://github.com/hraness/atet">GitHub <span aria-hidden="true">↗</span></a>
</nav>
{{APPEARANCE_MENU}}
</div>
</header>
<main class="route-state" id="main" tabindex="-1">
<p class="eyebrow">404 · Route not found</p>
<h1>This passage ends here.</h1>
<p>The address may have changed. Atet and its current installation guide remain at the canonical home.</p>
Expand All @@ -30,12 +43,6 @@ <h1>This passage ends here.</h1>
</svg>
<span>hraness</span>
</a>
<fieldset class="appearance" aria-label="Appearance">
<legend>Appearance</legend>
<button type="button" data-theme-choice="light">Light</button>
<button type="button" data-theme-choice="dark">Dark</button>
<button type="button" data-theme-choice="system" aria-pressed="true">System</button>
</fieldset>
</div>
</footer>
</body>
Expand Down
21 changes: 9 additions & 12 deletions apps/web/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#f7f3ea" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#09090d" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#0b0b0e" media="(prefers-color-scheme: dark)">
<script src="{{THEME_ASSET}}"></script>
<title>Atet: AI media generation and video editing for coding agents</title>
<meta name="description" content="Atet gives coding agents tools to generate images, video, and voice, edit real footage, add motion graphics and captions, and export finished videos.">
<meta name="application-name" content="Atet">
Expand Down Expand Up @@ -114,17 +115,19 @@
]
}
</script>
<script src="{{THEME_ASSET}}" defer></script>
{{ANALYTICS_SCRIPT}}
</head>
<body class="docs-route">
<a class="skip-link" href="#main">Skip to content</a>
<header class="topbar">
<a aria-label="Atet home" class="wordmark" href="/">Atet</a>
<nav aria-label="Primary">
<a href="#install">Install</a>
<a class="nav-github" href="https://github.com/hraness/atet">GitHub <span aria-hidden="true">↗</span></a>
</nav>
<div class="topbar-actions">
<nav aria-label="Primary">
<a href="#install">Install</a>
<a class="nav-github" href="https://github.com/hraness/atet">GitHub <span aria-hidden="true">↗</span></a>
</nav>
{{APPEARANCE_MENU}}
</div>
</header>

<main id="main" tabindex="-1">
Expand Down Expand Up @@ -310,12 +313,6 @@ <h3>The name Atet</h3>
<span>hraness</span>
</a>
<p class="footer-note">Atet · MIT · AI media generation and video editing for coding agents.</p>
<fieldset class="appearance" aria-label="Appearance">
<legend>Appearance</legend>
<button type="button" data-theme-choice="light">Light</button>
<button type="button" data-theme-choice="dark">Dark</button>
<button type="button" data-theme-choice="system" aria-pressed="true">System</button>
</fieldset>
</div>
</footer>
</body>
Expand Down
Loading
Loading