{"record":{"id":"e9343162fec7ea3d","repo":"gitbutlerapp/gitbutler","slug":"cannot-mix-mark-sources","errorCode":null,"errorMessage":"cannot mix mark sources","messagePattern":"cannot mix mark sources","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/status/tui/app/mark.rs","lineNumber":367,"sourceCode":"    }\n}\n\nimpl MarkStore<UncommittedHunkOrFile> for Marks {\n    type Error = anyhow::Error;\n\n    fn contains_mark(&self, mark: &UncommittedHunkOrFile) -> bool {\n        self.as_hunks()\n            .is_some_and(|hunks| hunks.iter().any(|hunk| hunk == mark))\n    }\n\n    fn insert_mark(&mut self, mark: UncommittedHunkOrFile) -> Result<(), Self::Error> {\n        if self.contains_mark(&mark) {\n            return Ok(());\n        }\n        match self {\n            Self::Empty => *self = Self::Hunks(NonEmpty::new(mark)),\n            Self::Hunks(hunks) => hunks.push(mark),\n            _ => anyhow::bail!(\"cannot mix mark sources\"),\n        }\n        Ok(())\n    }\n\n    fn remove_mark(&mut self, mark: &UncommittedHunkOrFile) {\n        let Self::Hunks(hunks) = self else {\n            return;\n        };\n\n        if remove_from_non_empty(hunks, |marked| marked == mark) {\n            *self = Self::Empty;\n        }\n    }\n}\n\nimpl MarkStore<CommitId> for Marks {\n    type Error = anyhow::Error;\n","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/command/legacy/status/tui/app/mark.rs#L349-L385","documentation":"The status TUI's mark store (crates/but/src/command/legacy/status/tui/app/mark.rs:367) is a single-kind selection enum (Empty/Hunks/Commits/CommittedFiles/Branches). insert_mark for uncommitted hunks/files bails with 'cannot mix mark sources' when the store already holds a different kind. This is an internal invariant guarding the TUI's one-kind-at-a-time marking model, not a user configuration error.","triggerScenarios":"TUI code calling insert_mark(UncommittedHunkOrFile) while marks of another variant (commits, committed files, branches) are active; a key sequence or new feature that lets the user mark a hunk and then a branch without clearing marks first.","commonSituations":"Developers extending TUI mark handling; regressions after refactoring mark modes; keybinding handlers that switch selection context without resetting marks.","solutions":["Clear existing marks (reset to Empty) before inserting marks of a different kind.","At the input layer, branch on the current kind (as_hunks()) and reject or reset mixed selections before insert_mark is reached.","Write a TUI test reproducing the mixed-kind key sequence and fix the handler that permits it."],"exampleFix":"// before\nmarks.insert_mark(UncommittedHunkOrFile::File(f))?; // may hit Hunks-vs-other mix\n\n// after\nif !matches!(marks, Marks::Empty | Marks::Hunks(_)) {\n    marks = Marks::Empty; // one kind at a time\n}\nmarks.insert_mark(UncommittedHunkOrFile::File(f))?;","handlingStrategy":"validation","validationCode":"if !matches!(marks, Marks::Empty | Marks::Hunks(_)) {\n    marks = Marks::Empty; // enforce one mark kind per session\n}\nmarks.insert_mark(hunk_or_file)?;","typeGuard":"fn accepts_hunk_marks(marks: &Marks) -> bool {\n    matches!(marks, Marks::Empty | Marks::Hunks(_))\n}","tryCatchPattern":"if let Err(err) = marks.insert_mark(mark) {\n    if err.to_string().contains(\"cannot mix mark sources\") {\n        marks = Marks::Empty;\n        marks.insert_mark(mark)?; // retry with a fresh session\n    } else { return Err(err); }\n}","preventionTips":["Reset marks to Empty on every mode transition in TUI code.","Gate every insert_mark call with a kind check.","Cover mixed-kind key sequences with TUI tests (see the tui-tests skill)."],"tags":["tui","internal","invariant","selection"],"backgroundTag":"selection-type-mismatch","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}