diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 7b784a3213..47652d17b0 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -56,6 +56,18 @@ pnpm build-storybook These run the `@paperclipai/ui` Storybook on port `6006` and build the static output to `ui/storybook-static/`. +## UI Fonts And Screenshots + +The board UI ships its own sans-serif webfont assets in `ui/public/fonts/`. +`ui/src/index.css` declares Inter v4.1 variable regular and italic faces and wires +the Tailwind `font-sans` token to those bundled files before system fallbacks. +Linux screenshot or Storybook capture jobs should not install host Inter packages +or inject external font CSS to make Paperclip text render correctly. + +Font assets live in Vite's public directory so `pnpm --filter @paperclipai/ui build` +emits them under `ui/dist/fonts/`. The server package copies the same output into +`server/ui-dist/fonts/` through `scripts/prepare-server-ui-dist.sh`. + Inspect or stop the current repo's managed dev runner: ```sh diff --git a/doc/spec/ui.md b/doc/spec/ui.md index c2ffdb7c02..2946b706c6 100644 --- a/doc/spec/ui.md +++ b/doc/spec/ui.md @@ -40,7 +40,8 @@ Priority indicators: ### Typography -- **Font:** System font stack (Inter if loaded, else `-apple-system, BlinkMacSystemFont, 'Segoe UI'`) +- **Font:** Bundled Inter v4.1 variable WOFF2 for sans text, loaded from `/fonts/InterVariable.woff2` and `/fonts/InterVariable-Italic.woff2`, with `Inter`, `ui-sans-serif`, `system-ui`, `-apple-system`, `BlinkMacSystemFont`, and `'Segoe UI'` fallbacks. +- **Mono:** System monospace stack via the `font-mono` token (`ui-monospace`, `SFMono-Regular`, `Menlo`, `Monaco`, `Consolas`, `Liberation Mono`, `Courier New`, `monospace`). - **Body:** 13px / 1.5 line-height - **Labels/metadata:** 11px / uppercase tracking - **Headings:** 14-18px / semi-bold, never all-caps diff --git a/ui/public/fonts/InterVariable-Italic.woff2 b/ui/public/fonts/InterVariable-Italic.woff2 new file mode 100644 index 0000000000..b3530f3f52 Binary files /dev/null and b/ui/public/fonts/InterVariable-Italic.woff2 differ diff --git a/ui/public/fonts/InterVariable.woff2 b/ui/public/fonts/InterVariable.woff2 new file mode 100644 index 0000000000..5a8d3e72ad Binary files /dev/null and b/ui/public/fonts/InterVariable.woff2 differ diff --git a/ui/public/fonts/NOTICE.md b/ui/public/fonts/NOTICE.md new file mode 100644 index 0000000000..699d3d0e35 --- /dev/null +++ b/ui/public/fonts/NOTICE.md @@ -0,0 +1,17 @@ +# Bundled UI Fonts + +Paperclip bundles Inter for the board UI so screenshots and packaged installs use +the same sans-serif text stack without relying on host font packages. + +## Inter + +- Upstream project: https://github.com/rsms/inter +- Version: v4.1 +- Source files: + - https://raw.githubusercontent.com/rsms/inter/v4.1/docs/font-files/InterVariable.woff2 + - https://raw.githubusercontent.com/rsms/inter/v4.1/docs/font-files/InterVariable-Italic.woff2 +- License: SIL Open Font License 1.1 +- License text: https://github.com/rsms/inter/blob/v4.1/LICENSE.txt + +Redistribution note: Inter is redistributed under the SIL Open Font License 1.1. +The bundled WOFF2 files are included unmodified from the upstream v4.1 release. diff --git a/ui/src/index.css b/ui/src/index.css index f86dd53ba9..0e5e2cf6ff 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -1,9 +1,27 @@ @import "tailwindcss"; @plugin "@tailwindcss/typography"; +@font-face { + font-family: "InterVariable"; + src: url("../fonts/InterVariable.woff2") format("woff2"); + font-display: swap; + font-style: normal; + font-weight: 100 900; +} + +@font-face { + font-family: "InterVariable"; + src: url("../fonts/InterVariable-Italic.woff2") format("woff2"); + font-display: swap; + font-style: italic; + font-weight: 100 900; +} + @custom-variant dark (&:is(.dark *)); @theme inline { + --font-sans: "InterVariable", "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); @@ -247,6 +265,7 @@ } body { @apply bg-background text-foreground antialiased; + font-family: var(--font-sans); height: 100%; overflow: hidden; } @@ -851,7 +870,7 @@ a.paperclip-mention-chip[data-mention-kind="agent"]::before { } .paperclip-mdxeditor-content code { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-family: var(--font-mono); font-size: 1em; } @@ -956,7 +975,7 @@ a.paperclip-mention-chip[data-mention-kind="agent"]::before { } .paperclip-markdown code { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-family: var(--font-mono); font-size: 1em; } diff --git a/ui/src/lib/ui-font-assets.test.ts b/ui/src/lib/ui-font-assets.test.ts new file mode 100644 index 0000000000..f2ea367a87 --- /dev/null +++ b/ui/src/lib/ui-font-assets.test.ts @@ -0,0 +1,36 @@ +import { existsSync, readFileSync, statSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const uiRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const fontDir = path.join(uiRoot, "public", "fonts"); +const cssPath = path.join(uiRoot, "src", "index.css"); + +const fontFiles = ["InterVariable.woff2", "InterVariable-Italic.woff2"]; + +describe("bundled UI font assets", () => { + it("ships the Inter variable font files used by the CSS", () => { + const css = readFileSync(cssPath, "utf8"); + + for (const fileName of fontFiles) { + const fontPath = path.join(fontDir, fileName); + expect(existsSync(fontPath), `${fileName} should exist in ui/public/fonts`).toBe(true); + expect(statSync(fontPath).isFile(), `${fileName} should be a file`).toBe(true); + expect(readFileSync(fontPath).subarray(0, 4).toString("ascii")).toBe("wOF2"); + expect(css).toContain(`url("../fonts/${fileName}")`); + } + + expect(css).toContain('--font-sans: "InterVariable"'); + }); + + it("includes redistribution notice text for the bundled Inter files", () => { + const notice = readFileSync(path.join(fontDir, "NOTICE.md"), "utf8"); + + expect(notice).toContain("Inter"); + expect(notice).toContain("v4.1"); + expect(notice).toContain("SIL Open Font License 1.1"); + expect(notice).toContain("InterVariable.woff2"); + expect(notice).toContain("InterVariable-Italic.woff2"); + }); +});