{"record":{"id":"63b5d6f5bcc32c26","repo":"zellij-org/zellij","slug":"duplicate-top-level-identifier-declared-in-bo","errorCode":null,"errorMessage":"duplicate top-level identifier '{}' declared in both '{}.js' and '{}.js'","messagePattern":"duplicate top-level identifier '(.+?)' declared in both '(.+?)\\.js' and '(.+?)\\.js'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"xtask/src/assets.rs","lineNumber":132,"sourceCode":"    Ok(vec![\n        (BUNDLE_FILE.to_string(), bundle),\n        (INTEGRITY_FILE.to_string(), integrity),\n        (INDEX_FILE.to_string(), index),\n    ])\n}\n\nfn build_bundle(assets_dir: &Path) -> anyhow::Result<String> {\n    let mut bundle = String::new();\n    let mut declarations: BTreeMap<String, String> = BTreeMap::new();\n\n    for module in MODULE_ORDER {\n        let path = assets_dir.join(format!(\"{}.js\", module));\n        let source = std::fs::read_to_string(&path)\n            .with_context(|| format!(\"failed to read '{}'\", path.display()))?;\n        let chunk = flatten_module(module, &source)?;\n        for name in top_level_declarations(&chunk) {\n            if let Some(previous) = declarations.insert(name.clone(), (*module).to_string()) {\n                return Err(anyhow!(\n                    \"duplicate top-level identifier '{}' declared in both '{}.js' and '{}.js'\",\n                    name,\n                    previous,\n                    module\n                ));\n            }\n        }\n        bundle.push_str(&chunk);\n        if !bundle.ends_with('\\n') {\n            bundle.push('\\n');\n        }\n    }\n\n    Ok(bundle)\n}\n\nfn flatten_module(module: &str, source: &str) -> anyhow::Result<String> {\n    let mut out = String::new();","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/zellij-org/zellij/blob/98a0837077492d53dd252ab30bc3e43e41e504f4/xtask/src/assets.rs#L114-L150","documentation":"The asset bundler flattens every module in MODULE_ORDER into a single app.js by concatenating top-level declarations (column-0 function/const/let/var/class). If the same top-level identifier is declared in two module files, the concatenated script would contain a duplicate binding, which would be a runtime SyntaxError, so bundling aborts with the offending name and both modules.","triggerScenarios":"Adding a top-level `function render(...)` (or const/let/var/class at column 0) in one module, e.g. keyboard.js, when `render` is already declared at top level in an earlier module such as terminal.js or utils.js.","commonSituations":"Copy-pasting helpers between modules; new modules using generic names like `init`, `handleEvent`, `config`; renaming a function in one file but not its twin in another.","solutions":["Rename one of the duplicate top-level identifiers to something specific (e.g. `render` -> `renderTerminal`)","If both modules need it, keep the declaration in one module and add `import { render } from './terminal.js';` in the other (imports are stripped by the flattener, so the single surviving declaration serves the whole bundle)","Re-run `cargo xtask assets` to confirm the bundle builds"],"exampleFix":"// before: terminal.js and keyboard.js both declare top-level `render`\n// keyboard.js\nfunction render() { /* ... */ }\n\n// after: keyboard.js imports it instead\nimport { render } from './terminal.js';","handlingStrategy":"validation","validationCode":"# crude pre-flight: flag duplicated top-level declarations across bundled modules\nfor name in $(cat zellij-client/assets/*.js | grep -E '^(async function |function |const |let |var |class )' \\\n  | sed -E 's/^(async function |function |const |let |var |class )([A-Za-z0-9_$]+).*/\\2/' | sort); do\n  count=$(grep -cE \"^(async function |function |const |let |var |class )${name}\\\\b\" zellij-client/assets/*.js | awk -F: '{s+=$2} END {print s}')\n  [ \"$count\" -gt 1 ] && echo \"duplicate top-level identifier: $name\"\ndone","typeGuard":"fn has_duplicate_top_level(module_a: &str, module_b: &str) -> bool {\n    let names = |src: &str| -> std::collections::HashSet<String> {\n        src.lines().filter(|l| !l.starts_with(char::is_whitespace))\n            .filter_map(|l| [\"function \", \"const \", \"let \", \"var \", \"class \"]\n                .iter().find_map(|p| l.strip_prefix(p)))\n            .map(|r| r.chars().take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '$').collect())\n            .collect()\n    };\n    !names(module_a).is_disjoint(&names(module_b))\n}","tryCatchPattern":"match assets::generate(&assets_dir) {\n    Ok(_) => {}\n    Err(e) if e.to_string().contains(\"duplicate top-level identifier\") => {\n        // extract the identifier from the message, rename it, regenerate\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Namespace identifiers per module (terminalRender, keyboardInit) instead of generic names","Import shared helpers from the module that owns them rather than redeclaring them","Run `cargo xtask assets` locally before pushing; CI duplicates check is slower feedback"],"tags":["javascript","bundler","assets","duplicate-identifier"],"backgroundTag":null,"analyzedSha":"98a0837077492d53dd252ab30bc3e43e41e504f4","analyzedAt":"2026-08-16T13:02:01.396Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}