* Self-host Geist fonts instead of loading them from Google Fonts * Move console font test to root tests so it typechecks
Unified · Split
| @@ -0,0 +1,5 @@ | ||
| 1 | +--- | |
| 2 | +"executor": patch | |
| 3 | +--- | |
| 4 | + | |
| 5 | +Bundle the Geist and Geist Mono fonts with the console instead of loading them from Google Fonts. The UI no longer stays blank when a self-hosted deployment cannot reach fonts.googleapis.com. | |
| @@ -116,12 +116,6 @@ export const Route = createRootRoute({ · 115 unmodified lines | ||
| 116 | 116 | { rel: "icon", type: "image/png", sizes: "32x32", href: "/favicon-32.png" }, |
| 117 | 117 | { rel: "icon", type: "image/png", sizes: "192x192", href: "/favicon-192.png" }, |
| 118 | 118 | { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" }, |
| 119 | − { rel: "preconnect", href: "https://fonts.googleapis.com" }, | |
| 120 | − { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }, | |
| 121 | − { | |
| 122 | − rel: "stylesheet", | |
| 123 | − href: "https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500;700&display=swap", | |
| 124 | − }, | |
| 125 | 119 | { rel: "stylesheet", href: appCss }, |
| 126 | 120 | ], |
| 127 | 121 | }), |
| @@ -8,12 +8,6 @@ · 7 unmodified lines | ||
| 8 | 8 | <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" /> |
| 9 | 9 | <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> |
| 10 | 10 | <title>Executor</title> |
| 11 | − <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| 12 | − <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| 13 | − <link | |
| 14 | − rel="stylesheet" | |
| 15 | − href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600&family=Geist+Mono:wght@400;500&display=swap" | |
| 16 | − /> | |
| 17 | 11 | </head> |
| 18 | 12 | <body> |
| 19 | 13 | <div id="root"></div> |
| @@ -8,12 +8,6 @@ · 7 unmodified lines | ||
| 8 | 8 | <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" /> |
| 9 | 9 | <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> |
| 10 | 10 | <title>Executor (self-hosted)</title> |
| 11 | − <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| 12 | − <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| 13 | − <link | |
| 14 | − rel="stylesheet" | |
| 15 | − href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600&family=Geist+Mono:wght@400;500&display=swap" | |
| 16 | − /> | |
| 17 | 11 | </head> |
| 18 | 12 | <body> |
| 19 | 13 | <div id="root"></div> |
| @@ -8,12 +8,6 @@ · 7 unmodified lines | ||
| 8 | 8 | <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" /> |
| 9 | 9 | <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> |
| 10 | 10 | <title>Executor</title> |
| 11 | − <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| 12 | − <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| 13 | − <link | |
| 14 | − rel="stylesheet" | |
| 15 | − href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600&family=Geist+Mono:wght@400;500&display=swap" | |
| 16 | − /> | |
| 17 | 11 | <script type="module"> |
| 18 | 12 | if (import.meta.env.DEV) { |
| 19 | 13 | import("react-grab"); |
| @@ -4,12 +4,6 @@ · 3 unmodified lines | ||
| 4 | 4 | <meta charset="utf-8" /> |
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 6 | 6 | <title>Executor onboarding — current flow</title> |
| 7 | − <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| 8 | − <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| 9 | − <link | |
| 10 | − rel="stylesheet" | |
| 11 | − href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600&family=Geist+Mono:wght@400;500&display=swap" | |
| 12 | − /> | |
| 13 | 7 | </head> |
| 14 | 8 | <body> |
| 15 | 9 | <div id="root"></div> |
| @@ -0,0 +1,92 @@ | ||
| 1 | +Copyright (c) 2023 Vercel, in collaboration with basement.studio | |
| 2 | + | |
| 3 | +This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
| 4 | +This license is copied below, and is also available with a FAQ at: | |
| 5 | +http://scripts.sil.org/OFL | |
| 6 | + | |
| 7 | +----------------------------------------------------------- | |
| 8 | +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| 9 | +----------------------------------------------------------- | |
| 10 | + | |
| 11 | +PREAMBLE | |
| 12 | +The goals of the Open Font License (OFL) are to stimulate worldwide | |
| 13 | +development of collaborative font projects, to support the font creation | |
| 14 | +efforts of academic and linguistic communities, and to provide a free and | |
| 15 | +open framework in which fonts may be shared and improved in partnership | |
| 16 | +with others. | |
| 17 | + | |
| 18 | +The OFL allows the licensed fonts to be used, studied, modified and | |
| 19 | +redistributed freely as long as they are not sold by themselves. The | |
| 20 | +fonts, including any derivative works, can be bundled, embedded, | |
| 21 | +redistributed and/or sold with any software provided that any reserved | |
| 22 | +names are not used by derivative works. The fonts and derivatives, | |
| 23 | +however, cannot be released under any other type of license. The | |
| 24 | +requirement for fonts to remain under this license does not apply | |
| 25 | +to any document created using the fonts or their derivatives. | |
| 26 | + | |
| 27 | +DEFINITIONS | |
| 28 | +"Font Software" refers to the set of files released by the Copyright | |
| 29 | +Holder(s) under this license and clearly marked as such. This may | |
| 30 | +include source files, build scripts and documentation. | |
| 31 | + | |
| 32 | +"Reserved Font Name" refers to any names specified as such after the | |
| 33 | +copyright statement(s). | |
| 34 | + | |
| 35 | +"Original Version" refers to the collection of Font Software components as | |
| 36 | +distributed by the Copyright Holder(s). | |
| 37 | + | |
| 38 | +"Modified Version" refers to any derivative made by adding to, deleting, | |
| 39 | +or substituting -- in part or in whole -- any of the components of the | |
| 40 | +Original Version, by changing formats or by porting the Font Software to a | |
| 41 | +new environment. | |
| 42 | + | |
| 43 | +"Author" refers to any designer, engineer, programmer, technical | |
| 44 | +writer or other person who contributed to the Font Software. | |
| 45 | + | |
| 46 | +PERMISSION AND CONDITIONS | |
| 47 | +Permission is hereby granted, free of charge, to any person obtaining | |
| 48 | +a copy of the Font Software, to use, study, copy, merge, embed, modify, | |
| 49 | +redistribute, and sell modified and unmodified copies of the Font | |
| 50 | +Software, subject to the following conditions: | |
| 51 | + | |
| 52 | +1) Neither the Font Software nor any of its individual components, | |
| 53 | +in Original or Modified Versions, may be sold by itself. | |
| 54 | + | |
| 55 | +2) Original or Modified Versions of the Font Software may be bundled, | |
| 56 | +redistributed and/or sold with any software, provided that each copy | |
| 57 | +contains the above copyright notice and this license. These can be | |
| 58 | +included either as stand-alone text files, human-readable headers or | |
| 59 | +in the appropriate machine-readable metadata fields within text or | |
| 60 | +binary files as long as those fields can be easily viewed by the user. | |
| 61 | + | |
| 62 | +3) No Modified Version of the Font Software may use the Reserved Font | |
| 63 | +Name(s) unless explicit written permission is granted by the corresponding | |
| 64 | +Copyright Holder. This restriction only applies to the primary font name as | |
| 65 | +presented to the users. | |
| 66 | + | |
| 67 | +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |
| 68 | +Software shall not be used to promote, endorse or advertise any | |
| 69 | +Modified Version, except to acknowledge the contribution(s) of the | |
| 70 | +Copyright Holder(s) and the Author(s) or with their explicit written | |
| 71 | +permission. | |
| 72 | + | |
| 73 | +5) The Font Software, modified or unmodified, in part or in whole, | |
| 74 | +must be distributed entirely under this license, and must not be | |
| 75 | +distributed under any other license. The requirement for fonts to | |
| 76 | +remain under this license does not apply to any document created | |
| 77 | +using the Font Software. | |
| 78 | + | |
| 79 | +TERMINATION | |
| 80 | +This license becomes null and void if any of the above conditions are | |
| 81 | +not met. | |
| 82 | + | |
| 83 | +DISCLAIMER | |
| 84 | +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 85 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |
| 86 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |
| 87 | +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |
| 88 | +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 89 | +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |
| 90 | +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 91 | +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |
| 92 | +OTHER DEALINGS IN THE FONT SOFTWARE. | |
Binary file not shown.
Binary file not shown.
| @@ -1,5 +1,30 @@ | ||
| 1 | 1 | @import "tailwindcss"; |
| 2 | 2 | |
| 3 | +/* Fonts: vendored and served with the app, never fetched from a third party. | |
| 4 | + Every host loads this stylesheet, so the faces are declared once here rather | |
| 5 | + than in each entry document. A self-hosted deployment may not be able to | |
| 6 | + reach Google Fonts at all; a synchronous stylesheet from there used to hold | |
| 7 | + the whole console blank until the request timed out. The bundler resolves | |
| 8 | + these url()s into content-hashed assets next to the rest of the build, and | |
| 9 | + `swap` means text paints in the system stack the instant the document does. | |
| 10 | + Variable faces cover every weight the console uses (300 to 700) in one file | |
| 11 | + each. Source: the `geist` npm package (SIL OFL, see fonts/LICENSE.txt). */ | |
| 12 | +@font-face { | |
| 13 | + font-family: "Geist"; | |
| 14 | + src: url("./fonts/geist-sans.woff2") format("woff2"); | |
| 15 | + font-weight: 100 900; | |
| 16 | + font-style: normal; | |
| 17 | + font-display: swap; | |
| 18 | +} | |
| 19 | + | |
| 20 | +@font-face { | |
| 21 | + font-family: "Geist Mono"; | |
| 22 | + src: url("./fonts/geist-mono.woff2") format("woff2"); | |
| 23 | + font-weight: 100 900; | |
| 24 | + font-style: normal; | |
| 25 | + font-display: swap; | |
| 26 | +} | |
| 27 | + | |
| 3 | 28 | @custom-variant dark (&:is(.dark *)); |
| 4 | 29 | |
| 5 | 30 | @source "../**/*.{ts,tsx}"; |
| @@ -0,0 +1,45 @@ | ||
| 1 | +import { readFileSync } from "node:fs"; | |
| 2 | +import path from "node:path"; | |
| 3 | +import { fileURLToPath } from "node:url"; | |
| 4 | +import { describe, expect, it } from "@effect/vitest"; | |
| 5 | + | |
| 6 | +// The console must paint without reaching any third-party host. A self-hosted | |
| 7 | +// deployment behind a restrictive network once sat blank until a synchronous | |
| 8 | +// Google Fonts stylesheet timed out, even though the app itself was healthy. | |
| 9 | +// The faces are vendored next to the shared stylesheet and declared once for every | |
| 10 | +// host; the entry documents carry no font links at all. | |
| 11 | + | |
| 12 | +const here = path.dirname(fileURLToPath(import.meta.url)); | |
| 13 | +const repoRoot = path.resolve(here, ".."); | |
| 14 | +const stylesDir = path.join(repoRoot, "packages/react/src/styles"); | |
| 15 | +const globalsCss = readFileSync(path.join(stylesDir, "globals.css"), "utf-8"); | |
| 16 | + | |
| 17 | +const entryDocuments = [ | |
| 18 | + "apps/host-selfhost/web/index.html", | |
| 19 | + "apps/host-cloudflare/web/index.html", | |
| 20 | + "apps/cloud/src/routes/__root.tsx", | |
| 21 | + "apps/desktop/src/renderer/index.html", | |
| 22 | + "packages/app/index.html", | |
| 23 | + "packages/onboarding-demo/index.html", | |
| 24 | +]; | |
| 25 | + | |
| 26 | +describe("console fonts", () => { | |
| 27 | + it("declares Geist and Geist Mono from vendored files with swap display", () => { | |
| 28 | + expect(globalsCss).toMatch( | |
| 29 | + /@font-face\s*\{[^}]*font-family:\s*"Geist";[^}]*url\("\.\/fonts\/geist-sans\.woff2"\)[^}]*font-display:\s*swap;/, | |
| 30 | + ); | |
| 31 | + expect(globalsCss).toMatch( | |
| 32 | + /@font-face\s*\{[^}]*font-family:\s*"Geist Mono";[^}]*url\("\.\/fonts\/geist-mono\.woff2"\)[^}]*font-display:\s*swap;/, | |
| 33 | + ); | |
| 34 | + for (const file of ["geist-sans.woff2", "geist-mono.woff2"]) { | |
| 35 | + expect(readFileSync(path.join(stylesDir, "fonts", file)).byteLength, file).toBeGreaterThan(0); | |
| 36 | + } | |
| 37 | + }); | |
| 38 | + | |
| 39 | + it("keeps every entry document free of third-party font hosts", () => { | |
| 40 | + for (const relative of entryDocuments) { | |
| 41 | + const source = readFileSync(path.join(repoRoot, relative), "utf-8"); | |
| 42 | + expect(source, relative).not.toMatch(/fonts\.googleapis\.com|fonts\.gstatic\.com/); | |
| 43 | + } | |
| 44 | + }); | |
| 45 | +}); | |