Type src/ against API and database shapes, fixing implicit anys

Adds .d.ts declarations beside the untyped JS modules imported from
TS (api.js, navConfig.js, adminSchema.js, adminNav.js, src/data/*),
with shapes taken from the server routes and migrations. get/post/
patch/del now return unknown unless the caller names the response.

Fixes found along the way:
- website/email/instagram are bare strings from splitLinks, not Link
  objects; OrganizationDetail's website and email pills rendered with
  no href or label and now link correctly.
- The chapter map falls back to FALLBACK_COLOR for a region with no
  colour instead of painting its tiles black.

Adds defineSection() so each page manifest entry's props are checked
against its own Component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Zaldimmar 2026-09-25 04:13:46 -05:00
parent 5286cae9ca
commit 2428f4412a
39 changed files with 1318 additions and 343 deletions

10
src/data/bannerConfig.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
/* Types for bannerConfig.js. */
export type BannerPart = { text: string; href?: string; external?: boolean };
export declare const SITE_BANNER: {
enabled: boolean;
id: string;
content: BannerPart[];
dismissible: boolean;
};

47
src/data/chapters.d.ts vendored Normal file
View file

@ -0,0 +1,47 @@
/* Types for chapters.js: GET /organizations rows with their
kind-specific details lifted to the top level. */
import type { OrganizationListItem, RegionArea, RegionScope } from "../lib/useContent.ts";
import type { AreaSlice, RegionAreaRow } from "./mapGrid.js";
export { initialsFor } from "./organizations.js";
export type Region = OrganizationListItem & {
scope: RegionScope | null;
map_note: string | null;
areas: RegionArea[];
};
export type Chapter = OrganizationListItem & {
region_id: string | null;
region_name: string | null;
region_color: string | null;
meets: string | null;
started: string | null;
/** The map tile this chapter lights up, or null if it has none. */
area_code: string | null;
};
export type Community = {
loading: boolean;
error: Error | null;
regions: Region[];
regionAreas: RegionAreaRow[];
regionById: Record<string, Region>;
domestic: Region[];
international: Region[];
virtual: Region[];
chapters: Chapter[];
chaptersIn: (regionId: string) => Chapter[];
slices: Record<string, AreaSlice[]>;
chapterCounts: Record<string, number>;
regionsForArea: (areaCode: string) => Region[];
areasLabelFor: (regionId: string) => string;
subtextFor: (region: Pick<Region, "id" | "map_note">) => string;
};
export declare function useCommunity(): Community;

27
src/data/eventData.d.ts vendored Normal file
View file

@ -0,0 +1,27 @@
/* Types for eventData.js. Rows are GET /events as shapeEvent in
server/src/routes/content.js sends them. */
import type { EventType } from "../lib/eventTypes.ts";
import type { EventListItem } from "../lib/useContent.ts";
export type EventFilter = {
section?: string;
host?: string;
status?: EventListItem["status"];
type?: EventType | EventType[];
};
export declare function useEvents(filter?: EventFilter): {
events: EventListItem[];
loading: boolean;
error: Error | null;
};
export declare function splitByStatus<T extends { status: string }>(
events?: T[],
): { upcoming: T[]; past: T[] };
export declare function typesPresent<D extends { id: string }>(
events?: Array<{ event_type: string }>,
declared?: readonly D[],
): D[];

7
src/data/feedbackTypes.d.ts vendored Normal file
View file

@ -0,0 +1,7 @@
/* Types for feedbackTypes.js. */
export type FeedbackType = { id: string; label: string; hint: string };
export declare const FEEDBACK_TYPES: FeedbackType[];
export declare function feedbackTypeLabel(id: string): string;

51
src/data/mapGrid.d.ts vendored Normal file
View file

@ -0,0 +1,51 @@
/* Types for mapGrid.js. */
import type { RegionArea } from "../lib/useContent.ts";
export declare const GRID_COLS: number;
export declare const GRID_ROWS: number;
export declare const AREA_NAMES: Record<string, string>;
/** One tile: a state, or a band such as CANADA with a span. */
export type MapArea = {
code: string;
name: string;
col: number;
row: number;
span: number;
isState: boolean;
};
export declare const AREAS: readonly MapArea[];
export declare const AREA_BY_CODE: Record<string, MapArea>;
/** A region_areas row flattened with its region, as buildAreaSlices takes it. */
export type RegionAreaRow = RegionArea & { region_id: string };
/** One region's share of a tile. */
export type AreaSlice = {
regionId: string;
name: string;
color: string | null | undefined;
share: number;
edge: RegionArea["edge"];
note: string | null;
};
type Locatable = {
is_online?: boolean;
country?: string | null;
state_code?: string | null;
};
export declare function areaForChapter(chapter: Locatable | null | undefined): string | null;
export declare function buildAreaSlices(
regionAreas?: RegionAreaRow[],
regions?: Array<{ id: string; name: string; color?: string | null }>,
): Record<string, AreaSlice[]>;
export declare function countChaptersByArea(chapters?: Locatable[]): Record<string, number>;
export declare function areasLabel(regionId: string, regionAreas?: RegionAreaRow[]): string;

21
src/data/organizations.d.ts vendored Normal file
View file

@ -0,0 +1,21 @@
/* Types for organizations.js. Rows are GET /organizations as
shapeOrganization in server/src/routes/content.js sends them. */
import type { OrgKind } from "../lib/hrefs.ts";
import type { OrganizationListItem, RegionArea } from "../lib/useContent.ts";
export declare function useOrganizations(kind?: OrgKind): {
organizations: OrganizationListItem[];
loading: boolean;
error: Error | null;
};
export declare function orgPath(
org: { id: string; kind?: string | null } | null | undefined,
): string | null;
export declare function areasSentence(
areas?: Array<Pick<RegionArea, "area_code" | "note">>,
): string;
export declare function initialsFor(name?: string): string;