Repository navigation
Conversation
Brand › Fonts gains Your own font: up to six woff2 files with the weight and style read from each name, a family name, headings, body or both, and a licence to tick before anything uploads. The draft's font becomes the uploaded family, with the font the slot had as its fallback; both pickers list it, and review names the change by the family. The upload URL signs font/woff2 under fonts/ for the branding permission, refusing other names, files over 400 KB, no storage and a plan without custom CSS as values. The upload itself checks the wOF2 signature, the prefix and the size. fonts/ is public and always proxied from the portal's own origin, since @font-face fetches in CORS mode. The schema takes an uploaded key only as a woff2 under fonts/ in characters that cannot end a url(). The theme's CSS carries an @font-face rule per file, and the head preloads the file nearest the body's and headings' weights.
mortondev
force-pushed
the
feat/portal-design-v2-phase5-font-uploads
branch
from
October 31, 2026 08:31
a710593 to
a468ad5
Compare
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The twenty-fourth of the stack that builds a new design engine for the portal, and the fifth of phase 5. A workspace can upload its own font for the portal's headings, body or both, so a brand with a licensed face is not limited to the bundled families. Behind the
portalDesignV2flag.In Brand › Fonts
The font controls move out of the Brand panel into
fonts-section.tsx, with a closed "Your own font" disclosure under the pickers:woff2files. Each gets a weight and style read from its name (Brand-SemiBoldItalic.woff2is 600 italic), which can be changed, and the family name comes from the first file.Once added, the family is listed under "Your fonts" in both pickers, the heading weights offer the weights it has, and review names the change by the family ("Space Grotesk → Unbounded") rather than its fields. Without file storage the disclosure says "Uploading fonts needs file storage." and shows no controls.
Storage and serving
getFontUploadUrlFnsigns an upload offont/woff2underfonts/, for someone with the branding permission. It returns refusals as values: not a woff2 name, over 400 KB, no storage, or a plan without custom CSS.PUT /api/storage/*) checks what the signature cannot: afont/woff2upload must be underfonts/, start with thewOF2signature and be at most 400 KB. Any other type still has to be an image whose bytes match.fonts/is a public prefix, since every visitor loads the files. They are always proxied from the portal's own origin, even withoutS3_PROXY:@font-facefetches in CORS mode, so a redirect to the bucket would depend on its CORS rules. Served with their stored type as a download withnosniff, they embed as fonts but never open as a page.woff2underfonts/made of characters that cannot end aurl(), since the key is written into the page's CSS.Rendering
@font-facerule per uploaded file (font-display: swap), from/api/storage/….Plan
Uploading fonts is gated on the plan's custom CSS, as the spec recommends. Pricing is the owner's call, so this is the line to change if it should sit elsewhere.
Not in this PR
Fallback metric overrides (
size-adjust,ascent-override). They need the uploaded face's own metrics, which means decompressing the woff2 tables, so the fallback draws at its own metrics until the face loads.Verification
.woff2passed the browser's checks and was refused by the server's signature check, with its message. Unbounded Bold and Regular (from the OFL-licensed@fontsource/unbounded) got the name "Unbounded" and weights 700 and 400 from their file names. After the licence tick and Add font, the heading picker showed Unbounded, and the preview drew its headings in it with the face loaded. Review read "Heading font: Space Grotesk → Unbounded". After publishing, the live document preloaded the file from/api/storage/fonts/…, served from the portal asfont/woff2, fetched once from the head, and the headline drew in Unbounded. No page errors.font/woff2underfonts/for branding, refusing other names, large files, no storage and a plan without custom CSS; the upload refuses a non-woff2 body, a font outsidefonts/and one over 400 KB; a font is served from the portal withoutS3_PROXY; the key rule refuses other prefixes, traversal, dot segments and CSS-breaking characters; one@font-facerule per file and the body stack; the upright file nearest a weight; the preload from storage; review naming the family; weights and family from file names; the licence holding the upload; a refusal shown with the draft untouched.proxy-cache-tenant.test.tswhile formatting that folder.