[codex] Bundle UI webfonts with the app (#9020)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The browser UI depends on the Inter font family for its intended visual baseline > - The app previously referenced remote Google Fonts stylesheets at runtime > - That meant self-hosted, offline, or privacy-sensitive deployments could lose the intended typography or depend on an external request > - This pull request bundles the required Inter variable font files with the UI and serves them from the app > - A normal UI test now covers the static assets and CSS wiring without adding a package script or build step > - The benefit is a more reliable, self-contained UI that does not rely on third-party webfont hosting ## Linked Issues or Issue Description No public GitHub issue was found for this exact gap. **Subsystem affected** ui/ — React + Vite board UI **Problem or motivation** Paperclip's UI should ship the webfont assets it references so production and self-hosted deployments render consistently without reaching out to Google Fonts at runtime. **Proposed solution** Bundle the Inter variable font files under the UI public assets, load them with local `@font-face` declarations, document the bundled assets, and cover the source assets/CSS wiring with a normal UI Vitest test. **Alternatives considered** Keeping the remote stylesheet dependency is simpler, but leaves deployments dependent on external font hosting. Using system fonts only would avoid the asset footprint, but changes the intended UI typography. **Roadmap alignment** This is a focused UI reliability/polish fix, not a roadmap-level core feature. **Additional context** Searched public GitHub issues and PRs for `webfonts repo:paperclipai/paperclip`; no duplicates or closely related open items were found. ## What Changed - Added bundled Inter variable font assets and their notice under `ui/public/fonts/`. - Replaced remote Google Fonts imports with local `@font-face` declarations using relative public-asset URLs that remain subpath-safe from built CSS. - Documented the local font asset expectation in development and UI spec docs. - Removed the follow-up font asset checker scripts and package/build wiring after review feedback clarified they are not required for building the UI. - Added `ui/src/lib/ui-font-assets.test.ts` to verify the shipped WOFF2 files, notice text, and CSS font references through the normal UI test suite. ## Verification - `pnpm --filter @paperclipai/ui exec vitest run src/lib/ui-font-assets.test.ts --config vitest.config.ts` - Passed. - Confirms the bundled font files exist, are WOFF2 files, have notice coverage, and are referenced by `ui/src/index.css`. - `pnpm --filter @paperclipai/ui build` - Passed. - Confirmed the UI still builds after removing the checker from `ui/package.json`. - Confirmed `ui/dist/fonts/` contains `InterVariable.woff2`, `InterVariable-Italic.woff2`, and `NOTICE.md` after the build. - The UI build emitted existing warnings about `::highlight(...)`, a dynamic/static import overlap for `MarkdownEditor.tsx`, unresolved relative public font URLs left for runtime resolution, and large chunks, but completed successfully. ## Risks Low risk. This adds static font assets and swaps the font source from a remote stylesheet to same-origin files. The main tradeoff is a larger repository/UI asset footprint from the bundled `.woff2` files. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex, GPT-5-based coding agent, tool-enabled terminal/GitHub workflow with reasoning support. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
518fc71cec
commit
574b4e71db
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -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.
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue