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 .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @smiggleworth
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

- name: Setup Node
uses: actions/setup-node@v7
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with:
node-version: 'lts/*'
cache: npm
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

- name: Setup Node
uses: actions/setup-node@v7
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with:
node-version: 'lts/*'
cache: npm
cache-dependency-path: package-lock.json

- name: Configure GitHub Pages
uses: actions/configure-pages@v6
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d

- name: Install dependencies
run: npm ci
Expand All @@ -49,10 +49,10 @@ jobs:
run: npm test

- name: Upload artifact
uses: actions/upload-pages-artifact@v5
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9
with:
path: ./dist

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128
36 changes: 26 additions & 10 deletions scripts/generate-api-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type PackageLock = {
type PackageManifest = {
version: string;
exports?: Record<string, unknown>;
peerDependencies?: Record<string, string>;
};

type Entrypoint = {
Expand Down Expand Up @@ -42,16 +43,30 @@ const packageNames = installedPackageNames.filter(
(name) => name !== '@askrjs/cli'
);

const installedManifests = installedPackageNames.map((packageName) => {
const manifest = JSON.parse(
readFileSync(
resolve(root, 'node_modules', packageName, 'package.json'),
'utf8'
)
) as PackageManifest;
return [packageName.slice('@askrjs/'.length), manifest] as const;
});

const packageVersions = Object.fromEntries(
installedPackageNames.map((packageName) => {
const manifest = JSON.parse(
readFileSync(
resolve(root, 'node_modules', packageName, 'package.json'),
'utf8'
)
) as PackageManifest;
return [packageName.slice('@askrjs/'.length), manifest.version];
})
installedManifests.map(([name, manifest]) => [name, manifest.version])
);

// Which @askrjs packages each package requires you to already have.
// Drives the platform page's dependency table.
const packagePeers = Object.fromEntries(
installedManifests.map(([name, manifest]) => [
name,
Object.keys(manifest.peerDependencies ?? {})
.filter((peer) => peer.startsWith('@askrjs/'))
.map((peer) => peer.slice('@askrjs/'.length))
.sort(),
])
);

function exportedTypesTarget(value: unknown): string | undefined {
Expand Down Expand Up @@ -173,7 +188,8 @@ const unformattedSnapshotSource =
`export const apiSymbolSets = ${JSON.stringify(symbolSets, null, 2)} as const;\n`;
const unformattedVersionsSource =
`// Generated from installed @askrjs package manifests. Do not edit.\n` +
`export const packageVersions = ${JSON.stringify(packageVersions, null, 2)} as const;\n`;
`export const packageVersions = ${JSON.stringify(packageVersions, null, 2)} as const;\n\n` +
`export const packagePeers = ${JSON.stringify(packagePeers, null, 2)} as const;\n`;
const manifestSource = formatGeneratedTypeScript(
manifestPath,
unformattedManifestSource
Expand Down
4 changes: 2 additions & 2 deletions scripts/verify-static-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,9 @@ for (const expectation of expectations) {
`${expectation.route} must include a directly adaptable code example`
);
assert(
html.includes('How to use') ||
html.includes('id="how-to-use"') ||
expectation.route.endsWith('/lucide-gallery'),
`${expectation.route} must explain how to use the documented surface`
`${expectation.route} must show a worked example of the documented surface`
);
}
}
Expand Down
53 changes: 46 additions & 7 deletions src/pages/docs/catalog.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { apiManifest } from './api-manifest';
import { headingOverrides } from './content-overrides';
import {
descriptionOverrides,
headingOverrides,
lateHeadingOverrides,
} from './content-overrides';
import { docsPrimarySections } from './primary-sections';
import { packageVersions } from './package-versions';
import { buildUsageGuide, routeExampleFor } from './usage-guide';
import type {
DocsHeadingDefinition,
DocsPageDefinition,
Expand Down Expand Up @@ -76,6 +81,36 @@ function guidance(group: string, title: string, heading: string): string {
return `Implement ${heading.toLowerCase()} at the ${subject} boundary: make inputs visible at the call site, keep cleanup with the work that created it, and render the success, unavailable, and failure states where a user can act on them.`;
}

/**
* Per-section meta description for pages without a hand-written one in
* descriptionOverrides. Each section gets copy describing what that kind of
* page actually covers, rather than one template stretched across all of them.
*/
function sectionDescription(group: string, title: string): string {
switch (group) {
case 'UI & Components':
return `${title}: anatomy, keyboard behavior, state, and theming in Askr.`;
case 'Server & APIs':
return `${title} at the Askr server boundary, with validated input and explicit failure states.`;
case 'Guides':
return `${title}: a worked guide from route registry through to a production build.`;
case 'Reference':
return `${title}: published entrypoints, signatures, and the constraints around them.`;
case 'Routing & Data':
return `${title} in the Askr router: typed routes, lifecycle-bound data, and cancellation.`;
case 'Fundamentals':
return `${title} in the Askr runtime: what owns it, when it runs, and how it renders deterministically.`;
case 'Rendering':
return `${title}: how and when Askr turns a route tree into HTML.`;
case 'Tooling':
return `${title} with the Askr CLI — generated files you review, and checks that catch drift.`;
case 'Getting Started':
return `${title}: install Askr, scaffold a project, and get a first route rendering.`;
default:
return `${title}, built on the current published Askr packages.`;
}
}

function heading(
group: string,
pageTitle: string,
Expand All @@ -84,7 +119,9 @@ function heading(
) {
if (typeof value !== 'string') return value;
const id = slug(value);
const override = route ? headingOverrides[route]?.[id] : undefined;
const override = route
? (headingOverrides[route]?.[id] ?? lateHeadingOverrides[route]?.[id])
: undefined;
return {
id,
title: value,
Expand All @@ -111,7 +148,8 @@ function definePage(
title: input.title,
description:
input.description ??
`Use ${input.title.toLowerCase()} with the current published Askr packages.`,
descriptionOverrides[route] ??
sectionDescription(group, input.title),
navGroup: group,
navSection: section,
status: input.status ?? 'stable',
Expand Down Expand Up @@ -1568,11 +1606,12 @@ export function docsTableOfContents(
return page.headings;
}

// The example section only renders when there is a page-specific example,
// so the table of contents has to agree.
const headings: Pick<DocsHeadingDefinition, 'id' | 'title'>[] = [
{
id: 'how-to-use',
title: `How to use ${page.title.toLowerCase()}`,
},
...(buildUsageGuide(page, routeExampleFor(page.route))
? [{ id: 'how-to-use', title: 'Example' }]
: []),
...page.headings,
];
if (page.route === '/docs') {
Expand Down
Loading