Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2ccd677
refactor(datagrid): collapse cell hierarchy to single DataGridCellView
datlechin May 8, 2026
e64c2c9
refactor(datagrid): bound display cache via NSCache and add O(1) RowI…
datlechin May 8, 2026
9803534
refactor(datagrid): incremental row visual state via RowVisualIndex
datlechin May 8, 2026
522bb48
refactor(datagrid): off-main JSON parse and cancellable Task.sleep co…
datlechin May 8, 2026
8fa7779
fix(datagrid): use NSButton for cell accessory clicks and brighten ch…
datlechin May 8, 2026
90a93dd
refactor(datagrid): add typeSelect, animated undo insert, defensive r…
datlechin May 8, 2026
f505edc
fix(datagrid): row tint refresh on mark-delete and focus-follow on pr…
datlechin May 8, 2026
fee0430
fix(datagrid): force focus overlay refresh on every selection change
datlechin May 8, 2026
36cecab
fix(datagrid): keep focus overlay on top via zPosition and defer key-…
datlechin May 8, 2026
1f523fc
fix(datagrid): defer focus overlay refresh through every reload path
datlechin May 8, 2026
e774ec7
refactor(datagrid): cell-owned focus border replaces FocusOverlayView…
datlechin May 8, 2026
ed2df66
chore(datagrid): delete dead code surfaced by audit
datlechin May 8, 2026
2d005e5
refactor(datagrid): snapshot theme palette per render pass and weak-c…
datlechin May 8, 2026
e8b5d85
refactor(quickswitcher): replace .sheet with NSPanel for Spotlight pa…
datlechin May 8, 2026
6628ab0
fix(switcher): ESC clears search if non-empty otherwise bubbles to di…
datlechin May 8, 2026
60bddee
chore(datagrid): inline single-caller TypePicker and drop TableViewCo…
datlechin May 8, 2026
8964a55
refactor(window): replace custom restoration with NSWindowRestoration…
datlechin May 8, 2026
0fb724d
refactor(hig): honor Reduce Transparency and Increase Contrast for ma…
datlechin May 8, 2026
1f231d1
refactor(datagrid): drop redundant resize cursor handling and consoli…
datlechin May 8, 2026
1981808
refactor(plugin-redis): replace recursive DisclosureGroup with Outlin…
datlechin May 8, 2026
803b242
Merge branch 'main' into refactor/datagrid-stage-15-redis-outline
datlechin May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- AI Chat views: replace custom pill buttons with native `.borderless` styles, switch hardcoded text colors to semantic system colors, use relative font sizing in Markdown rendering, align spacing to the 8-pt grid, and add accessibility labels to icon-only buttons
- Translucent backgrounds (Welcome sidebar, settings banners, ER diagram toolbar, JSON editor controls, Pro feature scrim) honor the system Reduce Transparency and Increase Contrast accessibility settings, swapping the material for a solid surface color when either is on
- Internal: result-grid sortable header drops the custom resize cursor handling that duplicated AppKit's built-in column-edge resize, and consolidates three sort delegate methods into one that carries the full sort state. No user-facing change; multi-column sort, shift-click cycle, and the column resize cursor still work the same.
- Internal: Redis sidebar key tree uses SwiftUI `OutlineGroup` instead of recursive `DisclosureGroup` + `ForEach` wrapped in `AnyView`. Expansion state is now managed natively per branch identifier; the explicit `expandedPrefixes` set is gone.

### Fixed

Expand Down
7 changes: 7 additions & 0 deletions TablePro/Models/UI/RedisKeyNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ internal enum RedisKeyNode: Identifiable, Hashable {
}
}

var children: [RedisKeyNode]? {
switch self {
case .namespace(_, _, let children, _): return children
case .key: return nil
}
}

// Hash on id only (children excluded for performance)
func hash(into hasher: inout Hasher) {
hasher.combine(id)
Expand Down
2 changes: 0 additions & 2 deletions TablePro/ViewModels/RedisKeyTreeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ internal final class RedisKeyTreeViewModel {
private static let maxKeys = 50_000

var rootNodes: [RedisKeyNode] = []
var expandedPrefixes: Set<String> = []
var isLoading = false
var isTruncated = false
var separator: String = ":"
Expand Down Expand Up @@ -65,7 +64,6 @@ internal final class RedisKeyTreeViewModel {
func clear() {
rootNodes = []
allKeys = []
expandedPrefixes = []
isTruncated = false
}

Expand Down
91 changes: 34 additions & 57 deletions TablePro/Views/Sidebar/RedisKeyTreeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import SwiftUI

internal struct RedisKeyTreeView: View {
let nodes: [RedisKeyNode]
@Binding var expandedPrefixes: Set<String>
let isLoading: Bool
let isTruncated: Bool
var onSelectNamespace: ((String) -> Void)?
Expand All @@ -29,7 +28,9 @@ internal struct RedisKeyTreeView: View {
.font(.caption)
.padding(.vertical, 4)
} else {
renderNodes(nodes)
OutlineGroup(nodes, children: \.children) { node in
row(for: node)
}
if isTruncated {
Text("Showing first 50,000 keys")
.foregroundStyle(.secondary)
Expand All @@ -39,65 +40,41 @@ internal struct RedisKeyTreeView: View {
}
}

private func renderNodes(_ items: [RedisKeyNode]) -> AnyView {
AnyView(
ForEach(items) { node in
switch node {
case .namespace(let name, let fullPrefix, let children, let keyCount):
DisclosureGroup(isExpanded: Binding(
get: { expandedPrefixes.contains(fullPrefix) },
set: { expanded in
if expanded {
expandedPrefixes.insert(fullPrefix)
} else {
expandedPrefixes.remove(fullPrefix)
}
}
)) {
renderNodes(children)
} label: {
namespaceLabel(name: name, keyCount: keyCount, fullPrefix: fullPrefix)
}
case .key(let name, let fullKey, let keyType):
keyLabel(name: name, fullKey: fullKey, keyType: keyType)
@ViewBuilder
private func row(for node: RedisKeyNode) -> some View {
switch node {
case .namespace(let name, let fullPrefix, _, let keyCount):
Button {
onSelectNamespace?(fullPrefix)
} label: {
HStack {
Label(name, systemImage: "folder")
.foregroundStyle(.primary)
Spacer()
Text("\(keyCount)")
.font(.caption2)
.foregroundStyle(.secondary)
.padding(.horizontal, 6)
.padding(.vertical, 1)
.background(.quaternary, in: Capsule())
}
}
)
}

private func namespaceLabel(name: String, keyCount: Int, fullPrefix: String) -> some View {
Button {
onSelectNamespace?(fullPrefix)
} label: {
HStack {
Label(name, systemImage: "folder")
.foregroundStyle(.primary)
Spacer()
Text("\(keyCount)")
.font(.caption2)
.foregroundStyle(.secondary)
.padding(.horizontal, 6)
.padding(.vertical, 1)
.background(.quaternary, in: Capsule())
}
}
.buttonStyle(.plain)
}

private func keyLabel(name: String, fullKey: String, keyType: String) -> some View {
Button {
onSelectKey?(fullKey, keyType)
} label: {
HStack {
Label(name, systemImage: keyTypeIcon(keyType))
.foregroundStyle(.primary)
Spacer()
Text(keyType)
.font(.caption2)
.foregroundStyle(.tertiary)
.buttonStyle(.plain)
case .key(let name, let fullKey, let keyType):
Button {
onSelectKey?(fullKey, keyType)
} label: {
HStack {
Label(name, systemImage: keyTypeIcon(keyType))
.foregroundStyle(.primary)
Spacer()
Text(keyType)
.font(.caption2)
.foregroundStyle(.tertiary)
}
}
.buttonStyle(.plain)
}
.buttonStyle(.plain)
}

private func keyTypeIcon(_ type: String) -> String {
Expand Down
4 changes: 0 additions & 4 deletions TablePro/Views/Sidebar/SidebarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,6 @@ struct SidebarView: View {
Section(isExpanded: $viewModel.isRedisKeysExpanded) {
RedisKeyTreeView(
nodes: keyTreeVM.displayNodes(searchText: viewModel.searchText),
expandedPrefixes: Binding(
get: { keyTreeVM.expandedPrefixes },
set: { keyTreeVM.expandedPrefixes = $0 }
),
isLoading: keyTreeVM.isLoading,
isTruncated: keyTreeVM.isTruncated,
onSelectNamespace: { prefix in
Expand Down
Loading