Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
build-test:
name: Build & Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# macos-15 ships Xcode 16 (Swift 6.1): exercises the older-SDK fallback
# compile path. macos-26 ships Xcode 26 (Swift 6.2): the real shipping
# toolchain, with the macOS 26 SDK and Liquid Glass. The app uses
# MeshGradient (macOS 15) and glassEffect (macOS 26), so it needs the
# macOS 15+ SDK to compile, macos-14 cannot build it.
os: [macos-15, macos-26]
steps:
- uses: actions/checkout@v4

- name: Cache SwiftPM
uses: actions/cache@v4
with:
path: |
.build
~/Library/Caches/org.swift.swiftpm
key: ${{ matrix.os }}-spm-${{ hashFiles('Package.resolved') }}
restore-keys: |
${{ matrix.os }}-spm-

# Static-analysis gate: the Swift compiler (with StrictConcurrency enabled
# in Package.swift) type-checks and concurrency-checks all production code.
- name: Build
run: swift build -v

- name: Test
run: swift test
61 changes: 61 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Deploy Pages

on:
push:
branches: [main]
paths:
- "guide/**"
- "mkdocs.yml"
- "docs/**"
- ".github/workflows/pages.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Install MkDocs Material
run: pip install mkdocs-material

# Builds the docs-as-code site (tutorial/how-to/reference) from guide/,
# per mkdocs.yml's `site_dir: site`.
- name: Build docs site
run: mkdocs build --strict

# Combine the existing landing page (docs/index.html, screenshots, logo)
# with the freshly built docs site, so the same GitHub Pages URL keeps
# serving the landing page at / while the docs move to /guide/.
- name: Assemble Pages artifact
run: |
mkdir -p _site/guide
cp docs/index.html docs/privacy.html docs/logo.png docs/screenshot*.png _site/
cp -r site/. _site/guide/

- uses: actions/upload-pages-artifact@v3
with:
path: _site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ DerivedData/
# Test results
*.xcresult

# MkDocs build output (built by CI, not committed)
/site/

# Amore / Sparkle private signing keys — NEVER commit these
*.private
sparkle_private_key
Expand Down
15 changes: 15 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ let package = Package(
products: [
.executable(name: "MacDirStat", targets: ["MacDirStat"])
],
dependencies: [
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.9.1")
],
targets: [
.executableTarget(
name: "MacDirStat",
dependencies: [
.product(name: "Sparkle", package: "Sparkle")
],
path: "Sources",
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<p><strong>See where your disk space went.</strong><br/>
A fast, beautiful macOS disk usage visualizer — built entirely in Swift.</p>

[![CI](https://github.com/Ti-03/MacDirStat/actions/workflows/ci.yml/badge.svg)](https://github.com/Ti-03/MacDirStat/actions/workflows/ci.yml)
[![App Store](https://img.shields.io/badge/Mac_App_Store-Download-0D96F6?style=flat-square&logo=apple)](https://apps.apple.com/app/dirstat/id6766033292?mt=12)
[![macOS](https://img.shields.io/badge/macOS-14%2B-black?style=flat-square&logo=apple)](https://www.apple.com/macos/)
[![License](https://img.shields.io/badge/license-AGPL--3.0-blue?style=flat-square)](./LICENSE)
Expand Down Expand Up @@ -72,6 +73,10 @@ Pure Swift + SwiftUI — no Electron, no web views, no dependencies.
| Haptics | `NSHapticFeedbackManager` — intensity scales with file size |
| Duplicates | SHA-256 content hashing on a background actor |

## Documentation

Tutorial, how-to guides, and API reference: **[docs site](https://ti-03.github.io/MacDirStat/guide/)**. Architecture decisions are recorded in [`docs/adr/`](docs/adr).

## Privacy

MacDirStat collects zero data. No network access. No analytics. No tracking. Everything runs on your device. [Full privacy policy](https://ti-03.github.io/MacDirStat/privacy.html).
Expand Down
37 changes: 37 additions & 0 deletions Sources/App/GlassCompat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,77 +3,114 @@ import SwiftUI
// Compatibility shims for macOS 26 Liquid Glass and macOS 14/15 APIs.
// On older OS versions these fall back to NSVisualEffectView-based materials.

// `glassEffect` / `.glass` button styles only exist in the macOS 26 SDK (Xcode 26,
// Swift 6.2). `#available` is a *runtime* check, it does not stop the compiler from
// needing the symbol, so on an older SDK these references fail to compile. Gate them
// at *compile* time with `#if compiler(>=6.2)`; the runtime `#available` stays inside
// so an Xcode-26 build still back-deploys correctly to macOS 14/15.
extension View {
@ViewBuilder
func glassCapsule() -> some View {
#if compiler(>=6.2)
if #available(macOS 26, *) {
self.glassEffect(in: .capsule)
} else {
self.background(.ultraThinMaterial, in: Capsule())
}
#else
self.background(.ultraThinMaterial, in: Capsule())
#endif
}

@ViewBuilder
func glassCircle() -> some View {
#if compiler(>=6.2)
if #available(macOS 26, *) {
self.glassEffect(.regular, in: .circle)
} else {
self.background(.ultraThinMaterial, in: Circle())
}
#else
self.background(.ultraThinMaterial, in: Circle())
#endif
}

@ViewBuilder
func glassCard(cornerRadius: CGFloat) -> some View {
#if compiler(>=6.2)
if #available(macOS 26, *) {
self.glassEffect(.regular, in: .rect(cornerRadius: cornerRadius))
} else {
self.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius))
}
#else
self.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius))
#endif
}

@ViewBuilder
func glassInteractiveCard(cornerRadius: CGFloat) -> some View {
#if compiler(>=6.2)
if #available(macOS 26, *) {
self.glassEffect(.regular.interactive(), in: .rect(cornerRadius: cornerRadius))
} else {
self.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius))
}
#else
self.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius))
#endif
}

@ViewBuilder
func glassTintedCard(tint: Color, cornerRadius: CGFloat) -> some View {
#if compiler(>=6.2)
if #available(macOS 26, *) {
self.glassEffect(.regular.tint(tint), in: .rect(cornerRadius: cornerRadius))
} else {
self.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius))
}
#else
self.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius))
#endif
}

@ViewBuilder
func glassTintedInteractiveCapsule(tint: Color) -> some View {
#if compiler(>=6.2)
if #available(macOS 26, *) {
self.glassEffect(.regular.tint(tint).interactive(), in: .capsule)
} else {
self.background(.ultraThinMaterial, in: Capsule())
}
#else
self.background(.ultraThinMaterial, in: Capsule())
#endif
}

@ViewBuilder
func glassButton() -> some View {
#if compiler(>=6.2)
if #available(macOS 26, *) {
self.buttonStyle(.glass)
} else {
self.buttonStyle(.bordered)
}
#else
self.buttonStyle(.bordered)
#endif
}

@ViewBuilder
func glassProminentButton() -> some View {
#if compiler(>=6.2)
if #available(macOS 26, *) {
self.buttonStyle(.glassProminent)
} else {
self.buttonStyle(.borderedProminent)
}
#else
self.buttonStyle(.borderedProminent)
#endif
}
}

Expand Down
Loading
Loading