{"record":{"id":"5ab39ab29983b311","repo":"astrid-runtime/astrid","slug":"branding-icon-is-bytes-distros-must-not-embed","errorCode":null,"errorMessage":"branding.icon is {} bytes — distros must not embed assets larger than 64 KiB","messagePattern":"branding\\.icon is (.+?) bytes — distros must not embed assets larger than 64 KiB","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/distro/validate.rs","lineNumber":242,"sourceCode":"            parse_invite_duration(exp).map_err(|e| anyhow::anyhow!(e))?;\n        }\n        if let Some(cap) = &invites.max_principals\n            && cap != \"unlimited\"\n            && cap.parse::<u32>().is_err()\n        {\n            anyhow::bail!(\n                \"invites.max-principals must be \\\"unlimited\\\" or a non-negative integer (got {cap:?})\",\n            );\n        }\n    }\n\n    // Branding — only structural rails. The dashboard interprets the\n    // values; the parser just refuses obvious garbage.\n    if let Some(branding) = &manifest.branding {\n        if let Some(icon) = &branding.icon\n            && icon.len() > 64 * 1024\n        {\n            anyhow::bail!(\n                \"branding.icon is {} bytes — distros must not embed assets larger than 64 KiB\",\n                icon.len()\n            );\n        }\n        if let Some(color) = &branding.primary_color {\n            validate_hex_color(color)\n                .map_err(|e| anyhow::anyhow!(\"branding.primary-color: {e}\"))?;\n        }\n        if let Some(color) = &branding.accent_color {\n            validate_hex_color(color).map_err(|e| anyhow::anyhow!(\"branding.accent-color: {e}\"))?;\n        }\n    }\n\n    Ok(())\n}\n\n/// Enforce a manifest's `[distro].astrid-version` floor against the running CLI.\n///","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/validate.rs#L224-L260","documentation":"Fired by validate_manifest in distro validation when the decoded branding.icon asset exceeds 64 KiB. Distro manifests must not embed large binary assets; icons above this size are rejected to keep manifests lightweight.","triggerScenarios":"Setting branding.icon to a data blob (e.g. base64 or raw bytes) longer than 64 * 1024 bytes. Detected during any distro manifest validation.","commonSituations":"Exporting a high-resolution logo without downscaling; pasting a full-size PNG/SVG asset into the manifest instead of an optimized icon; accidental duplication of the asset payload.","solutions":["Replace the icon with a smaller optimized asset under 64 KiB (resize/compress the image).","Reference the icon externally instead of embedding it in the manifest.","If embedding is required, strip metadata and reduce resolution/color depth to fit the cap."],"exampleFix":"// before\nbranding.icon = \"<87000-byte base64 PNG>\"\n\n// after\nbranding.icon = \"<optimized 32x32 PNG, ~4KB base64>\"","handlingStrategy":"validation","validationCode":"fn icon_size_ok(icon: &[u8]) -> bool {\n    icon.len() <= 64 * 1024\n}\n// call before writing manifest:\nassert!(icon_size_ok(&icon_bytes), \"icon must be <= 64 KiB\");","typeGuard":"fn embeddable_icon(icon: &[u8]) -> Option<&[u8]> {\n    (icon.len() <= 64 * 1024).then_some(icon)\n}","tryCatchPattern":"if icon.len() > 64 * 1024 {\n    eprintln!(\"icon is {} bytes; compress below 64 KiB\", icon.len());\n    std::process::exit(1);\n}","preventionTips":["Pre-optimize icons (small PNG, stripped metadata) before embedding.","Add an asset pipeline step that enforces the 64 KiB cap.","Reference large assets externally instead of inlining them."],"tags":["manifest","validation","branding","assets"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}