v3: src/ is documented like api/ and shared/, and the reference covers the whole repository — the frontend had no module docs, JSDoc on some components and hooks and not on others, props typed inline in half the components and in private interfaces in the rest, // comments on declarations, and no return types on 147 exported functions, so deno doc --lint over src/ found 637 errors. The rule api/ follows is applied to every non-test module of src/: a /** … @module */ block, JSDoc on every export and every field of an exported interface, /** */ for declarations and // for notes, verbs for functions ("Renders…" for a component), parameters named in the prose, {@link} only for symbols in scope, and a page's JSDoc names its route and who may see it (App.tsx's module doc lists the route table). For deno doc to show them, every exported function states its return type: components React.JSX.Element (or | null, or a portal), memo and forwardRef components their exotic types, contexts React.Context<…>, and a hook returning several things a documented Use<Name>Result interface (useAuth, useRequiredAuth, useFading, useEmojiTrigger, useMentionAutocomplete, useDumpCardLink, usePlayRichContent; FeedCacheResult, YouTubePlaylistControls and UseApiFormReturn are renamed to match). Every component that takes props takes an exported, documented <Name>Props interface — the 64 inline prop types are lifted into one, DumpFeed gets DumpFeedProps extending MainFeedProps. React types go through the global React namespace: the four default imports of react are gone, since deno doc can't follow them. Two duplicates go too: useMentionAutocomplete's UserResult was shared/'s UserSuggestion, and JournalCard re-exported JournalShape for no one; i18n.ts no longer re-exports Lingui's i18n, which its three users now import from @lingui/core. deno task doc and doc:lint now take src/ (but its tests and the generated locales), the index page gains a Frontend section linking every module by layer and domain, mod.test.ts walks src/ as well, and the docs workflow runs on changes to src/. deno doc doesn't document npm packages, so it takes a type imported from one — react-hook-form's FieldValues in the form fields, frimousse's SkinTone — for a private one; doc:lint now goes through doc-lint.ts, which fails on every diagnostic but those, 25 of them. The README states the frontend's part of the rule. No behaviour changes; 205 backend and 628 frontend tests pass, and tsc, deno check and deno lint are clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
57
src/App.tsx
57
src/App.tsx
@@ -1,3 +1,29 @@
|
||||
/**
|
||||
* The app: its providers and its routes.
|
||||
*
|
||||
* - `/` — redirects to the default feed tab.
|
||||
* - `/~/:feedTab` — the feed of every dump ({@link Index}).
|
||||
* - `/:categorySlug/:feedTab` — a category's feed; `/:categorySlug` redirects
|
||||
* to
|
||||
* its default tab.
|
||||
* - `/dumps/:selectedDump` — a dump ({@link Dump}); `/edit` — its editor
|
||||
* ({@link DumpEdit}), signed in.
|
||||
* - `/login`, `/register` — sign-in and sign-up ({@link UserLogin},
|
||||
* {@link UserRegister}), signed out.
|
||||
* - `/reset-password` — a new password from a reset link ({@link
|
||||
* ResetPassword}).
|
||||
* - `/users/:username` and `/users/:username/~/:profileTab` — a profile
|
||||
* ({@link UserPublicProfile}); `/dumps`, `/upvoted`, `/playlists` — its full
|
||||
* lists ({@link UserDumps}, {@link UserUpvoted}, {@link UserPlaylists}).
|
||||
* - `/playlists/:playlistId` — a playlist ({@link PlaylistDetail}).
|
||||
* - `/search` and `/search/~/:searchTab` — search results ({@link Search}).
|
||||
* - `/notifications` — the user's notifications ({@link Notifications}), signed
|
||||
* in.
|
||||
* - anything else — {@link NotFound}.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { lazy, Suspense } from "react";
|
||||
import {
|
||||
BrowserRouter,
|
||||
@@ -27,6 +53,7 @@ import { ChatFab } from "./components/layout/ChatFab.tsx";
|
||||
|
||||
import "./App.css";
|
||||
|
||||
/** The feed page, loaded with its route; so is every page below. */
|
||||
const Index = lazy(() =>
|
||||
import("./pages/Index.tsx").then((m) => ({ default: m.Index }))
|
||||
);
|
||||
@@ -80,22 +107,27 @@ const NotFound = lazy(() =>
|
||||
import("./pages/NotFound.tsx").then((m) => ({ default: m.NotFound }))
|
||||
);
|
||||
|
||||
// Default feed tab for the current viewer ("followed" needs a session).
|
||||
/**
|
||||
* Returns the default feed tab for the current viewer ("followed" needs a
|
||||
* session).
|
||||
*/
|
||||
function useResolvedDefaultTab() {
|
||||
const { user } = useAuth();
|
||||
const [preferredTab] = useDefaultFeedTab();
|
||||
return preferredTab === "followed" && !user ? DEFAULT_FEED_TAB : preferredTab;
|
||||
}
|
||||
|
||||
// The query string has to survive the hop: the Web Share Target posts to "/",
|
||||
// so an Android share arrives here as `/?share_url=…` and the feed below is the
|
||||
// only thing that can act on it.
|
||||
/**
|
||||
* Redirects `/` to the default feed tab. The query string has to survive the
|
||||
* hop: the Web Share Target posts to "/", so an Android share arrives here as
|
||||
* `/?share_url=…` and the feed below is the only thing that can act on it.
|
||||
*/
|
||||
function IndexRedirect() {
|
||||
const { search } = useLocation();
|
||||
return <Navigate to={`/~/${useResolvedDefaultTab()}${search}`} replace />;
|
||||
}
|
||||
|
||||
// Bare `/<slug>` lands on that category's default feed tab.
|
||||
/** Redirects a bare `/<slug>` to that category's default feed tab. */
|
||||
function CategoryRedirect() {
|
||||
const { categorySlug } = useParams();
|
||||
const { search } = useLocation();
|
||||
@@ -107,14 +139,17 @@ function CategoryRedirect() {
|
||||
);
|
||||
}
|
||||
|
||||
// Both `/~/:feedTab` (all) and `/:categorySlug/:feedTab` render the same feed.
|
||||
// Keying by scope remounts the feed when switching categories so it refetches,
|
||||
// while switching tabs within a scope keeps it mounted (client-side re-sort).
|
||||
/**
|
||||
* Renders the feed of `/~/:feedTab` (all) and `/:categorySlug/:feedTab` alike.
|
||||
* Keying by scope remounts the feed when switching categories so it refetches,
|
||||
* while switching tabs within a scope keeps it mounted (client-side re-sort).
|
||||
*/
|
||||
function ScopedIndex() {
|
||||
const { categorySlug } = useParams();
|
||||
return <Index key={categorySlug ?? "~"} categorySlug={categorySlug} />;
|
||||
}
|
||||
|
||||
/** Renders the route table, and the floating buttons over every page. */
|
||||
function AppRoutes() {
|
||||
return (
|
||||
<>
|
||||
@@ -181,7 +216,11 @@ function AppRoutes() {
|
||||
);
|
||||
}
|
||||
|
||||
export function App() {
|
||||
/**
|
||||
* Renders the app: the theme, session, player, socket, follow and category
|
||||
* providers around the router, the chat, the routes and the global player.
|
||||
*/
|
||||
export function App(): React.JSX.Element {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<AuthProvider>
|
||||
|
||||
Reference in New Issue
Block a user