diff --git a/README.md b/README.md index 7a887cb..2f44a56 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,13 @@

- Website · Docs · Examples + npm + CI + MIT licence +

+ +

+ Website · Docs · Examples

Ask your coding agent to diagram a codebase, a system, a process or a request. It writes a small typed JSON, stackmap validates it with repair hints the agent acts on, lays it out, and delivers one self-contained HTML viewer. No server, no account, nothing to install to open it. @@ -83,7 +89,7 @@ The agent writes `.stackmap//diagram.json`, validates and repairs it, and - **Edges** can be `async` or a `return`, with a `tone` for the main path, security crossings and failure paths. - **No coordinates:** stackmap lays everything out, with [ELK](https://eclipse.dev/elk/) for architecture and dataflow and its own layout for lanes and sequences. -Full reference: [skill/references/schema.md](skill/references/schema.md), or the [schema reference](https://stackmap-site.vercel.app/docs/schema) on the website. Modelling guidance: [authoring contract](skill/references/authoring-contract.md). +Full reference: [skill/references/schema.md](skill/references/schema.md), or the [schema reference](https://stackmap.omsimos.com/docs/schema) on the website. Modelling guidance: [authoring contract](skill/references/authoring-contract.md). ## The CLI diff --git a/packages/cli/package.json b/packages/cli/package.json index 91fce8a..d41ff12 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -13,7 +13,7 @@ "html", "dataflow" ], - "homepage": "https://github.com/hyamero/stackmap#readme", + "homepage": "https://stackmap.omsimos.com", "repository": { "type": "git", "url": "git+https://github.com/hyamero/stackmap.git", diff --git a/site/components/landing/How.tsx b/site/components/landing/How.tsx index b27f441..e745a33 100644 --- a/site/components/landing/How.tsx +++ b/site/components/landing/How.tsx @@ -113,7 +113,10 @@ export function How({ demo, receipt, repair }: { demo: LaidOutDiagram; receipt: }; layout(); setPinned(true); - setStep(0); + const draw = (progress: number) => { + if (fill.current) fill.current.style.transform = `scaleX(${progress})`; + setStep(Math.min(3, Math.floor(progress * 4))); + }; // CSS sticky does the pin; ScrollTrigger only reads how far through the runway the page is. const st = ScrollTrigger.create({ trigger: p, @@ -121,11 +124,10 @@ export function How({ demo, receipt, repair }: { demo: LaidOutDiagram; receipt: end: () => `+=${runway.current}`, invalidateOnRefresh: true, onRefreshInit: layout, - onUpdate: ({ progress }) => { - if (fill.current) fill.current.style.transform = `scaleX(${progress})`; - setStep(Math.min(3, Math.floor(progress * 4))); - }, + onUpdate: ({ progress }) => draw(progress), }); + // onUpdate waits for a scroll; until then the rail would keep its resting (full) fill. + draw(st.progress); return () => { st.kill(); p.style.height = ''; diff --git a/site/e2e/smoke.spec.ts b/site/e2e/smoke.spec.ts index f2105a9..a7d4f6a 100644 --- a/site/e2e/smoke.spec.ts +++ b/site/e2e/smoke.spec.ts @@ -68,6 +68,8 @@ test('with motion allowed the walkthrough pins and the scroll picks its step', a await page.goto('/'); const pin = page.locator('.how-pin'); await expect(pin).toHaveAttribute('data-live', 'true'); + // Before any scroll the rail is unlit, as step 1 is. + await expect.poll(() => page.locator('.hfill').evaluate((el) => getComputedStyle(el).transform)).toBe('matrix(0, 0, 0, 1, 0, 0)'); const at = (f: number) => page.evaluate((f) => { const p = document.querySelector('.how-pin')!; @@ -191,9 +193,12 @@ test('the old kind pages redirect into the docs', async ({ page }) => { test('the docs search opens with ⌘K, finds a field and goes to it', async ({ page }) => { await page.goto('/docs'); - await page.keyboard.press('ControlOrMeta+k'); const box = page.getByRole('combobox', { name: 'Search the docs' }); - await expect(box).toBeFocused(); + // The shortcut is bound once the page hydrates; a press before that lands nowhere. + await expect(async () => { + await page.keyboard.press('ControlOrMeta+k'); + await expect(box).toBeFocused({ timeout: 500 }); + }).toPass(); await box.fill('statsNote'); await expect(page.getByRole('option').first()).toContainText('statsNote'); await page.keyboard.press('Enter'); diff --git a/site/lib/seo.test.ts b/site/lib/seo.test.ts index c1c7213..2a4d23e 100644 --- a/site/lib/seo.test.ts +++ b/site/lib/seo.test.ts @@ -20,20 +20,16 @@ function pageRoutes(dir = join(site, 'app')): string[] { } describe('siteUrl', () => { - it('uses the production domain Vercel reports', () => { - expect(siteUrl({ VERCEL_PROJECT_PRODUCTION_URL: 'stackmap.dev' }).href).toBe('https://stackmap.dev/'); - }); - - it('falls back to the project domain outside Vercel', () => { - expect(siteUrl({}).href).toBe('https://stackmap-site-hyameros-projects.vercel.app/'); + it('is the custom domain, never a vercel.app one', () => { + expect(siteUrl().href).toBe('https://stackmap.omsimos.com/'); }); }); describe('robots', () => { it('lets crawlers in on production and points them at the sitemap', () => { - const r = robotsFor({ VERCEL_ENV: 'production', VERCEL_PROJECT_PRODUCTION_URL: 'stackmap.dev' }); + const r = robotsFor({ VERCEL_ENV: 'production', VERCEL_PROJECT_PRODUCTION_URL: 'stackmap-site.vercel.app' }); expect(r.rules).toEqual({ userAgent: '*', allow: '/' }); - expect(r.sitemap).toBe('https://stackmap.dev/sitemap.xml'); + expect(r.sitemap).toBe('https://stackmap.omsimos.com/sitemap.xml'); }); it('keeps previews and local builds out of the index', () => { diff --git a/site/lib/seo.ts b/site/lib/seo.ts index cc7f7c5..602f662 100644 --- a/site/lib/seo.ts +++ b/site/lib/seo.ts @@ -21,15 +21,14 @@ export const OPEN_GRAPH = { /** Every static page; the sitemap test fails when a page.tsx is added without its route here. */ export const ROUTES = ['/', '/docs', '/docs/viewer', '/docs/cli', '/docs/schema', '/docs/brands', '/examples'] as const; -// Vercel sets the production domain on every deployment, previews included, so canonical and OG URLs -// never point at a preview. It follows a custom domain once one is attached. -export function siteUrl(env: Env = process.env): URL { - return new URL(`https://${env.VERCEL_PROJECT_PRODUCTION_URL ?? 'stackmap-site-hyameros-projects.vercel.app'}`); -} +// Pinned rather than read from VERCEL_PROJECT_PRODUCTION_URL: Vercel reports the project's vercel.app domain there +// even with the custom domain attached, and canonical, OG and sitemap URLs must all name the one real address. +export const SITE_URL = 'https://stackmap.omsimos.com'; +export const siteUrl = (): URL => new URL(SITE_URL); export function robotsFor(env: Env = process.env): MetadataRoute.Robots { if (env.VERCEL_ENV !== 'production') return { rules: { userAgent: '*', disallow: '/' } }; - return { rules: { userAgent: '*', allow: '/' }, sitemap: new URL('/sitemap.xml', siteUrl(env)).href }; + return { rules: { userAgent: '*', allow: '/' }, sitemap: new URL('/sitemap.xml', siteUrl()).href }; } export function sitemapFor(base: URL = siteUrl()): MetadataRoute.Sitemap {