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
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
</p>

<p align="center">
<a href="https://stackmap-site.vercel.app">Website</a> · <a href="https://stackmap-site.vercel.app/docs">Docs</a> · <a href="https://stackmap-site.vercel.app/examples">Examples</a>
<a href="https://www.npmjs.com/package/@hyamero/stackmap"><img alt="npm" src="https://img.shields.io/npm/v/@hyamero/stackmap?color=cb3837&amp;logo=npm"></a>
<a href="https://github.com/hyamero/stackmap/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/hyamero/stackmap/actions/workflows/ci.yml/badge.svg?branch=main"></a>
<a href="LICENSE"><img alt="MIT licence" src="https://img.shields.io/github/license/hyamero/stackmap"></a>
</p>

<p align="center">
<a href="https://stackmap.omsimos.com">Website</a> · <a href="https://stackmap.omsimos.com/docs">Docs</a> · <a href="https://stackmap.omsimos.com/examples">Examples</a>
</p>

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.
Expand Down Expand Up @@ -83,7 +89,7 @@ The agent writes `.stackmap/<name>/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

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 7 additions & 5 deletions site/components/landing/How.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,21 @@ 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,
start: () => `top top+=${stickTop.current}`,
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 = '';
Expand Down
9 changes: 7 additions & 2 deletions site/e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLElement>('.how-pin')!;
Expand Down Expand Up @@ -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');
Expand Down
12 changes: 4 additions & 8 deletions site/lib/seo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
11 changes: 5 additions & 6 deletions site/lib/seo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading