True when any tab holds unsaved edits — the "you'll lose work" signal for the host.
Current tabs, in strip order. Returns a copy — callers must not mutate internal state.
Drop every tab (session end / explicit discard-all).
Close a tab. When the closed tab was active, activation falls to its NEIGHBOUR — the tab to its right, else the one to its left, else null. (Closing the tab you're looking at should land you somewhere sensible, not on nothing.)
Mark a tab saved/clean without touching its payload.
Add a tab and make it active. An existing tab with the same Id is NOT duplicated — it is activated instead (re-opening the same draft focuses it, which is what a user means).
Move a tab from one position to another (drag-reorder). Active tab + payloads are untouched — only strip ORDER changes. Out-of-range or no-op indices return false and change nothing.
Move a tab through its lifecycle. Clears the rejection reason on any non-rejected state.
Replace a tab's host-owned payload. Marks the tab dirty unless told otherwise.
The pure, framework-free state machine behind
<mj-workspace-tab-strip>.Deliberately extracted from the component so the tab semantics are exhaustively unit-testable in isolation (see
__tests__/workspace-tab-store.test.ts); the Angular shell around it stays thin. No Angular, no DB, no app types — synchronous and total.v1 semantics: session-scoped only. Nothing here touches persistence.