From 12e61f1359a2584b32f4adabfdd5f5b706dfca3c Mon Sep 17 00:00:00 2001 From: 2witstudios <2witstudios@gmail.com> Date: Mon, 30 Mar 2026 19:29:05 -0500 Subject: [PATCH] feat(macos): Show dot files in project and worktree file trees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove blanket dot-file filter from scanDirectory so files like .gitignore, .env, .claude/, .pu/ are visible and editable — just like a real file system. The hiddenNames set (.git, .DS_Store, etc.) still filters noise. Dot files are excluded from gitignore checking so they always remain visible. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../purepoint-macos/State/FileTreeState.swift | 2 +- .../FileTreeStateTests.swift | 125 ++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 apps/purepoint-macos/purepoint-macosTests/FileTreeStateTests.swift diff --git a/apps/purepoint-macos/purepoint-macos/State/FileTreeState.swift b/apps/purepoint-macos/purepoint-macos/State/FileTreeState.swift index 77dbc8d..68bba6a 100644 --- a/apps/purepoint-macos/purepoint-macos/State/FileTreeState.swift +++ b/apps/purepoint-macos/purepoint-macos/State/FileTreeState.swift @@ -99,7 +99,7 @@ final class FileTreeState { var nodes: [FileTreeNode] = [] for name in contents { - if Self.hiddenNames.contains(name) || name.hasPrefix(".") { continue } + if Self.hiddenNames.contains(name) { continue } let absPath = (path as NSString).appendingPathComponent(name) let relPath = String(absPath.dropFirst(root.count + 1)) diff --git a/apps/purepoint-macos/purepoint-macosTests/FileTreeStateTests.swift b/apps/purepoint-macos/purepoint-macosTests/FileTreeStateTests.swift new file mode 100644 index 0000000..eacdc30 --- /dev/null +++ b/apps/purepoint-macos/purepoint-macosTests/FileTreeStateTests.swift @@ -0,0 +1,125 @@ +import Testing +import Foundation +@testable import PurePoint + +@MainActor +struct FileTreeStateTests { + + private func makeTempDir(files: [String], dirs: [String] = []) throws -> String { + let root = NSTemporaryDirectory() + "FileTreeStateTests-\(UUID().uuidString)" + let fm = FileManager.default + try fm.createDirectory(atPath: root, withIntermediateDirectories: true) + for dir in dirs { + try fm.createDirectory( + atPath: (root as NSString).appendingPathComponent(dir), + withIntermediateDirectories: true + ) + } + for file in files { + fm.createFile( + atPath: (root as NSString).appendingPathComponent(file), + contents: nil + ) + } + return root + } + + private func cleanup(_ path: String) { + try? FileManager.default.removeItem(atPath: path) + } + + // MARK: - Dot file visibility + + @Test func givenDotFileShouldIncludeInTree() throws { + // given + let root = try makeTempDir(files: [".gitignore", ".env.example", "README.md"]) + defer { cleanup(root) } + let state = FileTreeState() + + // when + state.load(worktreePath: root) + + // then + let names = state.rootNodes.map(\.name) + #expect(names.contains(".gitignore")) + #expect(names.contains(".env.example")) + #expect(names.contains("README.md")) + } + + @Test func givenDotDirectoryShouldIncludeInTree() throws { + // given + let root = try makeTempDir(files: [], dirs: [".claude", ".pu"]) + defer { cleanup(root) } + let state = FileTreeState() + + // when + state.load(worktreePath: root) + + // then + let names = state.rootNodes.map(\.name) + #expect(names.contains(".claude")) + #expect(names.contains(".pu")) + } + + // MARK: - Hidden names still excluded + + @Test func givenGitDirectoryShouldExcludeFromTree() throws { + // given + let root = try makeTempDir(files: [".DS_Store"], dirs: [".git", ".build"]) + defer { cleanup(root) } + let state = FileTreeState() + + // when + state.load(worktreePath: root) + + // then + let names = state.rootNodes.map(\.name) + #expect(!names.contains(".git")) + #expect(!names.contains(".DS_Store")) + #expect(!names.contains(".build")) + } + + @Test func givenNonDotHiddenNamesShouldExcludeFromTree() throws { + // given + let root = try makeTempDir(files: [], dirs: ["node_modules", "DerivedData", "xcuserdata", "__pycache__"]) + defer { cleanup(root) } + let state = FileTreeState() + + // when + state.load(worktreePath: root) + + // then + let names = state.rootNodes.map(\.name) + #expect(!names.contains("node_modules")) + #expect(!names.contains("DerivedData")) + #expect(!names.contains("xcuserdata")) + #expect(!names.contains("__pycache__")) + } + + // MARK: - Mixed content + + @Test func givenMixedContentShouldShowDotFilesButNotHiddenNames() throws { + // given + let root = try makeTempDir( + files: [".gitignore", ".env", ".DS_Store", "main.swift"], + dirs: [".claude", ".git", "Sources"] + ) + defer { cleanup(root) } + let state = FileTreeState() + + // when + state.load(worktreePath: root) + + // then + let names = Set(state.rootNodes.map(\.name)) + // Visible + #expect(names.contains(".gitignore")) + #expect(names.contains(".env")) + #expect(names.contains(".claude")) + #expect(names.contains("main.swift")) + #expect(names.contains("Sources")) + // Hidden + #expect(!names.contains(".git")) + #expect(!names.contains(".DS_Store")) + } +}