This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
FileBrowser is a Swift Package (swift-tools-version: 6.0, iOS 17+) providing a single
SwiftUI library: a document browser modeled on Procreate's, meant as a replacement for
DocumentGroup / UIDocumentBrowserViewController. It is consumed by host apps that own
their own document format; the package itself is format-agnostic.
swift build # builds for macOS host by default
swift test # runs FileBrowserTests
swift test --filter FileBrowserTests/example # run a single testNote: the package targets iOS but most code is gated behind #if os(iOS), so a plain
swift build on macOS compiles only the cross-platform pieces. To compile/test the iOS
surface, build through Xcode against an iOS Simulator destination
(xcodebuild -scheme FileBrowser -destination 'platform=iOS Simulator,name=iPhone 15').
Tests are written with Swift Testing (import Testing, @Test, #expect), not XCTest.
The library exposes one public view, FileBrowserView, plus the public FileManager
extension. Host apps embed FileBrowserView and pass closures for actions the package
deliberately does not own (opening a document, showing settings/intro, importing).
-
FileBrowserModel(@Observable,@unchecked Sendable) — the single source of truth. Owns the document list, selection state, and the "selecting" mode. It operates exclusively on the app's Documents directory:scan()enumerates it recursively, keeping only files whosepathExtensionmatches and skipping any directory named inexclude. ADispatchSourcefile-system watcher (startMonitoring) re-runsscan()on the main queue whenever the directory changes. The@unchecked Sendableand theDispatchQueue.main.async(rather thanTask) inside the event handler are deliberate workarounds for a Swift 6DispatchSourcecrash — see the linked forum thread in the source before changing them. -
FileBrowserView— owns layout: aLazyVGridofBrowserItemViews plus a blurred top toolbar that swaps between normal mode (Select / Import / New) and selecting mode (Duplicate / Delete / dismiss). The model is created lazily inonAppear, so it is optional throughout the view. -
model.openURLis a cross-component animation channel, not navigation state. Setting it makes the matchingBrowserItemViewscale/fade (the "opening" animation); the actual open is the host'sdocumentSelectedclosure, fired ~1s later viaTask.sleep. New-document creation chains several timed sleeps: create file → wait for the watcher to re-scan → scroll to it → triggeropenURL→ calldocumentSelected. -
Thumbnails —
ThumbnailView+ThumbnailLoaderload asynchronously. If a host passesthumbnailName, a PNG of that name is read from inside the document (documents are treated as package directories); otherwiseQLThumbnailGeneratorproduces one. Loading happens on a detached task, cancelled on reuse.
- New SwiftUI/UIKit-specific code should be wrapped in
#if os(iOS);ThumbnailLoaderandThumbnailViewadditionally carry#elsebranches for macOS (NSImage). - Bundled assets (colors in
Media.xcassets) are resolved withBundle.module. - Filename collisions are resolved by
getFileURL(base:), which appends1,2, … until a free name is found — reuse it for any new file-creating code.