source for getorbyt.com getorbyt.com/
client bsky orbytapp app orbyt bluesky getorbyt orbytvideo atproto video
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add JSDoc docstrings across all utility and i18n modules

Brings docstring coverage from ~33% to well above the 80% threshold.
Every exported and non-trivial internal function now has a /** */ docstring
describing its contract, not just its name.

Files updated:
- src/utils/bluesky-api.ts — all 9 functions (fetchWithTimeout, fetchProfile,
resolveHandle, fetchPost, extractPostId, truncateText, fetchVideoPosts,
toAbsoluteUrl, getImageMimeType) + 2 export interfaces
- src/utils/richtext.ts — escapeHtml, parseRichText, parseRichTextTruncated
- src/utils/video-ambient-backdrop.ts — oklchToCss, normalizeHue,
getVideoAmbientBackdropGradientColors, hashVideoAmbientBackdropSeed,
getVideoAmbientBackdropInlineStyle
- src/utils/orbyt-api.ts — ColorData, OrbytApiBinding, getColor
- src/utils/ios-distribution.ts — normalizeCountryCode, isAltstorePalRegion,
getIosDownloadOptions, getIosDownloadOptionsFromRequest, iosHrefForOptions
- src/utils/profile-colors.ts — getRelativeLuminance, blendColors
- src/middleware.ts — matchAcceptLanguage, onRequest (full detection-order docs)
- src/i18n/utils.ts — isValidLocale, useTranslations, localeToHtmlLang,
getLocaleUrl, hreflangLinks
- src/actions/index.ts — loadMorePosts

https://claude.ai/code/session_01Ek5Qwg11ePJwKdeS1x6LwS

Claude a57073b3 ca9a95d2

+103 -12
+1
src/actions/index.ts
··· 5 5 import { getVideoAmbientBackdropInlineStyle } from "../utils/video-ambient-backdrop"; 6 6 7 7 export const server = { 8 + /** Loads the next page of video posts for a handle using the stored cursor. */ 8 9 loadMorePosts: defineAction({ 9 10 input: z.object({ 10 11 handle: z.string(),
+12
src/i18n/utils.ts
··· 185 185 tr, 186 186 } 187 187 188 + /** Returns `true` if `locale` is `"en"` or one of the supported non-default locales. */ 188 189 export function isValidLocale(locale: string): boolean { 189 190 return locale === DEFAULT_LOCALE || (SUPPORTED_LOCALES as readonly string[]).includes(locale) 190 191 } 191 192 193 + /** 194 + * Returns a `t(key)` function scoped to `locale`. 195 + * Lookup order: locale dict → fallback locale dict (e.g. es-MX → es-419) → English → key itself. 196 + */ 192 197 export function useTranslations(locale: string) { 193 198 const dict = translations[locale] ?? {} 194 199 const fallback = FALLBACKS[locale] ··· 202 207 } 203 208 } 204 209 210 + /** Maps a locale string to the value for the HTML `lang` attribute (currently 1:1). */ 205 211 export function localeToHtmlLang(locale: string): string { 206 212 return locale 207 213 } 208 214 215 + /** Constructs a locale-prefixed URL path; English returns the bare path (no `/en/` prefix). */ 209 216 export function getLocaleUrl(locale: string, path = '/'): string { 210 217 const normalizedPath = path.startsWith('/') ? path : `/${path}` 211 218 if (locale === DEFAULT_LOCALE) return normalizedPath 212 219 return `/${locale}${normalizedPath === '/' ? '/' : normalizedPath}` 213 220 } 214 221 222 + /** 223 + * Generates `<link rel="alternate" hreflang>` objects for a canonical path. 224 + * Includes `x-default` and `en` pointing to the un-prefixed URL, plus one 225 + * entry per supported locale at `/{locale}{canonicalPath}`. 226 + */ 215 227 export function hreflangLinks(canonicalPath: string): Array<{ hreflang: string; href: string }> { 216 228 const base = 'https://getorbyt.com' 217 229 const links = [
+19
src/middleware.ts
··· 3 3 4 4 const LOCALE_COOKIE = 'orbyt-locale' 5 5 6 + /** 7 + * Parses the `Accept-Language` header and returns the best-matching supported locale. 8 + * Tries an exact case-insensitive match first, then a base-language prefix match 9 + * (e.g. `de-AT` → `de`, `pt-PT` → `pt-BR`), then English variants, then defaults to `en`. 10 + */ 6 11 function matchAcceptLanguage(header: string | null): string { 7 12 if (!header) return DEFAULT_LOCALE 8 13 ··· 34 39 return DEFAULT_LOCALE 35 40 } 36 41 42 + /** 43 + * Astro middleware that resolves the active locale for every request and stores 44 + * it in `context.locals.locale`. Also reads Cloudflare geo-IP (`cf.country` or 45 + * `CF-IPCountry` header) into `context.locals.countryCode` for geo-aware flag display. 46 + * 47 + * Detection order: 48 + * 1. `x-orbyt-locale` header set by an earlier internal rewrite (skips re-detection) 49 + * 2. URL path prefix — `/de/`, `/ja/`, etc. (rewrites internally, strips prefix) 50 + * 3. `orbyt-locale` cookie (persists user's manual language choice) 51 + * 4. `Accept-Language` header 52 + * 5. Default: `en` 53 + * 54 + * Non-English locales detected at the root path (`/`) are redirected to `/{locale}/`. 55 + */ 37 56 export const onRequest = defineMiddleware(async (context, next) => { 38 57 // Read Cloudflare geo-IP country — available via the cf object in Workers, 39 58 // or the CF-IPCountry header when running behind Cloudflare.
+33 -12
src/utils/bluesky-api.ts
··· 2 2 const SITE_URL = 'https://getorbyt.com'; 3 3 const API_TIMEOUT = 5000; 4 4 5 + /** Normalized profile fields returned from `app.bsky.actor.getProfile`. */ 5 6 export interface ProfileData { 6 7 handle: string; 7 8 displayName?: string; ··· 10 11 did: string; 11 12 } 12 13 14 + /** Normalized post fields used by SSR profile and post pages. */ 13 15 export interface PostData { 14 16 text: string; 15 17 createdAt?: string; ··· 83 85 'User-Agent': 'Orbyt/1.0 (https://getorbyt.com)', 84 86 }; 85 87 88 + /** Wraps `fetch` with an `AbortController` timeout so hung API calls don't block SSR. */ 86 89 async function fetchWithTimeout(url: string, timeout: number = API_TIMEOUT): Promise<Response> { 87 90 const controller = new AbortController(); 88 91 const timeoutId = setTimeout(() => controller.abort(), timeout); ··· 96 99 } 97 100 } 98 101 102 + /** Fetches a Bluesky profile by handle; returns `null` on any API or network error. */ 99 103 export async function fetchProfile(handle: string): Promise<ProfileData | null> { 100 104 try { 101 105 const url = `${BLUESKY_API_BASE}/app.bsky.actor.getProfile?actor=${encodeURIComponent(handle)}`; 102 106 const response = await fetchWithTimeout(url); 103 - 107 + 104 108 if (!response.ok) return null; 105 - 109 + 106 110 const data = (await response.json()) as BskyProfileResponse; 107 111 if (data.error || !data.handle || !data.did) return null; 108 112 ··· 118 122 } 119 123 } 120 124 125 + /** Resolves an AT Protocol handle to its DID via `com.atproto.identity.resolveHandle`. */ 121 126 async function resolveHandle(handle: string): Promise<string | null> { 122 127 try { 123 128 const url = `${BLUESKY_API_BASE}/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`; 124 129 const response = await fetchWithTimeout(url); 125 - 130 + 126 131 if (!response.ok) return null; 127 - 132 + 128 133 const data = (await response.json()) as { did?: string }; 129 134 return data.did || null; 130 135 } catch { ··· 132 137 } 133 138 } 134 139 140 + /** 141 + * Fetches a single post by handle + rkey (post ID). 142 + * Resolves the handle to a DID first, then constructs the AT URI for `getPosts`. 143 + * Handles plain video embeds and `recordWithMedia` wrappers; falls back to image thumbnails. 144 + */ 135 145 export async function fetchPost(handle: string, postId: string): Promise<PostData | null> { 136 146 try { 137 147 const did = await resolveHandle(handle); 138 148 if (!did) return null; 139 - 149 + 140 150 const atUri = `at://${did}/app.bsky.feed.post/${postId}`; 141 151 const url = `${BLUESKY_API_BASE}/app.bsky.feed.getPosts?uris=${encodeURIComponent(atUri)}`; 142 152 const response = await fetchWithTimeout(url); 143 - 153 + 144 154 if (!response.ok) return null; 145 - 155 + 146 156 const data = (await response.json()) as BskyGetPostsResponse; 147 157 const posts = data.posts; 148 158 if (!posts || posts.length === 0) return null; ··· 152 162 let thumbnail: string | undefined; 153 163 let videoUrl: string | undefined; 154 164 let aspectRatio: { width: number; height: number } | undefined; 155 - 165 + 156 166 if (post.embed) { 157 167 if (post.embed.$type === 'app.bsky.embed.video#view') { 158 168 thumbnail = post.embed.thumbnail; ··· 186 196 thumbnail = post.embed.images[0].thumb || post.embed.images[0].fullsize; 187 197 } 188 198 } 189 - 199 + 190 200 return { 191 201 text: post.record?.text || '', 192 202 createdAt: post.record?.createdAt, ··· 208 218 } 209 219 } 210 220 221 + /** A minimal video post entry used for the infinite-scroll feed grid. */ 211 222 export interface VideoPost { 212 223 uri: string; 213 224 postId: string; ··· 215 226 caption: string; 216 227 } 217 228 229 + /** Paginated result from `fetchVideoPosts`. */ 218 230 export interface VideoFeedResult { 219 231 posts: VideoPost[]; 220 232 cursor: string | null; 221 233 } 222 234 235 + /** Extracts the rkey (last path segment) from an AT URI. */ 223 236 function extractPostId(uri: string): string | null { 224 237 if (!uri) return null; 225 238 const parts = uri.split('/'); 226 239 return parts[parts.length - 1] || null; 227 240 } 228 241 242 + /** Truncates `text` to `maxLength` characters, appending `…` if cut. */ 229 243 function truncateText(text: string, maxLength: number = 90): string { 230 244 if (!text) return ''; 231 245 if (text.length <= maxLength) return text; 232 246 return text.substring(0, maxLength).trim() + '...'; 233 247 } 234 248 249 + /** 250 + * Fetches the video-only feed for a handle using `posts_with_video` filter. 251 + * Skips posts whose embed type is not `app.bsky.embed.video#view`. 252 + * Returns an empty result (no throw) on any network or API error. 253 + */ 235 254 export async function fetchVideoPosts(handle: string, cursor?: string, limit: number = 30): Promise<VideoFeedResult> { 236 255 try { 237 256 let url = `${BLUESKY_API_BASE}/app.bsky.feed.getAuthorFeed?actor=${encodeURIComponent(handle)}&filter=posts_with_video&limit=${limit}`; ··· 240 259 } 241 260 242 261 const response = await fetchWithTimeout(url); 243 - 262 + 244 263 if (!response.ok) { 245 264 return { posts: [], cursor: null }; 246 265 } ··· 283 302 } 284 303 } 285 304 305 + /** Ensures `url` is absolute using `SITE_URL` as the base; returns `fallback` when `url` is empty. */ 286 306 export function toAbsoluteUrl(url: string | undefined, fallback: string = '/images/post/Orbyt-with-background.png'): string { 287 307 if (!url) return `${SITE_URL}${fallback}`; 288 308 if (url.startsWith('http://') || url.startsWith('https://')) return url; ··· 290 310 return `${SITE_URL}/${url}`; 291 311 } 292 312 313 + /** Infers the image MIME type from URL path extension; defaults to `image/jpeg`. */ 293 314 export function getImageMimeType(url: string): string { 294 315 if (!url) return 'image/jpeg'; 295 - 316 + 296 317 const urlLower = url.toLowerCase(); 297 - 318 + 298 319 if (urlLower.includes('.png') || urlLower.endsWith('png')) { 299 320 return 'image/png'; 300 321 }
+5
src/utils/ios-distribution.ts
··· 34 34 'SE', 35 35 ]); 36 36 37 + /** Uppercases and validates a raw CF-IPCountry value; returns `null` for unknown/Tor (`XX`, `T1`) and malformed codes. */ 37 38 function normalizeCountryCode(value: string | null | undefined): string | null { 38 39 if (value == null || value === '') return null; 39 40 const code = value.trim().toUpperCase(); ··· 41 42 return code; 42 43 } 43 44 45 + /** Returns `true` for EU member states and Japan, where AltStore PAL is the primary iOS distribution channel. */ 44 46 function isAltstorePalRegion(cfIpCountry: string | null | undefined): boolean { 45 47 const code = normalizeCountryCode(cfIpCountry); 46 48 if (code == null) return false; ··· 59 61 /** Same marketplace ID as `public/altstore/source.json` */ 60 62 const DEFAULT_APP_STORE = 'https://apps.apple.com/app/id6751679299'; 61 63 64 + /** Builds iOS download options based on the visitor's country; AltStore PAL is primary for EU and Japan. */ 62 65 export function getIosDownloadOptions( 63 66 cfIpCountry: string | null | undefined, 64 67 overrides?: Partial<Pick<IosDownloadOptions, 'altstoreSourceUrl' | 'appStoreUrl'>>, ··· 74 77 }; 75 78 } 76 79 80 + /** Reads `cf-ipcountry` from the incoming request headers and delegates to `getIosDownloadOptions`. */ 77 81 export function getIosDownloadOptionsFromRequest( 78 82 request: Request, 79 83 overrides?: Partial<Pick<IosDownloadOptions, 'altstoreSourceUrl' | 'appStoreUrl'>>, ··· 89 93 return `altstore-pal://source?url=${encodeURIComponent(sourceUrl)}`; 90 94 } 91 95 96 + /** Resolves the primary iOS download href from pre-built options (AltStore deep link or App Store URL). */ 92 97 function iosHrefForOptions(options: IosDownloadOptions): string { 93 98 return options.primary === 'altstore' 94 99 ? altstorePalSourceDeepLink(options.altstoreSourceUrl)
+7
src/utils/orbyt-api.ts
··· 1 + /** Profile color theme returned by the Orbyt color API. */ 1 2 export interface ColorData { 2 3 textColor: string; 3 4 backgroundColor: string; ··· 5 6 isBeta: boolean; 6 7 } 7 8 9 + /** Minimal interface for the Cloudflare service binding to the `orbyt-api` Worker. */ 8 10 export interface OrbytApiBinding { 9 11 fetch(request: Request | string, init?: RequestInit): Promise<Response>; 10 12 } 11 13 14 + /** 15 + * Fetches profile color theme data for a DID from the Orbyt API. 16 + * Tries targets in priority order: Cloudflare service binding → localhost (dev) → public API. 17 + * Returns `null` if all targets fail or return a non-OK status. 18 + */ 12 19 export async function getColor( 13 20 did: string, 14 21 binding?: OrbytApiBinding
+2
src/utils/profile-colors.ts
··· 4 4 */ 5 5 export const ORBYT_BLACK = '#05070a' as const; 6 6 7 + /** Computes WCAG relative luminance for a 6-digit hex color using the IEC 61966-2-1 transfer function. */ 7 8 function getRelativeLuminance(hex: string): number { 8 9 const color = hex.replace('#', ''); 9 10 const r = parseInt(color.substring(0, 2), 16) / 255; ··· 14 15 return 0.2126 * transform(r) + 0.7152 * transform(g) + 0.0722 * transform(b); 15 16 } 16 17 18 + /** Linear RGB blend of two 6-digit hex colors; `ratio` 0 = `color1`, 1 = `color2`. */ 17 19 function blendColors(color1: string, color2: string, ratio: number): string { 18 20 const r = Math.round( 19 21 parseInt(color1.slice(1, 3), 16) * (1 - ratio) + parseInt(color2.slice(1, 3), 16) * ratio
+7
src/utils/richtext.ts
··· 1 1 /** Mentions with a TLD link to /@handle; URLs become external links. */ 2 2 3 + /** Escapes `&`, `<`, `>`, `"`, and `'` so user-provided text is safe to embed in HTML. */ 3 4 function escapeHtml(text: string): string { 4 5 return text 5 6 .replace(/&/g, '&amp;') ··· 9 10 .replace(/'/g, '&#x27;'); 10 11 } 11 12 13 + /** 14 + * Converts plain Bluesky post text to HTML, turning `@mention.bsky.social` 15 + * handles into profile links and bare/www/TLD URLs into external anchor tags. 16 + * Overlap between mentions and URLs is resolved in favour of the mention. 17 + */ 12 18 export function parseRichText(text: string, options?: { 13 19 mentionClass?: string; 14 20 linkClass?: string; ··· 125 131 return result; 126 132 } 127 133 134 + /** Truncates `text` to `maxLength` characters (appending `…`) before calling `parseRichText`. */ 128 135 export function parseRichTextTruncated(text: string, maxLength: number = 90, options?: { 129 136 mentionClass?: string; 130 137 linkClass?: string;
+17
src/utils/video-ambient-backdrop.ts
··· 2 2 // Golden angle maximizes hue separation across consecutive hashes. 3 3 const GOLDEN_ANGLE = 137.50776; 4 4 5 + /** Formats an oklch color triple as a CSS `oklch(...)` string. */ 5 6 function oklchToCss(l: number, c: number, h: number): string { 6 7 return `oklch(${l.toFixed(3)} ${c.toFixed(3)} ${h.toFixed(2)})`; 7 8 } 8 9 10 + /** Wraps an arbitrary hue value into the [0, 360) range. */ 9 11 function normalizeHue(value: number): number { 10 12 const hue = value % 360; 11 13 return hue < 0 ? hue + 360 : hue; 12 14 } 13 15 16 + /** 17 + * Derives a deterministic pair of dark oklch colors from a seed URL. 18 + * Primary and secondary hues are separated by ~150–210° so the gradient 19 + * reads as two distinct tones without clashing. 20 + */ 14 21 function getVideoAmbientBackdropGradientColors(seedUrl: string | null): readonly [string, string] { 15 22 const seed = seedUrl ?? 'fallback'; 16 23 ··· 36 43 const VIDEO_AMBIENT_BACKDROP_HIGHLIGHT_OPACITY = [0.07, 0.09, 0.11, 0.13] as const; 37 44 const VIDEO_AMBIENT_BACKDROP_SCRIM_OPACITY = [0.42, 0.46, 0.5, 0.54] as const; 38 45 46 + /** 47 + * Fast, non-cryptographic djb2-style hash for a string seed. 48 + * Used to pick deterministic but visually varied backdrop parameters 49 + * without any runtime randomness that would break SSR/hydration parity. 50 + */ 39 51 export function hashVideoAmbientBackdropSeed(value: string): number { 40 52 let hash = 0; 41 53 for (let i = 0; i < value.length; i += 1) { ··· 45 57 return Math.abs(hash); 46 58 } 47 59 60 + /** 61 + * Builds a complete CSS inline style string for the video ambient backdrop element. 62 + * All CSS custom properties (`--video-ambient-backdrop-*`) are derived from `seedUrl` 63 + * so the same thumbnail URL always produces the same gradient, angle, and highlight. 64 + */ 48 65 export function getVideoAmbientBackdropInlineStyle(seedUrl: string | null): string { 49 66 const seed = seedUrl ?? 'fallback'; 50 67 const [start, end] = getVideoAmbientBackdropGradientColors(seed);