* 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 | { rel: "icon", type: "image/png", sizes: "32x32", href: "/favicon-32.png" }, | 116 | { rel: "icon", type: "image/png", sizes: "32x32", href: "/favicon-32.png" }, |
| 117 | { rel: "icon", type: "image/png", sizes: "192x192", href: "/favicon-192.png" }, | 117 | { rel: "icon", type: "image/png", sizes: "192x192", href: "/favicon-192.png" }, |
| 118 | { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" }, | 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 | { rel: "stylesheet", href: appCss }, | 119 | { rel: "stylesheet", href: appCss }, |
| 126 | ], | 120 | ], |
| 127 | }), | 121 | }), |
| @@ -8,12 +8,6 @@ · 7 unmodified lines | |||
| 8 | <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" /> | 8 | <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" /> |
| 9 | <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> | 9 | <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> |
| 10 | <title>Executor</title> | 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 | </head> | 11 | </head> |
| 18 | <body> | 12 | <body> |
| 19 | <div id="root"></div> | 13 | <div id="root"></div> |
| @@ -8,12 +8,6 @@ · 7 unmodified lines | |||
| 8 | <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" /> | 8 | <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" /> |
| 9 | <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> | 9 | <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> |
| 10 | <title>Executor (self-hosted)</title> | 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 | </head> | 11 | </head> |
| 18 | <body> | 12 | <body> |
| 19 | <div id="root"></div> | 13 | <div id="root"></div> |
| @@ -8,12 +8,6 @@ · 7 unmodified lines | |||
| 8 | <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" /> | 8 | <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" /> |
| 9 | <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> | 9 | <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> |
| 10 | <title>Executor</title> | 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 | <script type="module"> | 11 | <script type="module"> |
| 18 | if (import.meta.env.DEV) { | 12 | if (import.meta.env.DEV) { |
| 19 | import("react-grab"); | 13 | import("react-grab"); |
| @@ -4,12 +4,6 @@ · 3 unmodified lines | |||
| 4 | <meta charset="utf-8" /> | 4 | <meta charset="utf-8" /> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1" /> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 6 | <title>Executor onboarding — current flow</title> | 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 | </head> | 7 | </head> |
| 14 | <body> | 8 | <body> |
| 15 | <div id="root"></div> | 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 | @import "tailwindcss"; | 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 | @custom-variant dark (&:is(.dark *)); | 28 | @custom-variant dark (&:is(.dark *)); |
| 4 | 29 | ||
| 5 | @source "../**/*.{ts,tsx}"; | 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 | }); | ||