From 57028228b793be13bf0a2ac319c6eda0c077d8c1 Mon Sep 17 00:00:00 2001 From: 0thernet Date: Wed, 19 Aug 2026 19:21:24 -0400 Subject: [PATCH 1/2] Add privacy-bounded Atet visitor analytics --- AGENTS.md | 2 +- NOTICE.md | 5 + apps/web/.env.example | 3 + apps/web/AGENTS.md | 6 +- apps/web/README.md | 16 +-- apps/web/package.json | 5 +- apps/web/scripts/build.ts | 95 +++++++++++++++-- apps/web/site.test.ts | 165 +++++++++++++++++++++++++++-- apps/web/src/analytics-contract.ts | 37 +++++++ apps/web/src/analytics.ts | 51 +++++++++ apps/web/src/index.html | 1 + apps/web/vercel.json | 6 +- bun.lock | 28 +++++ docs/vercel.md | 6 ++ package.json | 1 + 15 files changed, 396 insertions(+), 31 deletions(-) create mode 100644 apps/web/.env.example create mode 100644 apps/web/src/analytics-contract.ts create mode 100644 apps/web/src/analytics.ts diff --git a/AGENTS.md b/AGENTS.md index 808da2b..61b2f4f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ - `src/` – portable declarative and imperative workflow SDKs, host-resource admission, diagrams, direct Vercel AI Gateway generation, local vectorization, semantic operations, MCP, and canvas integration. - `apps/desktop/` – the canonical `atet` CLI, complete local media host, durable scheduler, HTML and Three.js overlays, native capture helpers, frontend, and desktop shell. -- `apps/web/` – the dependency-free static `atet.sh` site, with no API, account, or credential surface. +- `apps/web/` – the static `atet.sh` site, with a local browser bundle and a production-only anonymous pageview boundary but no API, account, or credential surface. - `packages/scene/` – the shared local scene-analysis contract. - `src/code/` – portable declarative graph authoring, the closed public capability projection, compilation, planning, and execution contracts. - `schema/` – version-one diagram JSON Schema. diff --git a/NOTICE.md b/NOTICE.md index b824d56..c6b2628 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -42,3 +42,8 @@ Optional model-backed generation uses sent to Vercel AI Gateway and the model provider selected by the caller, under those services' terms. Atet does not operate an intermediary generation service. + +The production `atet.sh` browser bundle includes +[`posthog-js`](https://github.com/PostHog/posthog-js), distributed under its +combined Apache License 2.0 and MIT terms. The fingerprinted bundle carries the +upstream license text generated from the exact locked package. diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 0000000..262450f --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1,3 @@ +# Configure this public client token only in Vercel's Production environment. +NEXT_PUBLIC_POSTHOG_KEY= +NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com diff --git a/apps/web/AGENTS.md b/apps/web/AGENTS.md index 9ef84bf..365f2d7 100644 --- a/apps/web/AGENTS.md +++ b/apps/web/AGENTS.md @@ -1,17 +1,17 @@ # Contents - `src/` contains the static `atet.sh` homepage and documentation, visual system, appearance control, crawler files, favicons, and social preview. -- `scripts/build.ts` renders fingerprinted local assets into `dist/` from an explicit allowlist without dependencies or network access. +- `scripts/build.ts` renders fingerprinted local assets into `dist/` from an explicit allowlist and bundles the pinned PostHog browser client only for a configured Production build. - `site.test.ts`, `package.json`, and `vercel.json` define the content, identity, accessibility, performance, legacy-host, and deployment contracts. # Guidelines -- Keep the site static and useful without JavaScript. JavaScript may improve appearance controls, but it must never call a network API, load remote code, or receive credentials. +- 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. - 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. - Preserve the four public output families: images, diagrams, animated loops, and video. Treat audio and captions as composable project inputs rather than another project model. - Use Atet, Ra's solar barque, respectfully as an abstract metaphor for passage and transformation. Do not imitate sacred figures, hieroglyphs, or archaeological objects. -- Keep every page semantic, keyboard-operable, readable at 200% zoom, and free of analytics, remote fonts, client frameworks, and runtime network requests. Keep the homepage concise; put durable usage detail in `/docs`. +- Keep every page semantic, keyboard-operable, readable at 200% zoom, and free of remote fonts and client frameworks. Analytics may emit one anonymous cookieless `$pageview` from `https://atet.sh/` to `https://us.i.posthog.com`, tagged with `site_id=atet` and `analytics_schema_version=1`. Keep persons, persistence, autocapture, replay, flags, surveys, heatmaps, pageleave, web vitals, referrer, URL, query, hash, page text, content, and custom events disabled. Do not initialize analytics on Preview, staging, alternate hosts, or `404.html`. - Organize `/docs` by user intent: guided learning, goal-oriented how-to, factual reference, and conceptual explanation. Do not mix those modes into one undifferentiated command catalog. - Use the canonical Hraness footer lockup. Include every durable public route in crawler discovery. - Preserve permanent production and preview redirects for every reviewed predecessor host without redirecting canonical Atet hosts. diff --git a/apps/web/README.md b/apps/web/README.md index ed8b576..031f5f9 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,8 +1,8 @@ # Atet web -`atet.sh` is the dependency-free public site and documentation for Atet. It +`atet.sh` is the static public site and documentation for Atet. It presents the SDK, Bun CLI, local runtime, and desktop capture shell without -adding a server, account surface, API route, analytics, remote font, or browser credential path. +adding a server, account surface, API route, remote font, or browser credential path. Generation runs from the local Atet SDK or CLI with the operator's Vercel AI Gateway access. @@ -11,7 +11,11 @@ bun run check ``` The build fingerprints the local stylesheet and appearance script, then copies -an explicit allowlist from `src/` into `dist/`. Configure the Vercel project -with this directory as its Root Directory. The checked `vercel.json` performs -no dependency install, serves only built files under a strict CSP, and sends -reviewed predecessor hosts to their matching Atet production or preview host. +an explicit allowlist from `src/` into `dist/`. A configured Vercel Production +build also bundles the pinned PostHog client as a fingerprinted local asset. +That client sends one anonymous cookieless pageview from `https://atet.sh/` and +does not run on Preview, alternate hosts, or the not-found page. Configure the +Vercel project with this directory as its Root Directory. The checked +`vercel.json` installs from the frozen Bun lockfile, serves only built files +under a strict CSP, and sends reviewed predecessor hosts to their matching Atet +production or preview host. diff --git a/apps/web/package.json b/apps/web/package.json index c24a291..0ed5e62 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -2,9 +2,12 @@ "name": "@hraness/atet-web", "version": "0.0.0", "private": true, - "description": "The dependency-free static public site for Atet.", + "description": "The static public site for Atet.", "type": "module", "packageManager": "bun@1.3.14", + "dependencies": { + "posthog-js": "catalog:" + }, "scripts": { "build": "bun run ./scripts/build.ts", "check": "bun run test && bun run build", diff --git a/apps/web/scripts/build.ts b/apps/web/scripts/build.ts index 8373061..fed7ee0 100644 --- a/apps/web/scripts/build.ts +++ b/apps/web/scripts/build.ts @@ -5,7 +5,9 @@ import { fileURLToPath } from "node:url" const appDirectory = dirname(dirname(fileURLToPath(import.meta.url))) const sourceDirectory = join(appDirectory, "src") -const outputDirectory = join(appDirectory, "dist") +const defaultOutputDirectory = join(appDirectory, "dist") +const posthogIngestOrigin = "https://us.i.posthog.com" +const posthogPackageDirectory = dirname(fileURLToPath(import.meta.resolve("posthog-js/package.json"))) const copiedFiles = [ "apple-touch-icon.png", @@ -37,10 +39,75 @@ function renderDocument(template: string, assets: Readonly> + +type BuildOptions = Readonly<{ + environment?: BuildEnvironment + outputDirectory?: string +}> + +function productionAnalyticsConfig(environment: BuildEnvironment): Readonly<{ + host: string + key: string +}> | null { + const key = environment.NEXT_PUBLIC_POSTHOG_KEY?.trim() + if (environment.VERCEL_ENV !== "production" || key === undefined || key === "") { + return null + } + if (!/^phc_[A-Za-z0-9_-]+$/u.test(key)) { + throw new Error("NEXT_PUBLIC_POSTHOG_KEY must be a PostHog project token") + } + + const host = environment.NEXT_PUBLIC_POSTHOG_HOST?.trim() || posthogIngestOrigin + if (host !== posthogIngestOrigin) { + throw new Error(`NEXT_PUBLIC_POSTHOG_HOST must equal ${posthogIngestOrigin}`) + } + return { host, key } +} + +async function bundleAnalytics(config: Readonly<{ host: string; key: string }>): Promise { + const [license, manifestSource] = await Promise.all([ + readFile(join(posthogPackageDirectory, "LICENSE"), "utf8"), + readFile(join(posthogPackageDirectory, "package.json"), "utf8"), + ]) + const manifest = JSON.parse(manifestSource) as unknown + if ( + typeof manifest !== "object" + || manifest === null + || !("version" in manifest) + || typeof manifest.version !== "string" + || license.includes("*/") + ) { + throw new Error("The installed PostHog package has an invalid license boundary") + } + const result = await Bun.build({ + banner: `/*! posthog-js ${manifest.version}\n${license.trim()}\n*/`, + define: { + __ATET_POSTHOG_HOST__: JSON.stringify(config.host), + __ATET_POSTHOG_KEY__: JSON.stringify(config.key), + }, + entrypoints: [join(sourceDirectory, "analytics.ts")], + env: "disable", + format: "esm", + 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 analytics client${details === "" ? "" : `: ${details}`}`) + } + return new Uint8Array(await result.outputs[0].arrayBuffer()) +} + +export async function buildWebsite(options: BuildOptions = {}): Promise> { + const environment = options.environment ?? process.env + const outputDirectory = options.outputDirectory ?? defaultOutputDirectory + const analyticsConfig = productionAnalyticsConfig(environment) const [indexTemplate, notFoundTemplate, styles, theme] = await Promise.all([ readFile(join(sourceDirectory, "index.html"), "utf8"), readFile(join(sourceDirectory, "404.html"), "utf8"), @@ -50,19 +117,30 @@ export async function buildWebsite(): Promise`, + } as const await rm(outputDirectory, { force: true, recursive: true }) await mkdir(join(outputDirectory, "assets"), { recursive: true }) await Promise.all([ - writeFile(join(outputDirectory, "index.html"), renderDocument(indexTemplate, assets)), - writeFile(join(outputDirectory, "404.html"), renderDocument(notFoundTemplate, assets)), + writeFile(join(outputDirectory, "index.html"), renderDocument(indexTemplate, indexAssets)), + writeFile(join(outputDirectory, "404.html"), renderDocument(notFoundTemplate, commonAssets)), writeFile(join(outputDirectory, stylesPath.slice(1)), styles), writeFile(join(outputDirectory, themePath.slice(1)), theme), + ...(analyticsPath === null || analytics === null + ? [] + : [writeFile(join(outputDirectory, analyticsPath.slice(1)), analytics)]), ]) for (const file of copiedFiles) { @@ -77,10 +155,11 @@ export async function buildWebsite(): Promise { expect(icon).toContain('stop-color="#f6b94a"') }) - test("keeps the build dependency-free, fingerprinted, and network-inert", async () => { + 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 analytics = await readSource("analytics.ts") const build = await readFile(join(appDirectory, "scripts/build.ts"), "utf8") const manifest = JSON.parse( await readFile(join(appDirectory, "package.json"), "utf8"), - ) as Record + ) as { dependencies?: Record; devDependencies?: unknown } - expect(manifest.dependencies).toBeUndefined() + expect(manifest.dependencies).toEqual({ "posthog-js": "catalog:" }) expect(manifest.devDependencies).toBeUndefined() expect(new TextEncoder().encode(html).byteLength).toBeLessThan(20_000) expect(new TextEncoder().encode(css).byteLength).toBeLessThan(28_000) @@ -341,14 +348,151 @@ describe("static Atet site", () => { expect(html).not.toMatch(/https:\/\/[^"']+\.(?:css|js)/) expect(html).toContain('') expect(html).toContain('') - expect(html).not.toMatch(/analytics|posthog|plausible|segment/i) + expect(html).toContain("{{ANALYTICS_SCRIPT}}") expect(html.match(/ { + expect(isCanonicalAnalyticsPage({ origin: "https://atet.sh", pathname: "/" })).toBe(true) + expect(isCanonicalAnalyticsPage({ origin: "https://preview.atet.sh", pathname: "/" })).toBe(false) + expect(isCanonicalAnalyticsPage({ origin: "https://atet.sh", pathname: "/404" })).toBe(false) + + const timestamp = new Date("2026-08-19T12:00:00.000Z") + const sanitized = sanitizePageview({ + event: "$pageview", + properties: { + $cookieless_mode: true, + $current_url: "https://atet.sh/?private=value#fragment", + $device_id: "device", + $pathname: "/", + $referrer: "https://example.com/private", + analytics_schema_version: 99, + distinct_id: posthogCookielessDistinctId, + site_id: "wrong", + token: "phc_testtoken", + }, + timestamp, + uuid: "0198c6a7-7c00-7000-8000-000000000000", + }, "phc_testtoken") + + expect(sanitized).toEqual({ + event: "$pageview", + properties: { + $cookieless_mode: true, + $process_person_profile: false, + analytics_schema_version: 1, + distinct_id: posthogCookielessDistinctId, + site_id: "atet", + token: "phc_testtoken", + }, + timestamp, + uuid: "0198c6a7-7c00-7000-8000-000000000000", + }) + expect(sanitizePageview({ + event: "$autocapture", + properties: { distinct_id: posthogCookielessDistinctId, token: "phc_testtoken" }, + uuid: "0198c6a7-7c00-7000-8000-000000000001", + }, "phc_testtoken")).toBeNull() + expect(sanitizePageview({ + event: "$pageview", + properties: { distinct_id: "persisted-id", token: "phc_testtoken" }, + uuid: "0198c6a7-7c00-7000-8000-000000000002", + }, "phc_testtoken")).toBeNull() + expect(sanitizePageview({ + event: "$pageview", + properties: { + distinct_id: posthogCookielessDistinctId, + token: "phc_testtoken", + }, + uuid: "0198c6a7-7c00-7000-8000-000000000003", + }, "phc_testtoken")).toBeNull() + expect(sanitizePageview({ + event: "$pageview", + properties: { + $cookieless_mode: false, + distinct_id: posthogCookielessDistinctId, + token: "phc_testtoken", + }, + uuid: "0198c6a7-7c00-7000-8000-000000000004", + }, "phc_testtoken")).toBeNull() + }) + + test("emits analytics only for a configured Production build", async () => { + const productionDirectory = await mkdtemp(join(tmpdir(), "atet-web-production-")) + const secondDirectory = await mkdtemp(join(tmpdir(), "atet-web-production-repeat-")) + try { + const environment = { + NEXT_PUBLIC_POSTHOG_HOST: "https://us.i.posthog.com", + NEXT_PUBLIC_POSTHOG_KEY: "phc_test-token_value", + VERCEL_ENV: "production", + } as const + const first = await buildWebsite({ environment, outputDirectory: productionDirectory }) + const second = await buildWebsite({ environment, outputDirectory: secondDirectory }) + expect(first.analyticsPath).toMatch(/^\/assets\/analytics-[a-f0-9]{12}\.js$/u) + expect(second.analyticsPath).toBe(first.analyticsPath) + + const [html, notFound, asset] = await Promise.all([ + readFile(join(productionDirectory, "index.html"), "utf8"), + readFile(join(productionDirectory, "404.html"), "utf8"), + readFile(join(productionDirectory, first.analyticsPath?.slice(1) ?? "missing"), "utf8"), + ]) + expect(html).toContain(``) + expect(notFound).not.toMatch(/analytics-|posthog|phc_test-token_value/i) + expect(asset).toContain("phc_test-token_value") + expect(asset).toContain("https://us.i.posthog.com") + expect(asset).toStartWith("/*! posthog-js 1.413.2") + expect(asset).toContain("Apache License\n Version 2.0") + expect(new TextEncoder().encode(asset).byteLength).toBeLessThan(180_000) + } finally { + await Promise.all([ + rm(productionDirectory, { force: true, recursive: true }), + rm(secondDirectory, { force: true, recursive: true }), + ]) + } + }) + + test("keeps missing, Preview, and unsupported-host analytics builds inert", async () => { + const outputDirectory = await mkdtemp(join(tmpdir(), "atet-web-inert-")) + try { + const preview = await buildWebsite({ + environment: { + NEXT_PUBLIC_POSTHOG_KEY: "phc_testtoken", + VERCEL_ENV: "preview", + }, + outputDirectory, + }) + expect(preview.analyticsPath).toBeNull() + expect(await readFile(join(outputDirectory, "index.html"), "utf8")) + .not.toMatch(/analytics-|phc_testtoken/) + + await expect(buildWebsite({ + environment: { + NEXT_PUBLIC_POSTHOG_HOST: "https://example.com", + NEXT_PUBLIC_POSTHOG_KEY: "phc_testtoken", + VERCEL_ENV: "production", + }, + outputDirectory, + })).rejects.toThrow("NEXT_PUBLIC_POSTHOG_HOST must equal https://us.i.posthog.com") + } finally { + await rm(outputDirectory, { force: true, recursive: true }) + } + }) + test("renders one closed static page with resolved content-hashed assets", async () => { const [html, notFound, rootFiles, assetFiles] = await Promise.all([ readFile(join(appDirectory, "dist/index.html"), "utf8"), @@ -359,6 +503,8 @@ describe("static Atet site", () => { expect(html).toContain(``) expect(html).toContain(``) + expect(builtAssets.analyticsPath).toBeNull() + expect(html).not.toMatch(/analytics-/) expect(notFound).toContain(``) expect(notFound).toContain(``) expect(`${html}\n${notFound}`).not.toContain("{{") @@ -373,8 +519,8 @@ describe("static Atet site", () => { "sitemap.xml", ]) expect(assetFiles.sort()).toEqual([ - builtAssets.stylesPath.split("/").at(-1), - builtAssets.themePath.split("/").at(-1), + builtAssets.stylesPath.split("/").at(-1)!, + builtAssets.themePath.split("/").at(-1)!, ].sort()) }) @@ -468,11 +614,12 @@ describe("static Atet site", () => { const byKey = new Map(global.map(header => [header.key, header.value])) const csp = byKey.get("Content-Security-Policy") ?? "" - expect(csp).toContain("connect-src 'none'") + expect(csp).toContain("connect-src https://us.i.posthog.com") expect(csp).toContain("font-src 'none'") expect(csp).toContain("form-action 'none'") expect(csp).toContain("frame-ancestors 'none'") expect(csp).toContain("object-src 'none'") + expect(byKey.get("Referrer-Policy")).toBe("no-referrer") expect(byKey.get("Strict-Transport-Security")).toContain("includeSubDomains") expect(assets).toContainEqual({ key: "Cache-Control", diff --git a/apps/web/src/analytics-contract.ts b/apps/web/src/analytics-contract.ts new file mode 100644 index 0000000..bb6d673 --- /dev/null +++ b/apps/web/src/analytics-contract.ts @@ -0,0 +1,37 @@ +import type { CaptureResult } from "posthog-js/dist/module.slim.no-external" + +export const analyticsSchemaVersion = 1 +export const canonicalAnalyticsOrigin = "https://atet.sh" +export const posthogCookielessDistinctId = "$posthog_cookieless" +export const siteId = "atet" + +export function isCanonicalAnalyticsPage(location: Readonly>): boolean { + return location.origin === canonicalAnalyticsOrigin && location.pathname === "/" +} + +export function sanitizePageview(event: CaptureResult | null, publicKey: string): CaptureResult | null { + if (event?.event !== "$pageview") { + return null + } + if ( + event.properties.token !== publicKey + || event.properties.distinct_id !== posthogCookielessDistinctId + || event.properties.$cookieless_mode !== true + ) { + return null + } + + return { + event: "$pageview", + properties: { + $process_person_profile: false, + $cookieless_mode: true, + analytics_schema_version: analyticsSchemaVersion, + distinct_id: posthogCookielessDistinctId, + site_id: siteId, + token: publicKey, + }, + timestamp: event.timestamp, + uuid: event.uuid, + } +} diff --git a/apps/web/src/analytics.ts b/apps/web/src/analytics.ts new file mode 100644 index 0000000..f01a8cb --- /dev/null +++ b/apps/web/src/analytics.ts @@ -0,0 +1,51 @@ +import posthog from "posthog-js/dist/module.slim.no-external" + +import { isCanonicalAnalyticsPage, sanitizePageview } from "./analytics-contract" + +declare const __ATET_POSTHOG_HOST__: string +declare const __ATET_POSTHOG_KEY__: string + +if (isCanonicalAnalyticsPage(window.location)) { + posthog.init(__ATET_POSTHOG_KEY__, { + advanced_disable_flags: true, + advanced_disable_toolbar_metrics: true, + autocapture: false, + before_send: event => ( + isCanonicalAnalyticsPage(window.location) + ? sanitizePageview(event, __ATET_POSTHOG_KEY__) + : null + ), + capture_dead_clicks: false, + capture_exceptions: false, + capture_heatmaps: false, + capture_pageleave: false, + capture_pageview: false, + capture_performance: false, + cookieless_mode: "always", + disableDeviceModel: true, + disable_conversations: true, + disable_external_dependency_loading: true, + disable_persistence: true, + disable_product_tours: true, + disable_scroll_properties: true, + disable_session_recording: true, + disable_surveys: true, + disable_web_experiments: true, + enable_recording_console_log: false, + mask_all_element_attributes: true, + mask_all_text: true, + persistence: "memory", + person_profiles: "never", + request_batching: false, + save_campaign_params: false, + save_referrer: false, + api_host: __ATET_POSTHOG_HOST__, + }) + posthog.capture("$pageview", { + analytics_schema_version: 1, + site_id: "atet", + }, { + send_instantly: true, + transport: "fetch", + }) +} diff --git a/apps/web/src/index.html b/apps/web/src/index.html index ec100d9..a7cd007 100644 --- a/apps/web/src/index.html +++ b/apps/web/src/index.html @@ -115,6 +115,7 @@ } + {{ANALYTICS_SCRIPT}} diff --git a/apps/web/vercel.json b/apps/web/vercel.json index 2b6acce..844a475 100644 --- a/apps/web/vercel.json +++ b/apps/web/vercel.json @@ -1,7 +1,7 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "bun run build", - "installCommand": "bun --version", + "installCommand": "bun install --frozen-lockfile --ignore-scripts", "outputDirectory": "dist", "cleanUrls": true, "trailingSlash": false, @@ -71,7 +71,7 @@ "headers": [ { "key": "Content-Security-Policy", - "value": "default-src 'self'; base-uri 'none'; connect-src 'none'; font-src 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self' data:; object-src 'none'; script-src 'self'; style-src 'self'; upgrade-insecure-requests" + "value": "default-src 'self'; base-uri 'none'; connect-src https://us.i.posthog.com; font-src 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self' data:; object-src 'none'; script-src 'self'; style-src 'self'; upgrade-insecure-requests" }, { "key": "Permissions-Policy", @@ -79,7 +79,7 @@ }, { "key": "Referrer-Policy", - "value": "strict-origin-when-cross-origin" + "value": "no-referrer" }, { "key": "X-Content-Type-Options", diff --git a/bun.lock b/bun.lock index ed2ade5..d78a698 100644 --- a/bun.lock +++ b/bun.lock @@ -45,6 +45,9 @@ "apps/web": { "name": "@hraness/atet-web", "version": "0.0.0", + "dependencies": { + "posthog-js": "catalog:", + }, }, "packages/scene": { "name": "@hraness/atet-scene", @@ -72,6 +75,7 @@ "fast-check": "^4.8.0", "motion": "12.42.0", "playwright-core": "1.62.0", + "posthog-js": "1.413.2", "react": "19.2.3", "react-dom": "19.2.3", "three": "0.183.2", @@ -210,6 +214,12 @@ "@paper-design/shaders": ["@paper-design/shaders@0.0.77", "", {}, "sha512-y0mR3brBu6qvVgbnvBdXX3zL/hajdamkECLt8NbexaEc1L46W3a+ZIEwxu1NJ2/bqwxAgjJC0o2+4CAii5wp/w=="], + "@posthog/browser-common": ["@posthog/browser-common@0.4.0", "", { "dependencies": { "@posthog/core": "^1.46.8", "@posthog/types": "^1.402.0" } }, "sha512-W9DCGVks15docUMPvJ2nd8NS16Gn74bsGWuaeg31beEKFSjdW8wvnQ1ETY6WSql5pYxZb3GdJmEUZVVstKSrBQ=="], + + "@posthog/core": ["@posthog/core@1.48.5", "", { "dependencies": { "@posthog/types": "^1.405.0" } }, "sha512-ZE27PmVyPmw9wZOVVyn64uaTdqhopuwQfX7PzBpyCvsezuvNmrUHAMpJKkqOvaHxu7HeJt1jTQtjtTuwy+S7Og=="], + + "@posthog/types": ["@posthog/types@1.405.0", "", {}, "sha512-4rZ/taVXKQxs9Jrf7ZjlCRgrOSL69oKAgIWJQa5kRNJ6wll1UANbrJTSY+Su1e88LIG4zZVjKKKjyQHCkHdHcw=="], + "@radix-ui/number": ["@radix-ui/number@1.1.3", "", {}, "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.1.7", "", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="], @@ -474,6 +484,8 @@ "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.67.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/type-utils": "8.67.0", "@typescript-eslint/utils": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.67.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ=="], @@ -542,6 +554,8 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "core-js": ["core-js@3.50.0", "", {}, "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], @@ -556,6 +570,8 @@ "devtools-protocol": ["devtools-protocol@0.0.1678596", "", {}, "sha512-rYFqTG2omeVo0w4nLc6sLQj2Ja3TsSOt4w6mcoDpg0fi0wtT427vDcj56TksRJveZHv4XfsiztO1y7jwgWcpVA=="], + "dompurify": ["dompurify@3.4.14", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="], @@ -592,6 +608,8 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "fflate": ["fflate@0.4.9", "", {}, "sha512-zdxgIEddhfsyCaWpJ2SdXEP8ZMrKJ6+5jl4OupODcywU0IhRk6gdXuVGcPICyfx2H97hVK7xmJtRLPjkxAX8Vw=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], @@ -720,6 +738,10 @@ "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], + "posthog-js": ["posthog-js@1.413.2", "", { "dependencies": { "@posthog/browser-common": "^0.4.0", "@posthog/core": "^1.46.8", "@posthog/types": "^1.402.1", "core-js": "^3.49.0", "dompurify": "^3.4.12", "fflate": "^0.4.8", "preact": "^10.29.3", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^5.3.0", "web-vitals-soft-navs": "npm:web-vitals@6.0.0" } }, "sha512-LddTVMdYFd0jB+p5h3ZHcho9mWNjgKn9QySCQt74iISKrmC3b7KzW2Q7W7aOF6m/NwK7NubIHqLujCER64gSRw=="], + + "preact": ["preact@10.29.8", "", { "peerDependencies": { "preact-render-to-string": ">=5" }, "optionalPeers": ["preact-render-to-string"] }, "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "prosemirror-changeset": ["prosemirror-changeset@2.4.1", "", { "dependencies": { "prosemirror-transform": "^1.0.0" } }, "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw=="], @@ -752,6 +774,8 @@ "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], + "query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="], + "quickselect": ["quickselect@2.0.0", "", {}, "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="], "radix-ui": ["radix-ui@1.6.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-accessible-icon": "1.1.15", "@radix-ui/react-accordion": "1.2.20", "@radix-ui/react-alert-dialog": "1.1.23", "@radix-ui/react-arrow": "1.1.15", "@radix-ui/react-aspect-ratio": "1.1.15", "@radix-ui/react-avatar": "1.2.6", "@radix-ui/react-checkbox": "1.3.11", "@radix-ui/react-collapsible": "1.1.20", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-context-menu": "2.3.7", "@radix-ui/react-dialog": "1.1.23", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-dropdown-menu": "2.1.24", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-form": "0.1.16", "@radix-ui/react-hover-card": "1.1.23", "@radix-ui/react-label": "2.1.15", "@radix-ui/react-menu": "2.1.24", "@radix-ui/react-menubar": "1.1.24", "@radix-ui/react-navigation-menu": "1.2.22", "@radix-ui/react-one-time-password-field": "0.1.16", "@radix-ui/react-password-toggle-field": "0.1.11", "@radix-ui/react-popover": "1.1.23", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-progress": "1.1.16", "@radix-ui/react-radio-group": "1.4.7", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-scroll-area": "1.2.18", "@radix-ui/react-select": "2.3.7", "@radix-ui/react-separator": "1.1.15", "@radix-ui/react-slider": "1.4.7", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-switch": "1.3.7", "@radix-ui/react-tabs": "1.1.21", "@radix-ui/react-toast": "1.2.23", "@radix-ui/react-toggle": "1.1.18", "@radix-ui/react-toggle-group": "1.1.19", "@radix-ui/react-toolbar": "1.1.19", "@radix-ui/react-tooltip": "1.2.16", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-effect-event": "0.0.5", "@radix-ui/react-use-escape-keydown": "1.1.5", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-size": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA=="], @@ -824,6 +848,10 @@ "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + "web-vitals": ["web-vitals@5.3.0", "", {}, "sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g=="], + + "web-vitals-soft-navs": ["web-vitals@6.0.0", "", {}, "sha512-Guaibvy/+uNtL6Bsu4jmMJGzuSl91oeRH5iO9pPRbYftnFUr3yqT1TUNX/OE4o9HexuEMU3Kb/Wg7iKhlffZUA=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], diff --git a/docs/vercel.md b/docs/vercel.md index 040503b..c3f23c8 100644 --- a/docs/vercel.md +++ b/docs/vercel.md @@ -16,6 +16,12 @@ Atet's public site deploys from `apps/web` in the existing Hraness Vercel projec The remote `preview` branch name is provider authority. Do not delete, rename, repurpose, or force-push the branch without a deliberate provider migration. Do not fast-forward it as branch cleanup. Advancing `preview` is a staging release and requires an explicit content decision plus provider verification. +## Production analytics environment + +Set `NEXT_PUBLIC_POSTHOG_KEY` only in Vercel's Production environment. It is the public client token for shared PostHog project `543691`, not a personal API key. `NEXT_PUBLIC_POSTHOG_HOST` may be omitted; when present it must equal `https://us.i.posthog.com`. + +The build emits no analytics asset when the token is missing or `VERCEL_ENV` is not `production`. The bundled client also checks for the exact `https://atet.sh/` page before it initializes or sends an event, so stable staging, pull-request previews, predecessor hosts, and `404.html` remain inert. Keep PostHog's cookieless server hash mode enabled for the shared project so cookieless pageviews receive privacy-preserving visitor identity. + Production, stable preview, and pull-request previews are separate: - `main` supplies Production and the production domains. diff --git a/package.json b/package.json index f2fcdf0..6245caf 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "motion": "12.42.0", "@paper-design/shaders": "0.0.77", "playwright-core": "1.62.0", + "posthog-js": "1.413.2", "react": "19.2.3", "react-dom": "19.2.3", "three": "0.183.2", From 02207120e8780593e7c893ffa74e51e6757be217 Mon Sep 17 00:00:00 2001 From: 0thernet Date: Wed, 19 Aug 2026 19:33:57 -0400 Subject: [PATCH 2/2] Make Atet web installs self-contained --- apps/web/AGENTS.md | 1 + apps/web/README.md | 7 ++++--- apps/web/bun.lock | 37 +++++++++++++++++++++++++++++++++++++ apps/web/package.json | 2 +- apps/web/site.test.ts | 9 ++++++++- bun.lock | 3 +-- package.json | 1 - 7 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 apps/web/bun.lock diff --git a/apps/web/AGENTS.md b/apps/web/AGENTS.md index 365f2d7..93fc899 100644 --- a/apps/web/AGENTS.md +++ b/apps/web/AGENTS.md @@ -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. +- 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. - Preserve the four public output families: images, diagrams, animated loops, and video. Treat audio and captions as composable project inputs rather than another project model. diff --git a/apps/web/README.md b/apps/web/README.md index 031f5f9..3a78760 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -16,6 +16,7 @@ build also bundles the pinned PostHog client as a fingerprinted local asset. That client sends one anonymous cookieless pageview from `https://atet.sh/` and does not run on Preview, alternate hosts, or the not-found page. Configure the Vercel project with this directory as its Root Directory. The checked -`vercel.json` installs from the frozen Bun lockfile, serves only built files -under a strict CSP, and sends reviewed predecessor hosts to their matching Atet -production or preview host. +`vercel.json` installs from this directory's frozen Bun lockfile without relying +on the parent workspace catalog, serves only built files under a strict CSP, +and sends reviewed predecessor hosts to their matching Atet production or +preview host. diff --git a/apps/web/bun.lock b/apps/web/bun.lock new file mode 100644 index 0000000..6ae02c8 --- /dev/null +++ b/apps/web/bun.lock @@ -0,0 +1,37 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@hraness/atet-web", + "dependencies": { + "posthog-js": "1.413.2", + }, + }, + }, + "packages": { + "@posthog/browser-common": ["@posthog/browser-common@0.4.0", "", { "dependencies": { "@posthog/core": "^1.46.8", "@posthog/types": "^1.402.0" } }, "sha512-W9DCGVks15docUMPvJ2nd8NS16Gn74bsGWuaeg31beEKFSjdW8wvnQ1ETY6WSql5pYxZb3GdJmEUZVVstKSrBQ=="], + + "@posthog/core": ["@posthog/core@1.48.5", "", { "dependencies": { "@posthog/types": "^1.405.0" } }, "sha512-ZE27PmVyPmw9wZOVVyn64uaTdqhopuwQfX7PzBpyCvsezuvNmrUHAMpJKkqOvaHxu7HeJt1jTQtjtTuwy+S7Og=="], + + "@posthog/types": ["@posthog/types@1.405.0", "", {}, "sha512-4rZ/taVXKQxs9Jrf7ZjlCRgrOSL69oKAgIWJQa5kRNJ6wll1UANbrJTSY+Su1e88LIG4zZVjKKKjyQHCkHdHcw=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + + "core-js": ["core-js@3.50.0", "", {}, "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw=="], + + "dompurify": ["dompurify@3.4.14", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg=="], + + "fflate": ["fflate@0.4.9", "", {}, "sha512-zdxgIEddhfsyCaWpJ2SdXEP8ZMrKJ6+5jl4OupODcywU0IhRk6gdXuVGcPICyfx2H97hVK7xmJtRLPjkxAX8Vw=="], + + "posthog-js": ["posthog-js@1.413.2", "", { "dependencies": { "@posthog/browser-common": "^0.4.0", "@posthog/core": "^1.46.8", "@posthog/types": "^1.402.1", "core-js": "^3.49.0", "dompurify": "^3.4.12", "fflate": "^0.4.8", "preact": "^10.29.3", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^5.3.0", "web-vitals-soft-navs": "npm:web-vitals@6.0.0" } }, "sha512-LddTVMdYFd0jB+p5h3ZHcho9mWNjgKn9QySCQt74iISKrmC3b7KzW2Q7W7aOF6m/NwK7NubIHqLujCER64gSRw=="], + + "preact": ["preact@10.29.8", "", { "peerDependencies": { "preact-render-to-string": ">=5" }, "optionalPeers": ["preact-render-to-string"] }, "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q=="], + + "query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="], + + "web-vitals": ["web-vitals@5.3.0", "", {}, "sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g=="], + + "web-vitals-soft-navs": ["web-vitals@6.0.0", "", {}, "sha512-Guaibvy/+uNtL6Bsu4jmMJGzuSl91oeRH5iO9pPRbYftnFUr3yqT1TUNX/OE4o9HexuEMU3Kb/Wg7iKhlffZUA=="], + } +} diff --git a/apps/web/package.json b/apps/web/package.json index 0ed5e62..b0fc47f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -6,7 +6,7 @@ "type": "module", "packageManager": "bun@1.3.14", "dependencies": { - "posthog-js": "catalog:" + "posthog-js": "1.413.2" }, "scripts": { "build": "bun run ./scripts/build.ts", diff --git a/apps/web/site.test.ts b/apps/web/site.test.ts index f893e7b..07b3f2d 100644 --- a/apps/web/site.test.ts +++ b/apps/web/site.test.ts @@ -339,9 +339,16 @@ describe("static Atet site", () => { const manifest = JSON.parse( await readFile(join(appDirectory, "package.json"), "utf8"), ) as { dependencies?: Record; devDependencies?: unknown } + const rootManifest = JSON.parse( + await readFile(join(repositoryDirectory, "package.json"), "utf8"), + ) as { workspaces?: { catalog?: Record } } + const localLockfile = await readFile(join(appDirectory, "bun.lock"), "utf8") - expect(manifest.dependencies).toEqual({ "posthog-js": "catalog:" }) + expect(manifest.dependencies).toEqual({ "posthog-js": "1.413.2" }) expect(manifest.devDependencies).toBeUndefined() + expect(rootManifest.workspaces?.catalog?.["posthog-js"]).toBeUndefined() + 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) diff --git a/bun.lock b/bun.lock index d78a698..28c4790 100644 --- a/bun.lock +++ b/bun.lock @@ -46,7 +46,7 @@ "name": "@hraness/atet-web", "version": "0.0.0", "dependencies": { - "posthog-js": "catalog:", + "posthog-js": "1.413.2", }, }, "packages/scene": { @@ -75,7 +75,6 @@ "fast-check": "^4.8.0", "motion": "12.42.0", "playwright-core": "1.62.0", - "posthog-js": "1.413.2", "react": "19.2.3", "react-dom": "19.2.3", "three": "0.183.2", diff --git a/package.json b/package.json index 6245caf..f2fcdf0 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "motion": "12.42.0", "@paper-design/shaders": "0.0.77", "playwright-core": "1.62.0", - "posthog-js": "1.413.2", "react": "19.2.3", "react-dom": "19.2.3", "three": "0.183.2",