WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto
4
fork

Configure Feed

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

fix(web): re-throw programming errors in resolveTheme catch block (ATB-53)

Malpercio b42f9988 f29be654

+20 -3
+10 -1
apps/web/src/lib/__tests__/theme-resolution.test.ts
··· 89 89 }); 90 90 91 91 afterEach(() => { 92 - vi.unstubAllGlobals(); 93 92 mockFetch.mockReset(); 93 + vi.unstubAllGlobals(); 94 94 }); 95 95 96 96 function policyResponse(overrides: object = {}) { ··· 213 213 expect.stringContaining("Theme resolution failed"), 214 214 expect.objectContaining({ operation: "resolveTheme" }) 215 215 ); 216 + }); 217 + 218 + it("re-throws programming errors (TypeError) rather than swallowing them", async () => { 219 + // A TypeError from a bug in the code should propagate, not be silently logged 220 + mockFetch.mockResolvedValueOnce({ 221 + ok: true, 222 + json: () => { throw new TypeError("Cannot read properties of null"); }, 223 + }); 224 + await expect(resolveTheme(APPVIEW, undefined, undefined)).rejects.toThrow(TypeError); 216 225 }); 217 226 218 227 it("passes cssOverrides and fontUrls through from theme response", async () => {
+10 -2
apps/web/src/lib/theme-resolution.ts
··· 1 1 import neobrutalLight from "../styles/presets/neobrutal-light.json" with { type: "json" }; 2 + import { isProgrammingError } from "./errors.js"; 2 3 import { logger } from "./logger.js"; 3 4 4 5 export type ResolvedTheme = { ··· 105 106 return { ...FALLBACK_THEME, colorScheme }; 106 107 } 107 108 109 + // If the URI is absent from availableThemes (data inconsistency in AppView), 110 + // expectedCid will be null and the CID check is skipped — the theme is served 111 + // without integrity verification rather than falling back. 108 112 const expectedCid = 109 113 policy.availableThemes.find((t) => t.uri === defaultUri)?.cid ?? null; 110 114 ··· 126 130 } 127 131 128 132 return { 133 + // AppView stores tokens as jsonb — trusting that all values are strings 134 + // as enforced by the theme editor UI. Non-string values would render as 135 + // their string coercion in CSS, which is benign but unexpected. 129 136 tokens: theme.tokens as Record<string, string>, 130 137 cssOverrides: theme.cssOverrides ?? null, 131 138 fontUrls: theme.fontUrls ?? null, 132 139 colorScheme, 133 140 }; 134 141 } catch (error) { 135 - // Intentionally don't re-throw: a broken theme system should serve the 136 - // fallback and log the error, rather than crash every page request. 142 + // Re-throw programming errors (bugs) — they should surface, not be hidden. 143 + // Only network/data errors should fall back silently. 144 + if (isProgrammingError(error)) throw error; 137 145 logger.error("Theme resolution failed — using hardcoded fallback", { 138 146 operation: "resolveTheme", 139 147 error: error instanceof Error ? error.message : String(error),