Skip to content
Closed
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
18 changes: 10 additions & 8 deletions apps/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,18 @@ path from the built artifact. It does not run Next.js or a search backend.

Edit MDX in `content/docs/` and sidebar order in each `meta.json`. The build
checks local HTML links and assets, and emits a browser-searchable index at
`search.json`. Fonts come from the installed `@bloxwap/font` package (and `@bloxwap/hig-og-fonts` for social cards), so builds do not fetch Google Fonts.
`search.json`. Fonts come from the installed `@bloxwap/font` package (and `@bloxwap/hig/og-fonts` for social cards), so builds do not fetch Google Fonts.

The presentation follows the Bloxwap HIG and consumes it as packages, not copies:

- `@bloxwap/hig-tokens`, `@bloxwap/hig-contracts` and `@bloxwap/hig-react` (plus the build-time
`@bloxwap/hig-og-fonts`) are pinned to published npm version `0.1.0`; the lockfile and [release record](../../docs/hig-adoption/registry-release.json) retain their registry integrity hashes.
`app/global.css` imports `@bloxwap/hig-tokens/css` and `@bloxwap/hig-react/css` and maps HIG roles
onto Fumadocs; it does not declare palette values.
- `@bloxwap/hig` is pinned to published npm version `0.2.0`. The 0.1 split packages
(`@bloxwap/hig-tokens`, `@bloxwap/hig-contracts`, `@bloxwap/hig-react`, `@bloxwap/hig-og-fonts`)
404 on npm; tokens, contracts, React and OG fonts are subpath exports of the one package.
The lockfiles and [release record](../../docs/hig-adoption/registry-release.json) retain the registry integrity hash.
`app/global.css` imports `@bloxwap/hig/tokens/css` and `@bloxwap/hig/react/css` and maps HIG roles
onto Fumadocs; it does not declare palette values. `next.config.mjs` lists `@bloxwap/hig` in `transpilePackages`.
- Radius, type, motion and density roles (`--hig-radius-*`, `--hig-type-*`, `--hig-motion-*`,
`--hig-density-*`) and the font roles come from `@bloxwap/hig-tokens/css`; the docs declare none.
`--hig-density-*`) and the font roles come from `@bloxwap/hig/tokens/css`; the docs declare none.
`npm run lint` (`scripts/hig-tokens.mjs --check`) fails on registry version or integrity changes, local role declarations
or retired role names, copied palettes, `.btn`-style primitive copies or font binaries in the docs source.
- Buttons, install tabs/copy, code-window copy, callouts, search error/empty states and the playground
Expand Down Expand Up @@ -94,8 +96,8 @@ chrome's `--cts-font`/`--cts-mono` at Bloxwap Sans/Mono, and sets the canvas
`theme.fontFamily` to Bloxwap Sans and `monoFamily`/`scaleFontFamily` (status
line, drawing labels and scales) to Bloxwap Mono. The standalone playground and
SDK presets retain their existing font defaults.
Social cards use the static Bloxwap Sans faces shipped in `@bloxwap/hig-og-fonts/fonts` (OFL), a
build-time devDependency; Satori cannot read the variable WOFF2 in `@bloxwap/font`.
Social cards use the static Bloxwap Sans faces shipped in `@bloxwap/hig/og-fonts/fonts` (OFL);
Satori cannot read the variable WOFF2 in `@bloxwap/font`. The Node loader is `@bloxwap/hig/og-fonts/node`.

HIG presentation, registry integrity, validation, exceptions and upgrade/rollback
instructions are recorded in [the adoption report](../../docs/hig-adoption/README.md).
8 changes: 4 additions & 4 deletions apps/docs/app/global.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
@import '@bloxwap/hig-tokens/css';
@import '@bloxwap/hig-react/css/base';
@import '@bloxwap/hig-react/css';
@import '@bloxwap/hig/tokens/css';
@import '@bloxwap/hig/react/css/base';
@import '@bloxwap/hig/react/css';

@theme inline {
--font-sans: var(--font-bloxwap-sans), ui-rounded, system-ui, sans-serif;
Expand Down Expand Up @@ -151,7 +151,7 @@ html > body[data-scroll-locked] {
}

/*
* HIG controls (@bloxwap/hig-react/css) in the docs shell. HIG reads --hig-font-sans/--hig-font-mono, which
* HIG controls (@bloxwap/hig/react/css) in the docs shell. HIG reads --hig-font-sans/--hig-font-mono, which
* resolve to the @bloxwap/font Next loader variables, so no family bridge is needed. Links and icons share the
* button. Dense docs regions opt into HIG compact density on fine pointers; coarse pointers keep 48px targets.
*/
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components/callout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactNode } from 'react';
import { Callout as HigCallout } from '@bloxwap/hig-react/status';
import { Callout as HigCallout } from '@bloxwap/hig/react/status';

type FumadocsType = 'info' | 'note' | 'idea' | 'success' | 'warn' | 'warning' | 'error';
const severity = { info: 'info', note: 'note', idea: 'tip', success: 'success', warn: 'warning', warning: 'warning', error: 'error' } as const;
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/components/chart-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import { useEffect, useRef, useState, type ReactNode } from 'react';
import { ArrowUpRight, Maximize2, Minimize2 } from 'lucide-react';
import { Button } from '@bloxwap/hig-react/button';
import { Spinner } from '@bloxwap/hig-react/status';
import { Button } from '@bloxwap/hig/react/button';
import { Spinner } from '@bloxwap/hig/react/status';
import { assetUrl } from '@/lib/site';

/**
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components/copy-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useEffect, useState } from 'react';
import { Check, Copy } from 'lucide-react';
import { IconButton } from '@bloxwap/hig-react/button';
import { IconButton } from '@bloxwap/hig/react/button';
import { useClipboard } from '@/lib/clipboard';

/** HIG icon button that copies `text`, with a check mark, a screen-reader status and visible failure recovery. */
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components/hero-code.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import type { ReactNode } from 'react';
import { CodeWindow } from '@bloxwap/hig-react/recipes/code-window';
import { CodeWindow } from '@bloxwap/hig/react/recipes/code-window';
import { CopyButton } from './copy-button';

/*
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/components/install-command.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { Tabs } from '@bloxwap/hig-react/navigation';
import { InstallCommand as HigInstallCommand } from '@bloxwap/hig-react/recipes/install-command';
import { Tabs } from '@bloxwap/hig/react/navigation';
import { InstallCommand as HigInstallCommand } from '@bloxwap/hig/react/recipes/install-command';
import { useClipboard } from '@/lib/clipboard';

const managers = [
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay,
type SharedProps,
} from 'fumadocs-ui/components/dialog/search';
import { Alert, EmptyState } from '@bloxwap/hig-react/status';
import { Alert, EmptyState } from '@bloxwap/hig/react/status';
import { assetUrl } from '@/lib/site';

function NoResults() {
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/lib/og-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readOgFont } from '@bloxwap/hig-og-fonts/node';
import { tokens as hig } from '@bloxwap/hig-tokens';
import { readOgFont } from '@bloxwap/hig/og-fonts/node';
import { tokens as hig } from '@bloxwap/hig/tokens';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { ImageResponse } from 'next/og';
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export default withMDX({
reactStrictMode: true,
// Resolve both this app and the local chart package from the workspace root.
turbopack: { root: new URL('../..', import.meta.url).pathname },
transpilePackages: ['@bloxwap/chart'],
// HIG 0.2 is one package. Next must transpile it (the 0.1 split names are unpublished).
transpilePackages: ['@bloxwap/chart', '@bloxwap/hig'],
// Type checking runs through tsrs in `npm run check` (prebuild), so Next skips its own tsc pass.
typescript: { ignoreBuildErrors: true },
});
5 changes: 1 addition & 4 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
"dependencies": {
"@bloxwap/chart": "0.4.0",
"@bloxwap/font": "0.1.2",
"@bloxwap/hig-contracts": "0.1.0",
"@bloxwap/hig-react": "0.1.0",
"@bloxwap/hig-tokens": "0.1.0",
"@bloxwap/hig": "0.2.0",
"fumadocs-core": "16.17.7",
"fumadocs-mdx": "15.4.9",
"fumadocs-ui": "16.17.7",
Expand All @@ -29,7 +27,6 @@
"react-dom": "^19.3.1"
},
"devDependencies": {
"@bloxwap/hig-og-fonts": "0.1.0",
"@tailwindcss/postcss": "^4.3.3",
"@types/mdx": "^2.0.14",
"@types/node": "^24.19.2",
Expand Down
44 changes: 26 additions & 18 deletions apps/docs/scripts/hig-tokens.mjs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// HIG adoption guard for the docs site. Tokens, roles, CSS and components come from published
// @bloxwap/hig-* registry packages. @bloxwap/hig-tokens/css emits the radius, type,
// HIG adoption guard for the docs site. Tokens, roles, CSS and components come from the published
// @bloxwap/hig package. @bloxwap/hig/tokens/css emits the radius, type,
// motion and density roles, so this script only derives the canvas-compatible sRGB chart theme from the
// typed manifest, and its --check fails CI when copies of HIG sources or role declarations creep back in.
import { readFile, readdir } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { createRequire } from 'node:module';
import assert from 'node:assert/strict';
import { tokens } from '@bloxwap/hig-tokens';
import { tokens } from '@bloxwap/hig/tokens';

const require = createRequire(import.meta.url);
export const manifest = tokens;
export const tokensCssPath = require.resolve('@bloxwap/hig-tokens/css');
export const tokensCssPath = require.resolve('@bloxwap/hig/tokens/css');
export const colors = Object.fromEntries(Object.entries(manifest.colors).map(([key, value]) => [key, value.srgb]));
/** CSS value of a role as a modern browser computes it from @bloxwap/hig-tokens/css. */
/** CSS value of a role as a modern browser computes it from @bloxwap/hig/tokens/css. */
export const cssColor = (role) => manifest.colors[role].oklch ?? manifest.colors[role].srgb;
export const chartTheme = {
theme: { background: colors.surface, textColor: colors.text, borderColor: colors.border, fontFamily: '"Bloxwap Sans", system-ui, sans-serif', monoFamily: '"Bloxwap Mono", monospace', scaleFontFamily: '"Bloxwap Mono", monospace' },
Expand All @@ -25,25 +25,33 @@ if (process.argv.includes('--check')) {
const release = JSON.parse(await readFile(new URL('../../../docs/hig-adoption/registry-release.json', import.meta.url), 'utf8'));
const lock = JSON.parse(await readFile(new URL('../../../package-lock.json', import.meta.url), 'utf8'));
const docsPackage = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
for (const name of ['tokens', 'contracts', 'react', 'og-fonts']) {
const packageName = `@bloxwap/hig-${name}`;
const field = name === 'og-fonts' ? 'devDependencies' : 'dependencies';
assert.equal(docsPackage[field][packageName], release.version, `${packageName} must use the coordinated registry version`);
const locked = lock.packages[`node_modules/${packageName}`];
assert.equal(locked.version, release.version, `${packageName} lockfile version drifted`);
assert.equal(locked.resolved, `https://registry.npmjs.org/${packageName}/-/hig-${name}-${release.version}.tgz`, `${packageName} must resolve from npm`);
assert.equal(locked.resolved, release.packages[packageName].resolved);
assert.equal(locked.integrity, release.packages[packageName].integrity, `${packageName} registry integrity drifted`);
assert.equal(JSON.parse(await readFile(new URL(`../../../node_modules/${packageName}/package.json`, import.meta.url), 'utf8')).version, release.version, `Installed ${packageName} is not ${release.version}; run npm ci`);
const packageName = '@bloxwap/hig';
const splitPackages = ['@bloxwap/hig-tokens', '@bloxwap/hig-contracts', '@bloxwap/hig-react', '@bloxwap/hig-og-fonts', '@bloxwap/hig-native', '@bloxwap/hig-native-fonts', '@bloxwap/hig-chart', '@bloxwap/hig-feedback'];
assert.equal(docsPackage.dependencies[packageName], release.version, `${packageName} must use the coordinated registry version`);
assert.equal(docsPackage.devDependencies?.[packageName], undefined, `${packageName} is the docs dependency, not a second devDependency`);
for (const name of splitPackages) {
assert.equal(docsPackage.dependencies?.[name], undefined, `${name} is an unpublished 0.1 package`);
assert.equal(docsPackage.devDependencies?.[name], undefined, `${name} is an unpublished 0.1 package`);
}
assert.ok(!existsSync(new URL('../styles/', import.meta.url)), 'styles/ is gone: radius/type/motion/density roles come from @bloxwap/hig-tokens/css');
const locked = lock.packages[`node_modules/${packageName}`];
assert.equal(locked.version, release.version, `${packageName} lockfile version drifted`);
assert.equal(locked.resolved, `https://registry.npmjs.org/${packageName}/-/hig-${release.version}.tgz`, `${packageName} must resolve from npm`);
assert.equal(locked.resolved, release.packages[packageName].resolved);
assert.equal(locked.integrity, release.packages[packageName].integrity, `${packageName} registry integrity drifted`);
assert.equal(JSON.parse(await readFile(new URL(`../../../node_modules/${packageName}/package.json`, import.meta.url), 'utf8')).version, release.version, `Installed ${packageName} is not ${release.version}; run bun install --frozen-lockfile`);
const splitLocked = Object.keys(lock.packages).filter((key) => splitPackages.some((name) => key === `node_modules/${name}` || key.endsWith(`/node_modules/${name}`)));
assert.deepEqual(splitLocked, [], `unpublished 0.1 HIG packages are still locked: ${splitLocked.join(', ')}`);
const chart = lock.packages['node_modules/@bloxwap/chart'];
assert.equal(chart?.link, true, '@bloxwap/chart must stay the workspace package');
assert.equal(chart.resolved, 'packages/chart');
assert.ok(!existsSync(new URL('../styles/', import.meta.url)), 'styles/ is gone: radius/type/motion/density roles come from @bloxwap/hig/tokens/css');
// No copied palettes, role declarations, token snapshots, primitive classes or font binaries in the docs shell.
const forbidden = [
[/--(hig|bloxwap)-[\w-]+\s*:\s*(#|oklch\(|rgb|color\()/, 'copied palette declaration'],
[/--hig-(radius|type|motion|density|font|duration|control)-[\w-]+\s*:/, 'local HIG role declaration (use the @bloxwap/hig-tokens/css role)'],
[/--hig-(radius|type|motion|density|font|duration|control)-[\w-]+\s*:/, 'local HIG role declaration (use the @bloxwap/hig/tokens/css role)'],
[/var\(--hig-(duration|control)-|var\(--hig-type-[a-z]+-line\)/, 'retired generated role name (use --hig-motion-duration-*, --hig-density-*-control, --hig-type-*-line-height)'],
[/vendor\/hig\/|tokens\.generated\.css|@workspace\/(tokens|ui)/, 'obsolete token snapshot import'],
[/(^|[\s"'.])btn(--[\w-]+)?\b(?![-\w])/m, 'duplicate .btn primitive (use @bloxwap/hig-react Button or .hig-button)'],
[/(^|[\s"'.])btn(--[\w-]+)?\b(?![-\w])/m, 'duplicate .btn primitive (use @bloxwap/hig/react Button or .hig-button)'],
];
async function checkSurface(dir) {
for (const entry of await readdir(dir, { withFileTypes: true })) {
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/scripts/measure-hig.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const afterLock = lockPackages(await readFile('package-lock.json', 'utf8'));
const docsDeps = (text) => Object.keys({ ...JSON.parse(text).dependencies, ...JSON.parse(text).devDependencies });
const release = JSON.parse(await readFile('docs/hig-adoption/registry-release.json', 'utf8'));
// Generated token adapters: hig.generated.css (copied snapshot, step 1), hig-roles.generated.css (step 2), none since
// @bloxwap/hig-tokens/css emits the roles itself.
// @bloxwap/hig/tokens/css emits the roles itself.
const generated = (ref) => ['hig.generated.css', 'hig-roles.generated.css'].reduce((sum, file) => { try { return sum + Buffer.byteLength(git('show', `${ref}:apps/docs/styles/${file}`)); } catch { return sum; } }, 0);

console.log(JSON.stringify({
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/scripts/prepare-demo.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ await rm(output, { recursive: true, force: true });
await mkdir(new URL('demo/', output), { recursive: true });
await Promise.all([
cp(new URL('apps/playground/index.html', repository), new URL('demo/index.html', output)),
cp(tokensCssPath, new URL('assets/hig-tokens.css', output)), // @bloxwap/hig-tokens/css, unmodified
cp(tokensCssPath, new URL('assets/hig-tokens.css', output)), // @bloxwap/hig/tokens/css, unmodified
cp(new URL('packages/chart/dist/', repository), new URL('dist/', output), { recursive: true }),
]);

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/scripts/test-hig-browser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ try {
assert.deepEqual(presentation.legacy, []);
assert.match(presentation.motion, /^0(m?s)$/);
assert.equal(presentation.transform, 'none');
record('@bloxwap/hig-tokens palette, HIG controls in Bloxwap typography, no legacy aliases, reduced motion');
record('@bloxwap/hig/tokens palette, HIG controls in Bloxwap typography, no legacy aliases, reduced motion');
await page.getByRole('tab', { name: 'npm', exact: true }).click();
await page.getByRole('tab', { name: 'bun', exact: true }).click();
if (evidence) { await mkdir(evidence, { recursive: true }); await page.screenshot({ path: `${evidence}/after-home.png`, fullPage: true }); }
Expand Down
13 changes: 2 additions & 11 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading