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 @@
|
||||
/**
|
||||
* Keeping a list of playlists in step with the socket.
|
||||
* Keeps a page's list of playlists in step with the socket's `playlist_*`
|
||||
* messages. The user's profile and playlists pages call it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -8,36 +9,34 @@ import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
import type { Playlist } from "../../../shared/playlist.ts";
|
||||
import { useWS } from "../ws/useWS.ts";
|
||||
|
||||
/** How {@link usePlaylistListSync} treats the list. */
|
||||
/** Passed to {@link usePlaylistListSync} by each page, to fit its list. */
|
||||
export interface PlaylistListSyncOptions {
|
||||
/** Keep private playlists visible (caller is the owner). */
|
||||
/** Keeps private playlists in the list, for their owner. */
|
||||
isOwner?: boolean;
|
||||
/**
|
||||
* Only act on created/updated events for playlists owned by this user.
|
||||
* Leave undefined to act on any playlist already in the list.
|
||||
* Adds only this user's playlists, when one is created or comes back into
|
||||
* view. Without it, anyone's are added.
|
||||
*/
|
||||
ownerId?: string;
|
||||
/**
|
||||
* Set true for "followed" lists: never prepend newly created playlists
|
||||
* (followed membership is managed separately), but still update/remove.
|
||||
* Adds no playlist, for a list of followed playlists, since the socket
|
||||
* doesn't say what the user follows. Playlists already listed still change
|
||||
* and go.
|
||||
*/
|
||||
noNewEntries?: boolean;
|
||||
/**
|
||||
* When true, don't re-add a playlist that isn't in the list (idx === -1).
|
||||
* Use this when the caller handles re-insertion itself (e.g. with a position
|
||||
* map, like the dumps/votes pattern in UserPublicProfile).
|
||||
* Leaves out a playlist that comes back into view, for a caller that puts it
|
||||
* back where it was with `usePositionAwareSync`.
|
||||
*/
|
||||
skipReinsert?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps the playlist list set through `setPlaylists` in sync with real-time WS
|
||||
* events.
|
||||
*
|
||||
* - a playlist created → prepended (unless `noNewEntries`)
|
||||
* - a playlist updated → updated in place, or removed if now private
|
||||
* - a playlist deleted → removed
|
||||
* - a playlist's dumps changed → its `dumpCount` updated
|
||||
* Applies the socket's playlist events to the list set through
|
||||
* `setPlaylists`. A created playlist goes first, an updated one changes in
|
||||
* place or leaves when it turns private (unless `isOwner`), a deleted one
|
||||
* leaves, and a
|
||||
* playlist's `dumpCount` follows its dumps.
|
||||
*/
|
||||
export function usePlaylistListSync(
|
||||
setPlaylists: (fn: (prev: Playlist[]) => Playlist[]) => void,
|
||||
@@ -69,17 +68,14 @@ export function usePlaylistListSync(
|
||||
if (ev.type === "updated" && ev.playlist) {
|
||||
const idx = prev.findIndex((p) => p.id === ev.playlist!.id);
|
||||
if (idx === -1) {
|
||||
// Playlist was removed (went private). Re-add if it's now public and
|
||||
// visible to this list. noNewEntries lists (followed) skip this since
|
||||
// we can't verify the follow relationship is still active.
|
||||
// Left the list when it turned private, and may be back. A followed
|
||||
// list can't tell whether the user still follows it.
|
||||
if (noNewEntries) return prev;
|
||||
if (ownerId && ev.playlist.userId !== ownerId) return prev;
|
||||
if (!ev.playlist.isPublic && !isOwner) return prev;
|
||||
// skipReinsert: caller handles re-insertion at the correct position.
|
||||
if (optionsRef.current?.skipReinsert) return prev;
|
||||
return [ev.playlist, ...prev];
|
||||
}
|
||||
// Remove if it became private and the viewer can't see private playlists
|
||||
if (!ev.playlist.isPublic && !isOwner) {
|
||||
return prev.filter((p) => p.id !== ev.playlist!.id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user