From 5c4f1f30a62bbbaac092ed237b3c42145d3c84af Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:30:22 +0200 Subject: [PATCH 1/2] feat: bundle Inconsolata for the ClickHouse theme The ClickHouse theme names Inconsolata as its mono family, but the app did not ship the face, so code fell back to the system monospace. Self-host the latin 400 and 700 woff2 faces from Fontsource 5.3.0 with their OFL licence, declared with font-display: swap in a stylesheet of theme-opt-in faces. The browser fetches a declared face only when text renders in it, so the default theme never downloads them. --- client/public/fonts/inconsolata-OFL.txt | 93 ++++++++++++++++++ .../fonts/inconsolata-latin-400-normal.woff2 | Bin 0 -> 17664 bytes .../fonts/inconsolata-latin-700-normal.woff2 | Bin 0 -> 17960 bytes client/src/fonts.css | 18 ++++ client/src/main.jsx | 1 + .../scenarios/clickhouse-theme-shape.spec.ts | 55 +++++++++++ .../client/src/theme/themes/clickhouse.ts | 6 +- 7 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 client/public/fonts/inconsolata-OFL.txt create mode 100644 client/public/fonts/inconsolata-latin-400-normal.woff2 create mode 100644 client/public/fonts/inconsolata-latin-700-normal.woff2 create mode 100644 client/src/fonts.css diff --git a/client/public/fonts/inconsolata-OFL.txt b/client/public/fonts/inconsolata-OFL.txt new file mode 100644 index 00000000000..79721add751 --- /dev/null +++ b/client/public/fonts/inconsolata-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2006 The Inconsolata Project Authors (https://github.com/cyrealtype/Inconsolata) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/client/public/fonts/inconsolata-latin-400-normal.woff2 b/client/public/fonts/inconsolata-latin-400-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..a5ef8233e02fb4397f7382a6d4a2f243a8e768e9 GIT binary patch literal 17664 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 literal 0 HcmV?d00001 diff --git a/client/public/fonts/inconsolata-latin-700-normal.woff2 b/client/public/fonts/inconsolata-latin-700-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..0f59953923d57c948f8716a5759f196a6d187ac3 GIT binary patch literal 17960 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 { return menu; } +/** + * Paints code text, waits for the browser to settle its font loads, and reports the status of + * every declared Inconsolata face. A face is fetched only once text renders in it. + */ +async function inconsolataStatuses(page: Page): Promise { + return page.evaluate(async () => { + const probe = document.createElement('code'); + probe.textContent = 'const probe = 0;'; + document.body.append(probe); + await document.fonts.ready; + probe.remove(); + return [...document.fonts] + .filter((face) => face.family.replace(/"/g, '') === 'Inconsolata') + .map((face) => face.status); + }); +} + async function resolvedMode(page: Page): Promise { const dark = await page.evaluate(() => document.documentElement.classList.contains('dark')); return dark ? 'dark' : 'light'; @@ -104,4 +121,42 @@ test.describe('ClickHouse theme shape', () => { expect(family.replace(/"/g, '').startsWith('Inconsolata')).toBe(true); expect(family).toContain('monospace'); }); + + test('the ClickHouse theme renders code in the self-hosted Inconsolata face @scenario:clickhouse-mono-font-is-bundled', async ({ + page, + }) => { + const fetched: string[] = []; + page.on('requestfinished', (request) => { + if (/inconsolata-latin-\d+-normal/.test(request.url())) { + fetched.push(request.url()); + } + }); + await storeClickHouse(page); + + await openMentionMenu(page); + await expect(page.locator('html')).toHaveAttribute('data-theme', 'clickhouse'); + + expect(await inconsolataStatuses(page)).toContain('loaded'); + expect(await page.evaluate(() => document.fonts.check('16px Inconsolata'))).toBe(true); + expect(fetched).not.toHaveLength(0); + }); + + test('the default theme never fetches Inconsolata @scenario:default-theme-skips-inconsolata', async ({ + page, + }) => { + const fetched: string[] = []; + page.on('request', (request) => { + if (/inconsolata/i.test(request.url())) { + fetched.push(request.url()); + } + }); + + await openMentionMenu(page); + await expect(page.locator('html')).not.toHaveAttribute('data-theme', 'clickhouse'); + + const statuses = await inconsolataStatuses(page); + expect(statuses).not.toHaveLength(0); + expect(statuses.every((status) => status === 'unloaded')).toBe(true); + expect(fetched).toEqual([]); + }); }); diff --git a/packages/client/src/theme/themes/clickhouse.ts b/packages/client/src/theme/themes/clickhouse.ts index 945e2cadfbb..9d7650a36c4 100644 --- a/packages/client/src/theme/themes/clickhouse.ts +++ b/packages/client/src/theme/themes/clickhouse.ts @@ -275,9 +275,9 @@ export const clickHouseDarkTheme: IThemeRGB = { * the dialog-sized steps onto `radii.2`, and the largest onto `radii.3`. `rounded-sm` takes * `radii.1`, which LibreChat's `sm` also renders at on a 16px root; every larger step tightens. * - * Click UI's mono family is Inconsolata. The app does not bundle it, so the tail is the same - * metric-matched stack the default theme uses (Click UI's own tail names `"SFMono Regular"`, - * which no platform installs). + * Click UI's mono family is Inconsolata, which the client self-hosts in `client/src/fonts.css` + * (latin 400 and 700). The tail is the same metric-matched stack the default theme uses (Click + * UI's own tail names `"SFMono Regular"`, which no platform installs). * * Click UI raises every elevated surface (card, dialog, menu, panel, popover, toast) with * `shadow.1`, and its only lighter step is the hairline `shadow.5`. Steps 2 to 4 are the flyout's From 2a9690f38fcc3c5123d72a7ebc34dfe04b03b81d Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 25 Sep 2026 22:11:22 +0200 Subject: [PATCH 2/2] fix: ship the Inconsolata licence with the production build Production builds disable publicDir, so the woff2 faces reached dist through the $fonts alias while the OFL text next to them in public/fonts did not. The build now copies the licence texts from public/fonts into assets/fonts, beside the faces they cover. --- client/vite.config.ts | 17 +++++++++++++++-- .../scenarios/clickhouse-theme-shape.spec.ts | 13 +++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/client/vite.config.ts b/client/vite.config.ts index 5a279a92cac..fa1f0170820 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -462,8 +462,9 @@ export function sourcemapExclude(opts?: SourcemapExclude): Plugin { * Production builds set `publicDir: false`, so nothing under public/ reaches dist on its * own. This copies what the server actually has to serve: all of public/assets (the PWA * icons plus the endpoint, tool and language logos referenced at runtime) and robots.txt. - * public/fonts is deliberately left out, since fonts are emitted as bundle assets through - * the `$fonts` alias. + * The font files in public/fonts are emitted as bundle assets through the `$fonts` alias, + * so only their licence texts are copied, next to them in assets/fonts: the SIL OFL lets a + * font be redistributed only with its licence. * * The copy MUST happen inside the build. vite-plugin-pwa globs dist/ for * `workbox.globPatterns` from its `closeBundle` hook, which runs after every plugin's @@ -489,6 +490,18 @@ export function copyPublicAssets(): Plugin { path.join(publicDir, 'robots.txt'), path.join(outDir, 'robots.txt'), ); + const licences = (await fs.promises.readdir(path.join(publicDir, 'fonts'))).filter((name) => + name.endsWith('.txt'), + ); + await fs.promises.mkdir(path.join(outDir, 'assets', 'fonts'), { recursive: true }); + await Promise.all( + licences.map((name) => + fs.promises.copyFile( + path.join(publicDir, 'fonts', name), + path.join(outDir, 'assets', 'fonts', name), + ), + ), + ); }, }; } diff --git a/e2e/specs/mock/scenarios/clickhouse-theme-shape.spec.ts b/e2e/specs/mock/scenarios/clickhouse-theme-shape.spec.ts index 42d30507199..df70b50bef8 100644 --- a/e2e/specs/mock/scenarios/clickhouse-theme-shape.spec.ts +++ b/e2e/specs/mock/scenarios/clickhouse-theme-shape.spec.ts @@ -141,6 +141,19 @@ test.describe('ClickHouse theme shape', () => { expect(fetched).not.toHaveLength(0); }); + test('the production build serves the Inconsolata licence beside the font @scenario:inconsolata-licence-ships-with-font', async ({ + page, + }) => { + const font = await page.request.get('/assets/fonts/inconsolata-latin-400-normal.woff2'); + expect(font.status()).toBe(200); + + const licence = await page.request.get('/assets/fonts/inconsolata-OFL.txt'); + expect(licence.status()).toBe(200); + const text = await licence.text(); + expect(text).toContain('Copyright 2006 The Inconsolata Project Authors'); + expect(text).toContain('SIL OPEN FONT LICENSE Version 1.1'); + }); + test('the default theme never fetches Inconsolata @scenario:default-theme-skips-inconsolata', async ({ page, }) => {