{"record":{"id":"0b19607e4f369296","repo":"Freika/dawarich","slug":"failed-to-load-poster-theme-key-response-s","errorCode":null,"errorMessage":"Failed to load poster theme \"${key}\" (${response.status})","messagePattern":"Failed to load poster theme \"(.+?)\" \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"app/javascript/poster_studio/data/theme_loader.js","lineNumber":82,"sourceCode":"      primary: tokens.road_primary,\n      secondary: tokens.road_secondary,\n      tertiary: tokens.road_tertiary,\n      residential: tokens.road_residential,\n      default: tokens.road_default,\n    },\n    route: tokens.route ?? DEFAULT_ROUTE_COLOR,\n    casing: tokens.bg ?? DEFAULT_CASING_COLOR,\n  }\n}\n\nconst cache = new Map()\nconst tokenCache = new Map()\n\nexport async function loadThemeTokens(key) {\n  if (tokenCache.has(key)) return tokenCache.get(key)\n  const response = await fetch(`/poster_themes/${key}.json`)\n  if (!response.ok) {\n    throw new Error(`Failed to load poster theme \"${key}\" (${response.status})`)\n  }\n  const tokens = await response.json()\n  tokenCache.set(key, tokens)\n  return tokens\n}\n\nexport async function loadTheme(key) {\n  if (cache.has(key)) return cache.get(key)\n  const resolved = resolveTheme(await loadThemeTokens(key))\n  cache.set(key, resolved)\n  return resolved\n}\n","sourceCodeStart":64,"sourceCodeEnd":95,"githubUrl":"https://github.com/Freika/dawarich/blob/97fad417c5a11b0eb11157890635e015723a2e97/app/javascript/poster_studio/data/theme_loader.js#L64-L95","documentation":"Poster Studio lazily fetches theme token JSON from the static route /poster_themes/{key}.json (two-level cache: tokenCache then resolved-theme cache). Any non-OK status throws with the HTTP code baked into the message: 404 means the theme key has no JSON asset, 401/403 mean the route sits behind auth, 500 means the web server failed to serve the file. Because the throw happens inside loadThemeTokens, loadTheme and every dependent render also fail.","triggerScenarios":"loadTheme('dark') where public/poster_themes/dark.json was never shipped; a theme key read from a saved poster or URL param that was renamed; the asset missing from a Docker image because it was added after the image build; a CDN/proxy misroute returning 404 for static JSON.","commonSituations":"Deploying new themes without recompiling/copying static assets, users following old share links with retired theme keys, theme key typos in configuration, serving /poster_themes from a different host that requires auth headers.","solutions":["Confirm the file exists: check public/poster_themes/<key>.json (or the packaged equivalent) is present on the deployed server","Validate the key against the known theme list before calling loadTheme, and clamp to the default theme when unknown","Rebuild/redeploy the container or asset pipeline so new theme JSON files ship","Wrap loadTheme callers with a fallback to the default theme so one bad key cannot break the whole poster editor"],"exampleFix":"// before\nconst resolved = resolveTheme(await loadThemeTokens(key))\n\n// after\nlet tokens\ntry {\n  tokens = await loadThemeTokens(key)\n} catch {\n  tokens = await loadThemeTokens(DEFAULT_THEME_KEY)\n}\nconst resolved = resolveTheme(tokens)","handlingStrategy":"fallback","validationCode":"const KNOWN_THEMES = new Set(['light', 'dark', 'topographic']) // keep in sync with shipped assets\nfunction isKnownTheme(key) {\n  return KNOWN_THEMES.has(key)\n}\n// before loadTheme:\nconst safeKey = isKnownTheme(key) ? key : DEFAULT_THEME_KEY","typeGuard":null,"tryCatchPattern":"try {\n  theme = await loadTheme(key)\n} catch (error) {\n  console.warn(`Theme '${key}' failed to load (${error.message}); using default`) \n  theme = await loadTheme(DEFAULT_THEME_KEY)\n}","preventionTips":["Keep a manifest of shipped theme keys and validate user/URL-supplied keys against it","Include /poster_themes/*.json in deploy verification (asset presence check) so missing files surface at rollout, not at user render time","Cache the default theme eagerly so the fallback path never triggers a second network failure"],"tags":["fetch","theming","poster-studio","static-assets"],"backgroundTag":"http-404-not-found","analyzedSha":"97fad417c5a11b0eb11157890635e015723a2e97","analyzedAt":"2026-08-21T17:04:17.778Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}