From b9a6cc29abbdf6ab294c44276565acb78c7c2fa4 Mon Sep 17 00:00:00 2001 From: phranck Date: Wed, 28 Jan 2026 20:40:52 +0100 Subject: [PATCH] fix: Show all system items (quit, help, theme) and fix deduplication - StatusBarState now initializes with all 3 system items - Added deduplication logic to StatusBar.renderToBuffer - User items with same shortcut override system items - Updated Example App to not duplicate 'q quit' - Updated tests for 3 system items instead of 1 --- Sources/TUIKit/App/App.swift | 4 +-- Sources/TUIKit/Views/StatusBar.swift | 17 +++++++++--- Sources/TUIKitExample/ContentView.swift | 4 +-- Tests/TUIKitTests/StatusBarTests.swift | 36 +++++++++++++++---------- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/Sources/TUIKit/App/App.swift b/Sources/TUIKit/App/App.swift index 780702d..8bd640e 100644 --- a/Sources/TUIKit/App/App.swift +++ b/Sources/TUIKit/App/App.swift @@ -108,8 +108,8 @@ public final class StatusBarState: @unchecked Sendable { /// Creates a new status bar state. public init() { - // Initialize with default system items (quit only, no actions yet) - self.systemItems = [SystemStatusBarItem.quit] + // Initialize with all system items (quit, help, theme) + self.systemItems = SystemStatusBarItem.all } // MARK: - System Items Configuration diff --git a/Sources/TUIKit/Views/StatusBar.swift b/Sources/TUIKit/Views/StatusBar.swift index ade82ee..ca99c33 100644 --- a/Sources/TUIKit/Views/StatusBar.swift +++ b/Sources/TUIKit/Views/StatusBar.swift @@ -719,13 +719,16 @@ public struct StatusBar: View { self.labelColor = labelColor } - /// All items combined (sorted user items, then system items). + /// All items combined (sorted user items, then filtered system items). /// /// User items are sorted by their `order` property. /// System items maintain their fixed order (quit, help, theme). + /// User items override system items with the same shortcut. /// Use this for event handling to check all items. public var allItems: [any StatusBarItemProtocol] { - userItems.sorted { $0.order < $1.order } + systemItems + let userShortcuts = Set(userItems.map { $0.shortcut }) + let filteredSystemItems = systemItems.filter { !userShortcuts.contains($0.shortcut) } + return userItems.sorted { $0.order < $1.order } + filteredSystemItems } /// Whether the status bar has any items to display. @@ -742,9 +745,15 @@ public struct StatusBar: View { extension StatusBar: Renderable { public func renderToBuffer(context: RenderContext) -> FrameBuffer { - // Combine user items (sorted by order) and system items (fixed order) + // Get shortcuts used by user items (for deduplication) + let userShortcuts = Set(userItems.map { $0.shortcut }) + + // Filter out system items that are overridden by user items + let filteredSystemItems = systemItems.filter { !userShortcuts.contains($0.shortcut) } + + // Combine: sorted user items + filtered system items (fixed order) let sortedUserItems = userItems.sorted { $0.order < $1.order } - let combinedItems = sortedUserItems + systemItems + let combinedItems = sortedUserItems + filteredSystemItems guard !combinedItems.isEmpty else { return FrameBuffer() diff --git a/Sources/TUIKitExample/ContentView.swift b/Sources/TUIKitExample/ContentView.swift index 212a5de..c3c7ef3 100644 --- a/Sources/TUIKitExample/ContentView.swift +++ b/Sources/TUIKitExample/ContentView.swift @@ -45,7 +45,6 @@ struct ContentView: View { StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "nav") StatusBarItem(shortcut: Shortcut.enter, label: "select", key: .enter) StatusBarItem(shortcut: Shortcut.range("1", "6"), label: "jump") - StatusBarItem(shortcut: Shortcut.quit, label: "quit") } case .textStyles: TextStylesPage() @@ -74,8 +73,7 @@ struct ContentView: View { StatusBarItem(shortcut: Shortcut.escape, label: "back") { ExampleAppState.shared.currentPage = .menu }, - StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "scroll"), - StatusBarItem(shortcut: Shortcut.quit, label: "quit") + StatusBarItem(shortcut: Shortcut.arrowsUpDown, label: "scroll") ] } } diff --git a/Tests/TUIKitTests/StatusBarTests.swift b/Tests/TUIKitTests/StatusBarTests.swift index 604cfd5..60d3eb0 100644 --- a/Tests/TUIKitTests/StatusBarTests.swift +++ b/Tests/TUIKitTests/StatusBarTests.swift @@ -265,15 +265,17 @@ struct StatusBarStateTests { state.setItems([ StatusBarItem(shortcut: "s", label: "save"), - StatusBarItem(shortcut: "h", label: "help") + StatusBarItem(shortcut: "x", label: "extra") ]) - // System items + user items (q from system, s and h from user) - #expect(state.currentItems.count == 3) + // User items (s, x) + system items (q, ?, t) = 5 total + #expect(state.currentItems.count == 5) #expect(state.hasItems == true) #expect(state.currentItems.contains { $0.shortcut == "q" }) // system quit + #expect(state.currentItems.contains { $0.shortcut == "?" }) // system help + #expect(state.currentItems.contains { $0.shortcut == "t" }) // system theme #expect(state.currentItems.contains { $0.shortcut == "s" }) // user save - #expect(state.currentItems.contains { $0.shortcut == "h" }) // user help + #expect(state.currentItems.contains { $0.shortcut == "x" }) // user extra } @Test("Set global items with builder merges with system items") @@ -282,11 +284,11 @@ struct StatusBarStateTests { state.setItems { StatusBarItem(shortcut: "s", label: "save") - StatusBarItem(shortcut: "h", label: "help") + StatusBarItem(shortcut: "x", label: "extra") } - // System items + user items - #expect(state.currentItems.count == 3) + // User items (s, x) + system items (q, ?, t) = 5 total + #expect(state.currentItems.count == 5) } @Test("Push context overrides global items but keeps system items") @@ -302,9 +304,11 @@ struct StatusBarStateTests { StatusBarItem(shortcut: Shortcut.enter, label: "confirm") ]) - // System quit + context items (escape, enter) - #expect(state.currentItems.count == 3) + // Context items (escape, enter) + system items (q, ?, t) = 5 total + #expect(state.currentItems.count == 5) #expect(state.currentItems.contains { $0.shortcut == "q" }) // system quit + #expect(state.currentItems.contains { $0.shortcut == "?" }) // system help + #expect(state.currentItems.contains { $0.shortcut == "t" }) // system theme #expect(state.currentItems.contains { $0.shortcut == Shortcut.escape }) #expect(state.currentItems.contains { $0.shortcut == Shortcut.enter }) } @@ -317,10 +321,12 @@ struct StatusBarStateTests { StatusBarItem(shortcut: "a", label: "action") } - // System quit + context item - #expect(state.currentItems.count == 2) + // Context item (a) + system items (q, ?, t) = 4 total + #expect(state.currentItems.count == 4) #expect(state.currentItems.contains { $0.label == "action" }) #expect(state.currentItems.contains { $0.shortcut == "q" }) + #expect(state.currentItems.contains { $0.shortcut == "?" }) + #expect(state.currentItems.contains { $0.shortcut == "t" }) } @Test("Pop context returns to global items with system items") @@ -332,15 +338,17 @@ struct StatusBarStateTests { ]) state.push(context: "temp", items: [ - StatusBarItem(shortcut: "t", label: "temp") + StatusBarItem(shortcut: "x", label: "temp") ]) state.pop(context: "temp") - // System quit + global item - #expect(state.currentItems.count == 2) + // Global item (g) + system items (q, ?, t) = 4 total + #expect(state.currentItems.count == 4) #expect(state.currentItems.contains { $0.shortcut == "g" }) #expect(state.currentItems.contains { $0.shortcut == "q" }) + #expect(state.currentItems.contains { $0.shortcut == "?" }) + #expect(state.currentItems.contains { $0.shortcut == "t" }) } @Test("Context stack respects order")