v3: comments say what the code can't — the documentation pass wanted JSDoc on every export and every field of an exported interface, which filled the code with restatements ("The dump.", "Whether it is loading.", "The props of X.", "Renders the X.") and module docs that told a reader nothing ("The hot and new feeds, and the props every main feed of the index takes."); some were wrong as well as vague. Every comment in api/, shared/ and src/ is rewritten under one rule, now stated in the README's "Comments" section: every module and export still has a JSDoc, and it gives context (who uses it and where), usage (what a caller must know: a precondition, what it leaves to the caller, a side effect) or its place in the chain (where its input comes from, where its output goes); a field, parameter, private function or local gets one only when it adds to its parent's, and context about a whole type goes on the type; a type's doc is phrased from the value's side ("Returned to Y by X when Z", "Passed to Y by X"); full sentences, as short as they can be, no label-colon-list; // goes wherever the code isn't obvious and says why, and the section banners and narration go. Every claim was checked against the code, which turned up some forty comments that were wrong: DumpCard's claimed it was only used by the hot and new feeds, the WebSocket welcome claimed to be a socket's first message (presence comes first), UpdateUserRequest said null removes a description (it keeps it), deletedDumpIds leaves out that it also holds dumps made private, THEME_COLOR claimed to mirror index.html's, and the like. The routers' JSDoc now says where they are mounted and what becomes of a thrown APIException. doc-lint.ts lets a field without a doc through; a module or export without one still fails. No code changes; 204 backend and 628 frontend tests pass, and doc:lint, deno check, deno lint and tsc are clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
/**
|
||||
* Generates the full favicon set and web manifest from a single emoji.
|
||||
* Renders the site's favicons and web manifest from its emoji. The `icons`
|
||||
* task (`api/scripts/generate-icons.ts`) runs it before the server starts, so
|
||||
* the emoji can change without a rebuild.
|
||||
*
|
||||
* Twemoji ships each emoji as a plain vector-path SVG, so it rasterizes without
|
||||
* any system emoji font (which matters in the minimal Alpine runtime image).
|
||||
* `@resvg/resvg-wasm` does the rasterization; the .ico is assembled by hand.
|
||||
*
|
||||
* Invoked by api/scripts/generate-icons.ts at server startup (into dist/ in
|
||||
* prod, public/ in dev) — see that script for how the output dir is chosen.
|
||||
* The glyph comes from Twemoji as plain vector paths, so rendering it needs no
|
||||
* emoji font, which the Alpine runtime image lacks.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -19,9 +17,10 @@ const APPLE_SIZE = 180;
|
||||
const ICO_SIZES = [16, 32, 48];
|
||||
|
||||
/**
|
||||
* Returns the name Twemoji files `emoji` under: its codepoints joined with
|
||||
* "-", variation selectors (FE0F) stripped unless the emoji is a ZWJ sequence.
|
||||
* Also doubles as a stable cache-busting version string for the icon URLs.
|
||||
* Returns the name Twemoji files `emoji` under: its codepoints in hex, joined
|
||||
* with "-", without variation selectors (FE0F) unless the emoji is a ZWJ
|
||||
* sequence. `lib/index-html.ts` also uses it as the icon URLs' cache-busting
|
||||
* version.
|
||||
*/
|
||||
export function emojiToCodepoint(emoji: string): string {
|
||||
const cleaned = emoji.includes("") ? emoji : emoji.replace(/️/g, "");
|
||||
@@ -47,7 +46,7 @@ function renderPng(svg: string, size: number, background?: string): Uint8Array {
|
||||
return resvg.render().asPng();
|
||||
}
|
||||
|
||||
/** Packs PNG payloads into a (PNG-compressed) .ico container. */
|
||||
/** Packs PNG images into an .ico file, which may hold PNGs as they are. */
|
||||
function buildIco(images: { size: number; png: Uint8Array }[]): Uint8Array {
|
||||
const HEADER = 6;
|
||||
const ENTRY = 16;
|
||||
@@ -99,24 +98,23 @@ function buildManifest(siteName: string, bgColor: string): string {
|
||||
return JSON.stringify(manifest, null, 2) + "\n";
|
||||
}
|
||||
|
||||
/** What {@link generateSiteIcons} renders, and where. */
|
||||
/**
|
||||
* Passed to {@link generateSiteIcons} by `api/scripts/generate-icons.ts`, from
|
||||
* the site's config.
|
||||
*/
|
||||
export interface GenerateSiteIconsOptions {
|
||||
/** Emoji to render the icons from. */
|
||||
emoji: string;
|
||||
/** Directory to write the assets into (created if missing). */
|
||||
/** Created if missing. */
|
||||
outDir: string;
|
||||
/** Site name written into the manifest. */
|
||||
siteName: string;
|
||||
/**
|
||||
* Background color for the Apple touch icon and manifest (iOS ignores
|
||||
* transparency).
|
||||
*/
|
||||
/** Fills the Apple touch icon, and the manifest's theme and background. */
|
||||
bgColor: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates `favicon.svg`, `favicon.ico`, `favicon-<size>x<size>.png` (16–512),
|
||||
* `apple-touch-icon.png` and `manifest.webmanifest` into `outDir`.
|
||||
* Writes `favicon.svg`, `favicon.ico`, `favicon-<size>x<size>.png` (16 to 512),
|
||||
* `apple-touch-icon.png` and `manifest.webmanifest` into `opts.outDir`, the
|
||||
* directory the site's static files are served from.
|
||||
*
|
||||
* @throws {Error} When the emoji's glyph can't be fetched.
|
||||
*/
|
||||
@@ -137,7 +135,7 @@ export async function generateSiteIcons(
|
||||
for (const size of PNG_SIZES) {
|
||||
await Deno.writeFile(out(`favicon-${size}x${size}.png`), pngs.get(size)!);
|
||||
}
|
||||
// Apple touch icon gets a solid background — iOS doesn't honor transparency.
|
||||
// iOS draws a transparent touch icon on black.
|
||||
await Deno.writeFile(
|
||||
out("apple-touch-icon.png"),
|
||||
renderPng(svg, APPLE_SIZE, bgColor),
|
||||
|
||||
Reference in New Issue
Block a user