{"record":{"id":"071193135c9c5af2","repo":"ramensoftware/windhawk","slug":"more-than-one-settings-key","errorCode":null,"errorMessage":"More than one settings key","messagePattern":"More than one settings key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/windhawk-frontend/apps/windhawk-frontend/src/app/panel/mod-details/ModDetails.Website.tsx","lineNumber":108,"sourceCode":"    }\n\n    const parseSettings = (\n      settingsArray: Record<string, unknown>[]\n    ): InitialSettings => {\n      return settingsArray.map(parseSettingItem);\n    };\n\n    const parseSettingItem = (\n      value: Record<string, unknown>\n    ): InitialSettingItem => {\n      // Find the actual setting key (not starting with $)\n      const actualParameters = Object.keys(value).filter(\n        (x) => !x.startsWith('$')\n      );\n      if (actualParameters.length === 0) {\n        throw new Error('Missing settings key');\n      } else if (actualParameters.length > 1) {\n        throw new Error('More than one settings key');\n      }\n\n      const actualParameter = actualParameters[0];\n      const metaParameters = Object.keys(value).filter((x) =>\n        x.startsWith('$')\n      );\n\n      // Group meta parameters by their base name (name, description, options)\n      const metaGroups: Record<\n        string,\n        Array<{ language: string | null; value: unknown }>\n      > = {};\n\n      for (const paramWithPrefix of metaParameters) {\n        const param = paramWithPrefix.slice(1); // remove '$'\n        const paramParts = param.split(':');\n        const baseName = paramParts[0];\n        const lang = paramParts[1] ?? null;","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/ramensoftware/windhawk/blob/61d99ed8e182e1af1b60109612b6763ad1b4b74e/src/windhawk-frontend/apps/windhawk-frontend/src/app/panel/mod-details/ModDetails.Website.tsx#L90-L126","documentation":"parseSettingItem validates that each settings item object has exactly one non-meta key (the actual setting name); keys starting with '$' are metadata and excluded. If an item object carries two or more real setting keys, the shape is ambiguous and this Error is thrown so the caller knows the settings entry is malformed.","triggerScenarios":"Calling parseSettingItem with an object like { \"Setting1\": {...}, \"Setting2\": {...} } (more than one key not prefixed with '$'), e.g. a mod's [Mg] metadata JSON that accidentally merged two settings into one object.","commonSituations":"Hand-edited or corrupted mod metadata files; authors copy-pasting a settings block and forgetting to split two settings into separate objects; automated tooling generating merged settings entries.","solutions":["Inspect the settings item object at the failing location and split it so each object contains exactly one non-$ key.","Fix the mod's metadata (the [Mg] settings JSON) at the source so it is not committed with multiple keys per item.","If extra data is intentional, prefix those keys with '$' so they are treated as metadata instead of setting keys."],"exampleFix":"// before\n{ \"$maxValue\": 100, \"FontSize\": 14, \"Theme\": \"dark\" }\n// after\n{ \"$maxValue\": 100, \"FontSize\": 14 }\n{ \"$maxValue\": 3, \"Theme\": \"dark\" }","handlingStrategy":"validation","validationCode":"function isValidSettingItem(value) {\n  const keys = Object.keys(value).filter((k) => !k.startsWith('$'));\n  return keys.length === 1;\n}\nif (!isValidSettingItem(item)) throw new Error('Settings item must have exactly one key');","typeGuard":"const isSettingItem = (v: Record<string, unknown>): v is { [k: string]: unknown } =>\n  Object.keys(v).filter((k) => !k.startsWith('$')).length === 1;","tryCatchPattern":"try {\n  const parsed = parseSettingItem(item);\n} catch (e) {\n  if (e.message.includes('settings key')) {\n    console.warn('Malformed settings item, skipping:', item);\n    return null;\n  }\n  throw e;\n}","preventionTips":["Validate mod metadata JSON before committing (one non-$ key per settings object).","Add a schema check in CI that lints the [Mg] settings blocks of mods.","Prefix auxiliary data keys with '$' so they are treated as metadata."],"tags":["schema-validation","mod-settings","parse"],"backgroundTag":"schema-validation-failed","analyzedSha":"61d99ed8e182e1af1b60109612b6763ad1b4b74e","analyzedAt":"2026-09-12T14:02:41.115Z","contentChangedAt":"2026-09-12T14:02:41.115Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}