diff --git a/js/core/src/hooks/use-actor-typeahead.ts b/js/core/src/hooks/use-actor-typeahead.ts index 7e9ec7ed4..8643446f6 100644 --- a/js/core/src/hooks/use-actor-typeahead.ts +++ b/js/core/src/hooks/use-actor-typeahead.ts @@ -1,11 +1,6 @@ // Debounced handle typeahead for the login form. Hits the public // Bluesky API's searchActorsTypeahead endpoint and returns the top N -// matches. Platform-agnostic: just fetch + state, no React Native or -// web-only APIs. -// -// Mirrors js/app/hooks/useActorTypeahead.tsx so both surfaces share -// the same UX (minimum query length, debounce window, request-rate -// floor, abort-on-stale). +// matches. import { useEffect, useRef, useState } from "react"; export interface Actor { @@ -92,9 +87,9 @@ export function useActorTypeahead(query: string): TypeaheadResult { if (!controller.signal.aborted) { const newActors: Actor[] = data.actors || []; - // Skip the setState if the result is identical to what we - // already have — keeps the input stable when the typeahead - // returns the same matches on a re-fetch. + // Skip setState if the result matches what we already have. + // Keeps the input stable when the typeahead returns the same + // matches on a re-fetch. const actorsChanged = newActors.length !== actorsRef.current.length || newActors.some( diff --git a/js/core/src/lib/pds-hosts.ts b/js/core/src/lib/pds-hosts.ts index 545038ca2..de9224fbc 100644 --- a/js/core/src/lib/pds-hosts.ts +++ b/js/core/src/lib/pds-hosts.ts @@ -1,14 +1,7 @@ -// PDS (Personal Data Server) host list shown during signup. The -// streamplace user needs a PDS before they can sign in — this is the -// curated list we present to new users, plus an option to enter a -// custom host. -// -// `handlePolicyDocs` is a special case: hosts that have a handle -// policy (e.g. Blacksky's "you must follow this account") require the -// user to check a box before continuing. We pre-shuffle the list once -// at module load so the order feels fresh on each visit, but always -// pin a non-policy host to the top so the first option is always -// ready to go without an extra checkbox. +// PDS host list shown during signup. `handlePolicyDocs` hosts require +// the user to check a box before continuing. We pre-shuffle the list +// at module load, but pin a non-policy host to the top so the first +// option is always ready without an extra checkbox. export interface PdsHost { value: string; label: string; diff --git a/js/core/src/livestream-store/chat-reducer.test.ts b/js/core/src/livestream-store/chat-reducer.test.ts index 7f356f2d8..d744a0c95 100644 --- a/js/core/src/livestream-store/chat-reducer.test.ts +++ b/js/core/src/livestream-store/chat-reducer.test.ts @@ -4,7 +4,7 @@ import { reduceChat } from "./chat-reducer"; import type { LivestreamState } from "./state"; function makeMsg( - overrides: Partial & { + overrides: Omit, "uri"> & { uri?: string; did?: string; text?: string; @@ -20,13 +20,6 @@ function makeMsg( return { uri, cid: "cid-" + uri, - author: { did, handle: did + ".bsky.social" }, - record: { - $type: "place.stream.chat.message", - text, - createdAt, - streamer: "did:plc:streamer", - }, indexedAt: createdAt, chatProfile: { color: { red: 255, green: 255, blue: 255 } }, ...overrides, @@ -257,7 +250,7 @@ describe("reduceChat", () => { chatProfile: profile as any, }); const state2 = reduceChat(state, [msg2], [], []); - // Same ref — no re-render trigger + // Same ref, no re-render trigger. expect(state2.authors["did:plc:a"]).toBe(authorsBefore); }); }); diff --git a/js/core/src/livestream-store/chat-reducer.ts b/js/core/src/livestream-store/chat-reducer.ts index 4e263fde4..e4b301040 100644 --- a/js/core/src/livestream-store/chat-reducer.ts +++ b/js/core/src/livestream-store/chat-reducer.ts @@ -45,7 +45,7 @@ const profileIsDifferent = ( return true; } if (!newProfile.color) { - // idk. shouldn't happen. + // shouldn't happen, but don't overwrite an existing color with nothing. return false; } const { red: newRed, green: newGreen, blue: newBlue } = newProfile.color; diff --git a/js/web/src/components/auth/login-modal.tsx b/js/web/src/components/auth/login-modal.tsx index 6ca369e3e..431f4385d 100644 --- a/js/web/src/components/auth/login-modal.tsx +++ b/js/web/src/components/auth/login-modal.tsx @@ -140,7 +140,7 @@ export function LoginModal() { await signIn(trimmed); } catch { if (!popup) { - // Popup never opened — browser blocked it. Fall back to the + // Popup never opened; browser blocked it. Fall back to the // full-page /login route, which always does a full-page // redirect and works regardless of popup settings. goToLoginPage(); diff --git a/js/web/src/components/auth/pds-host-selector-modal.tsx b/js/web/src/components/auth/pds-host-selector-modal.tsx index b4a1e8300..19811e570 100644 --- a/js/web/src/components/auth/pds-host-selector-modal.tsx +++ b/js/web/src/components/auth/pds-host-selector-modal.tsx @@ -1,8 +1,4 @@ -// PDS host selector shown during signup. Mirrors -// js/app/components/login/pds-host-selector-modal.tsx but uses the -// web's Dialog primitive and Tailwind classes instead of React Native -// styles. The host list itself lives in @streamplace/core so the app -// and web share it. +// PDS host selector shown during signup. // // The flow: user clicks "Sign Up" in the login modal → login modal // closes and this one opens → user picks a PDS (or enters a custom diff --git a/js/web/src/components/dashboard/activity-picker.tsx b/js/web/src/components/dashboard/activity-picker.tsx index e3fd081af..ad0882156 100644 --- a/js/web/src/components/dashboard/activity-picker.tsx +++ b/js/web/src/components/dashboard/activity-picker.tsx @@ -4,10 +4,10 @@ import { cn } from "@/lib/utils"; import { Search, X } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import type { PlaceStreamLivestream } from "streamplace"; +import { place } from "streamplace"; /** - * Activity picker for livestreams. Mirrors the RN's ActivityPicker: + * Activity picker for livestreams: * - "Game" mode: search for actual games via the streamplace xrpc, * show cover art + name + genres. Stores an `ActivityGame` value * with `$type: "place.stream.defs#activityGame"`. @@ -38,9 +38,9 @@ const ACTIVITY_LABELS: Array<{ value: string; display: string }> = [ type Mode = "game" | "label"; interface ActivityPickerProps { - value: PlaceStreamLivestream.Record["activity"] | undefined; + value: place.stream.livestream.Main["activity"] | undefined; onChange: ( - activity: PlaceStreamLivestream.Record["activity"] | undefined, + activity: place.stream.livestream.Main["activity"] | undefined, ) => void; } @@ -85,12 +85,12 @@ export function ActivityPicker({ value, onChange }: ActivityPickerProps) { debounceRef.current = setTimeout(async () => { setSearching(true); try { - const res = await agent.place.stream.game.search({ + const res = await agent.client.call(place.stream.game.search, { q: query, limit: 8, }); const games: GameResult[] = []; - for (const result of res.data.results ?? []) { + for (const result of res.results ?? []) { // Type guard: only game summary views have the fields we need if (!result || typeof result !== "object" || !("name" in result)) { continue; @@ -129,7 +129,7 @@ export function ActivityPicker({ value, onChange }: ActivityPickerProps) { $type: "place.stream.defs#activityGame", uri: game.uri, name: game.name, - } as unknown as PlaceStreamLivestream.Record["activity"]); + } as unknown as place.stream.livestream.Main["activity"]); setQuery(""); setResults([]); }; @@ -249,7 +249,7 @@ export function ActivityPicker({ value, onChange }: ActivityPickerProps) { : ({ $type: "place.stream.defs#activityLabel", label: labelValue, - } as unknown as PlaceStreamLivestream.Record["activity"]), + } as unknown as place.stream.livestream.Main["activity"]), ) } className={cn( diff --git a/js/web/src/components/dashboard/control-panel.tsx b/js/web/src/components/dashboard/control-panel.tsx index 46b7a2c7d..aeab745c3 100644 --- a/js/web/src/components/dashboard/control-panel.tsx +++ b/js/web/src/components/dashboard/control-panel.tsx @@ -115,9 +115,9 @@ export function ControlPanel({ store, user }: ControlPanelProps) { /** * Unified drop handler. Supports two kinds of source: - * - leaf source (`leaf-{path}`): move the widget — split the target + * - leaf source (`leaf-{path}`): move the widget; split the target * and remove the source from its original slot - * - drawer source (`widget-{key}`): add the widget — split the target + * - drawer source (`widget-{key}`): add the widget; split the target * with the dragged widget, no source removal */ const handleDragEnd = useCallback( @@ -174,7 +174,7 @@ export function ControlPanel({ store, user }: ControlPanelProps) { const finalSourceWidget = sourceWidget; let nextLayout: LayoutNode; - // Dropping onto a blank slot just fills it — no split, since a + // Dropping onto a blank slot just fills it; no split, since a // split with a blank in one half would be weird. For leaf // sources we also remove the source so the widget effectively // moves into the empty slot. diff --git a/js/web/src/components/dashboard/dashboard-chrome.tsx b/js/web/src/components/dashboard/dashboard-chrome.tsx index 24814dc43..66f059016 100644 --- a/js/web/src/components/dashboard/dashboard-chrome.tsx +++ b/js/web/src/components/dashboard/dashboard-chrome.tsx @@ -39,7 +39,7 @@ interface NavLink { labelKey: string; } -/** Main nav — appears above the Settings group. */ +/** Main nav; appears above the Settings group. */ const MAIN_NAV: NavLink[] = [ { to: "/dashboard", icon: LayoutGrid, labelKey: "control-panel" }, { @@ -54,7 +54,7 @@ const MAIN_NAV: NavLink[] = [ }, ]; -/** Settings group — appears under a "Settings" header. */ +/** Settings group; appears under a "Settings" header. */ const SETTINGS_NAV: NavLink[] = [ { to: "/dashboard/stream", icon: Radio, labelKey: "stream-settings" }, { to: "/dashboard/keys", icon: Key, labelKey: "key-manager" }, diff --git a/js/web/src/components/dashboard/dashboard-metrics.tsx b/js/web/src/components/dashboard/dashboard-metrics.tsx index 90ccb2acb..9cf4eef5e 100644 --- a/js/web/src/components/dashboard/dashboard-metrics.tsx +++ b/js/web/src/components/dashboard/dashboard-metrics.tsx @@ -13,7 +13,7 @@ import { useDashboardStore } from "./dashboard-store-context"; /** How often we sample the live metrics into the history buffer. */ const SAMPLE_INTERVAL_MS = 2000; -/** Range presets — sliding-window lengths. */ +/** Range presets; sliding-window lengths. */ export type DashboardRange = "1m" | "2m" | "5m" | "10m" | "25m"; export const DASHBOARD_RANGES: { @@ -78,7 +78,7 @@ interface ProviderProps { * history buffer. Mount this high in the tree (the dashboard chrome) so * the buffer keeps populating while the user navigates between dashboard * sub-routes. When the user is on a non-dashboard page the provider - * unmounts and tracking stops — the buffer is fresh on remount. + * unmounts and tracking stops; the buffer is fresh on remount. */ export function DashboardMetricsProvider({ children }: ProviderProps) { const store = useDashboardStore(); diff --git a/js/web/src/components/dashboard/layout-renderer.tsx b/js/web/src/components/dashboard/layout-renderer.tsx index d7d6da5d1..8cebbe9fc 100644 --- a/js/web/src/components/dashboard/layout-renderer.tsx +++ b/js/web/src/components/dashboard/layout-renderer.tsx @@ -263,7 +263,7 @@ export function findElementById(id: string): HTMLElement | null { } /** - * dnd-kit's `Position` is a `ValueHistory` — it tracks `.current` (the + * dnd-kit's `Position` is a `ValueHistory`; it tracks `.current` (the * latest pointer position) and `.initial` (where the drag started). It * does not expose direct `.x`/`.y` properties, so we read `.current`. */ diff --git a/js/web/src/components/dashboard/layout.ts b/js/web/src/components/dashboard/layout.ts index 6a683dfb7..e96f5ddb5 100644 --- a/js/web/src/components/dashboard/layout.ts +++ b/js/web/src/components/dashboard/layout.ts @@ -202,7 +202,7 @@ export function replaceLeafWithSplit( * remains a valid layout (splits have >= 2 children). * * If the path points at the root leaf (path length 0), the root is - * returned unchanged — there's no sensible empty layout to produce. + * returned unchanged; there's no sensible empty layout to produce. */ export function removeLeafAt(root: LayoutNode, path: number[]): LayoutNode { if (path.length === 0) return root; diff --git a/js/web/src/components/dashboard/multistream-status.tsx b/js/web/src/components/dashboard/multistream-status.tsx index 14e478dea..301edcbbd 100644 --- a/js/web/src/components/dashboard/multistream-status.tsx +++ b/js/web/src/components/dashboard/multistream-status.tsx @@ -1,3 +1,4 @@ +import { place } from "streamplace"; import { cn } from "@/lib/utils"; import type { LivestreamStore } from "@streamplace/core"; import { Globe, Loader2 } from "lucide-react"; @@ -56,7 +57,7 @@ function statusColor(target: MultistreamTarget): string { /** * Lists multistream targets with active/inactive toggles and connection - * status. Port of MultistreamStatus from the RN app. + * status. */ export function MultistreamStatusWidget({ store }: { store: LivestreamStore }) { const { t } = useTranslation("common"); @@ -69,10 +70,10 @@ export function MultistreamStatusWidget({ store }: { store: LivestreamStore }) { if (!agent) return; try { setLoading(true); - const response = await agent.place.stream.multistream.listTargets({ + const response = await agent.client.call(place.stream.multistream.listTargets, { limit: 50, }); - setTargets(response.data.targets as unknown as MultistreamTarget[]); + setTargets(response.targets as unknown as MultistreamTarget[]); } catch (error) { console.error("Failed to load multistream targets:", error); setTargets([]); @@ -86,7 +87,7 @@ export function MultistreamStatusWidget({ store }: { store: LivestreamStore }) { if (!agent) return; try { setToggling((prev) => new Set(prev).add(target.uri)); - await agent.place.stream.multistream.putTarget({ + await agent.client.call(place.stream.multistream.putTarget, { multistreamTarget: { ...target.record, $type: "place.stream.multistream.target" as const, diff --git a/js/web/src/components/dashboard/stream-health.tsx b/js/web/src/components/dashboard/stream-health.tsx index 117763823..d4d1e06e6 100644 --- a/js/web/src/components/dashboard/stream-health.tsx +++ b/js/web/src/components/dashboard/stream-health.tsx @@ -77,7 +77,7 @@ const METRIC_KEYS = Object.keys(METRICS) as MetricId[]; /** * Compact number formatter for chart axis labels: 1500 → "1.5k", * 2400 → "2.4k", 6000 → "6k", 12000 → "12k", 2_500_000 → "2.5M". Anything - * under 1000 is shown as-is. The unit is implicit — viewers read the chips + * under 1000 is shown as-is. The unit is implicit; viewers read the chips * to know whether the axis is kbps, ms, msg/min, etc. */ function compactNumber(v: number): string { @@ -107,7 +107,7 @@ function getConnectionQuality( /** * Stream Health widget. All heavy lifting (metric sampling, history * buffer, range management) lives in `DashboardMetricsProvider` higher - * in the tree — this component only reads the pre-computed data and + * in the tree; this component only reads the pre-computed data and * renders the charts, chips, and footer. */ export function StreamHealthWidget({ store }: { store: LivestreamStore }) { @@ -119,7 +119,7 @@ export function StreamHealthWidget({ store }: { store: LivestreamStore }) { ); // Track the chart container's aspect ratio. The two charts split along - // the longest axis — landscape → vertical split (left/right), + // the longest axis; landscape → vertical split (left/right), // portrait → horizontal split (top/bottom). const chartContainerRef = useRef(null); const [isLandscape, setIsLandscape] = useState(true); @@ -250,7 +250,7 @@ export function StreamHealthWidget({ store }: { store: LivestreamStore }) { )} - {/* Metric chips — each shows its current value always, and toggles + {/* Metric chips; each shows its current value always, and toggles that metric in/out of the chart when clicked. */}
{METRIC_KEYS.map((id) => { @@ -309,7 +309,7 @@ export function StreamHealthWidget({ store }: { store: LivestreamStore }) { })}
- {/* Footer stats — avg + peak per active metric (current is in the chip) */} + {/* Footer stats; avg + peak per active metric (current is in the chip) */} {activeMetrics.size > 0 && (
{METRIC_KEYS.filter((id) => activeMetrics.has(id)).map((id) => { diff --git a/js/web/src/components/dashboard/stream-monitor.tsx b/js/web/src/components/dashboard/stream-monitor.tsx index 20cd614c4..562034203 100644 --- a/js/web/src/components/dashboard/stream-monitor.tsx +++ b/js/web/src/components/dashboard/stream-monitor.tsx @@ -11,7 +11,7 @@ import { Player } from "../player/player"; /** * Self-preview video monitor for the dashboard. Shows the streamer's * own stream with connection status, stream title, and a hide/show - * toggle. Port of StreamMonitor from the RN app. + * toggle. */ export function StreamMonitorWidget({ store, diff --git a/js/web/src/components/dashboard/upload-form.tsx b/js/web/src/components/dashboard/upload-form.tsx index 9d9cfc079..aafa0c6aa 100644 --- a/js/web/src/components/dashboard/upload-form.tsx +++ b/js/web/src/components/dashboard/upload-form.tsx @@ -215,7 +215,7 @@ export function UploadForm({ {upload.file && ( - {upload.file.type || "unknown"} —{" "} + {upload.file.type || "unknown"}{" "} {humanBytes(upload.file.size)} )} diff --git a/js/web/src/components/player/hls-player.tsx b/js/web/src/components/player/hls-player.tsx index 2882b044f..9a758ea1b 100644 --- a/js/web/src/components/player/hls-player.tsx +++ b/js/web/src/components/player/hls-player.tsx @@ -1,5 +1,5 @@ // HLS backend for the component. Owns hls.js setup, manifest -// loading, and error recovery. Renders nothing — the video element +// loading, and error recovery. Renders nothing; the video element // lives in so the chrome (controls, fullscreen, error display) // is shared across backends. When a WebRTC backend lands it will be a // sibling of this file with the same shape. @@ -105,11 +105,11 @@ export function HLSPlayer({ } switch (data.type) { case Hls.ErrorTypes.NETWORK_ERROR: - onError?.("Network error — retrying"); + onError?.("Network error. Retrying."); hls.startLoad(); return; case Hls.ErrorTypes.MEDIA_ERROR: - onError?.("Media error — recovering"); + onError?.("Media error. Recovering."); hls.recoverMediaError(); return; default: diff --git a/js/web/src/components/player/player-controls.tsx b/js/web/src/components/player/player-controls.tsx index 7a4ee6cfb..7bc2ff351 100644 --- a/js/web/src/components/player/player-controls.tsx +++ b/js/web/src/components/player/player-controls.tsx @@ -37,7 +37,7 @@ export type PlayerControlsProps = { containerRef: RefObject; /** Live streams hide the scrubber and show a "LIVE" badge in its place. */ isLive: boolean; - /** Parent controls visibility — auto-hide logic lives in the HLSPlayer. */ + /** Parent controls visibility; auto-hide logic lives in the HLSPlayer. */ showControls: boolean; /** When true, show a centered play button (no control bar). */ bigPlay: boolean; @@ -257,7 +257,7 @@ export function PlayerControls({ visible ? "opacity-100" : "pointer-events-none opacity-0", )} > - {/* Top gradient — subtle hint that there's a controls bar. + {/* Top gradient; subtle hint that there's a controls bar. Not strictly needed since the bar has its own background, but gives the play button overlay a darker canvas. */}
diff --git a/js/web/src/components/player/player.stories.tsx b/js/web/src/components/player/player.stories.tsx index db760de78..2357cff17 100644 --- a/js/web/src/components/player/player.stories.tsx +++ b/js/web/src/components/player/player.stories.tsx @@ -24,7 +24,7 @@ export default meta; type Story = StoryObj; /** - * Inactive player — shows the poster image only, no video backend. + * Inactive player; shows the poster image only, no video backend. */ export const Inactive: Story = { args: { @@ -48,7 +48,7 @@ export const LiveHLS: Story = { }; /** - * VOD mode — same backend but the chrome shows a scrubber instead of + * VOD mode; same backend but the chrome shows a scrubber instead of * the "LIVE" indicator. */ export const VOD: Story = { @@ -60,7 +60,7 @@ export const VOD: Story = { }; /** - * Inactive with a fallback poster — shown when the stream is offline. + * Inactive with a fallback poster; shown when the stream is offline. */ export const Offline: Story = { args: { diff --git a/js/web/src/components/player/player.tsx b/js/web/src/components/player/player.tsx index 2945e07b5..effd4ef22 100644 --- a/js/web/src/components/player/player.tsx +++ b/js/web/src/components/player/player.tsx @@ -1,7 +1,7 @@ // Public component. Owns the video element and the shared // chrome (controls overlay, error display, fullscreen, auto-hide, // click-to-toggle). The actual playback source is handled by a -// backend — currently , soon and others. +// backend, currently , soon and others. // Backends are rendered as siblings of the