feat(settings): rebuild server fonts as a specimen ledger with a family sheet

Each family is a row with its specimen, a weight grid of the styles readers get and how readers
use it; its sheet previews the face on reader pages and inside the reader's own font list. Uploads
answer per file in one tray, removing a family confirms with its cost, and a rename is one request.
Server: additive GET /server-fonts/usage (counts only), PATCH /server-fonts/families, upload error codes.
This commit is contained in:
neon
2026-10-16 16:27:42 +00:00
parent f89ae86d69
commit e21a92b8a4
36 changed files with 2918 additions and 170 deletions
+40
View File
@@ -58,6 +58,46 @@ export interface FontUploadResult {
suggestedStyle: FontStyle;
}
/**
* Why an upload was refused, carried as `errorCode` on the error body so a client can say it in
* the reader's language. The English `message` stays beside it for clients that predate the codes.
*/
export const FONT_UPLOAD_ERROR_CODES = [
"FONT_TOO_LARGE",
"FONT_UNSUPPORTED_TYPE",
"FONT_INVALID_FILE",
"FONT_DUPLICATE",
"FONT_FACE_TAKEN",
"FONT_LIMIT_REACHED",
] as const;
export type FontUploadErrorCode = (typeof FONT_UPLOAD_ERROR_CODES)[number];
/**
* How readers use one server family. Counts only: which books and which readers stay private to
* them, because the people who curate fonts are not necessarily allowed to see reading activity.
*/
export interface ServerFontFamilyUsage {
familyName: string;
/** Books some reader has set to this family. */
books: number;
/** Readers with at least one such book. */
readers: number;
/** Readers whose default eBook font is this family. */
defaults: number;
/** Readers who hid the family from their font list. */
hidden: number;
}
export interface ServerFontUsageResponse {
families: ServerFontFamilyUsage[];
}
/** Renames every file of a family together, so a failure cannot leave the family split in two. */
export interface RenameServerFontFamilyRequest {
familyName: string;
newFamilyName: string;
}
export const FONT_FORMATS: readonly FontFormat[] = ["ttf", "otf", "woff", "woff2"];
export const FONT_FORMAT_EXTENSIONS: Record<FontFormat, string> = {