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,5 +1,6 @@
|
||||
/**
|
||||
* The fallback link provider, for any page no other provider claims.
|
||||
* Previews any page no other provider claims, for `rich-content-service.ts`,
|
||||
* from whatever metadata its HTML carries.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -21,15 +22,16 @@ import {
|
||||
import { fetchWithTimeout, readTextCapped } from "../fetch.ts";
|
||||
|
||||
/**
|
||||
* The provider for any HTML page, previewed from its OG and Twitter tags, JSON-LD, and markup:
|
||||
* title, description, artwork, icon and brand color. Matches every URL, so it
|
||||
* must come last.
|
||||
* Matches every URL, so it comes last in the service's list. Each field falls
|
||||
* back from OG tags to Twitter tags, JSON-LD and the markup itself. The icon
|
||||
* and brand color feed the placeholder the frontend draws when there is no
|
||||
* thumbnail.
|
||||
*/
|
||||
export const genericProvider: RichContentProvider = {
|
||||
name: "generic",
|
||||
|
||||
matches(_url: string): boolean {
|
||||
return true; // fallback — always matches
|
||||
return true;
|
||||
},
|
||||
|
||||
async fetch(url: string): Promise<RichContent> {
|
||||
@@ -44,9 +46,9 @@ export const genericProvider: RichContentProvider = {
|
||||
const html = await readTextCapped(res);
|
||||
const ld = extractJsonLd(html);
|
||||
|
||||
// If og:url is present but points to a different page (e.g. the homepage),
|
||||
// the og: block is a site-level fallback, not page-specific metadata.
|
||||
// In that case skip og:title and og:image so page-level signals win.
|
||||
// An og:url naming another page (often the home page) means the OG tags
|
||||
// are the site's defaults, not this page's: its title and image are
|
||||
// skipped.
|
||||
const ogUrl = extractOgTag(html, "url");
|
||||
const useOg = !ogUrl || (() => {
|
||||
try {
|
||||
@@ -58,36 +60,29 @@ export const genericProvider: RichContentProvider = {
|
||||
}
|
||||
})();
|
||||
|
||||
// Title: og:title (page-matched) → twitter:title → JSON-LD → <title>
|
||||
const title = (useOg ? extractOgTag(html, "title") : undefined) ??
|
||||
extractMetaName(html, "twitter:title") ??
|
||||
ld.title ??
|
||||
extractPageTitle(html);
|
||||
|
||||
// Site name: og:site_name → hostname
|
||||
const siteName = extractOgTag(html, "site_name") ??
|
||||
new URL(url).hostname.replace(/^www\./, "");
|
||||
|
||||
// Description: og:description → twitter:description → JSON-LD → <meta name="description">
|
||||
const description = extractOgTag(html, "description") ??
|
||||
extractMetaName(html, "twitter:description") ??
|
||||
ld.description ??
|
||||
extractMetaName(html, "description");
|
||||
|
||||
// Image: og:image (page-matched) → twitter:image → JSON-LD → large <img> →
|
||||
// first content <img>. The chain deliberately stops there: a favicon is not
|
||||
// artwork, and pretending otherwise means every art-less page gets a 16×16
|
||||
// icon cover-cropped into a 128×72 box. No match here means "no artwork",
|
||||
// and the frontend draws a generated placeholder instead.
|
||||
// Never the favicon: a 16×16 icon cropped into a thumbnail looks broken,
|
||||
// and the placeholder drawn instead shows the icon properly.
|
||||
const thumbnailUrl = (useOg ? extractOgTag(html, "image") : undefined) ??
|
||||
extractMetaName(html, "twitter:image") ??
|
||||
ld.thumbnailUrl ??
|
||||
extractLargeImage(html, url) ??
|
||||
extractFirstContentImage(html, url);
|
||||
|
||||
// Icon and brand color are independent facts about the page, so they're
|
||||
// collected whether or not there's artwork. `/favicon.ico` is a guess; when
|
||||
// it 404s the placeholder falls back to the site's initial.
|
||||
// `/favicon.ico` is a guess. When it doesn't load, the placeholder shows
|
||||
// the site's initial.
|
||||
const faviconUrl = extractBestIcon(html, url) ??
|
||||
`${new URL(url).origin}/favicon.ico`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user