diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e550a95..1aed936 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,3 +38,27 @@ jobs:
- name: Test
run: swift test
+
+ # Prose gate: Vale lints the docs against the Microsoft style guide. A style
+ # violation fails the build, so it never reaches a reviewer. Runs on Linux
+ # (no Xcode needed) so it starts fast and in parallel with the build matrix.
+ prose-lint:
+ name: Docs prose (Vale)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Vale
+ run: |
+ VALE_VERSION=3.15.1
+ curl -sfL "https://github.com/errata-ai/vale/releases/download/v${VALE_VERSION}/vale_${VALE_VERSION}_Linux_64-bit.tar.gz" -o vale.tar.gz
+ tar -xzf vale.tar.gz vale
+ sudo install vale /usr/local/bin/vale
+ rm -f vale vale.tar.gz
+
+ # Downloads the Microsoft style package declared in .vale.ini.
+ - name: Sync Vale styles
+ run: vale sync
+
+ - name: Lint docs prose
+ run: vale guide docs/adr README.md
diff --git a/.gitignore b/.gitignore
index 45f6a39..2bd582b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,9 @@ DerivedData/
# MkDocs build output (built by CI, not committed)
/site/
+# Vale prose-lint styles are downloaded by `vale sync` (locally and in CI)
+.github/styles/
+
# Amore / Sparkle private signing keys — NEVER commit these
*.private
sparkle_private_key
diff --git a/.vale.ini b/.vale.ini
new file mode 100644
index 0000000..0228d07
--- /dev/null
+++ b/.vale.ini
@@ -0,0 +1,19 @@
+# Vale configuration for MacDirStat docs.
+# Prose is linted with the Microsoft Writing Style Guide, the same voice the
+# guide and ADR were already written in (active voice, present tense, "you").
+StylesPath = .github/styles
+
+# CI fails on errors (clear-cut style violations). Warnings and suggestions
+# still print locally for review but don't block the build.
+MinAlertLevel = error
+
+# Vale downloads the Microsoft package into StylesPath (see the CI workflow and
+# the `vale sync` step in the docs).
+Packages = Microsoft
+
+[*.md]
+BasedOnStyles = Vale, Microsoft
+
+# Reference pages quote real API symbols (ByteFormatter, TreemapLayout) and code
+# identifiers; don't flag those as spelling errors.
+Vale.Spelling = NO
diff --git a/README.md b/README.md
index 6b4aa8b..98fa50c 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
MacDirStat
See where your disk space went.
-A fast, beautiful macOS disk usage visualizer — built entirely in Swift.
+A fast, beautiful macOS disk usage visualizer, built entirely in Swift.
[](https://github.com/Ti-03/MacDirStat/actions/workflows/ci.yml)
[](https://apps.apple.com/app/dirstat/id6766033292?mt=12)
@@ -23,21 +23,21 @@ A fast, beautiful macOS disk usage visualizer — built entirely in Swift.
## What it does
-MacDirStat scans any folder and turns your filesystem into an interactive sunburst chart — every ring is a depth level, every arc is a file or folder, sized by disk usage. Hover to inspect. Click to drill in. Right-click to delete.
+MacDirStat scans any folder and turns your filesystem into an interactive sunburst chart: every ring is a depth level, every arc is a file or folder, sized by disk usage. Hover to inspect. Click to drill in. Right-click to delete.
## Features
-- **Sunburst visualization** — depth rings, color-coded by file type (video, code, images, archives…)
-- **Spotlight hover** — everything else fades when you hover; selected files pulse with a glow
-- **Force Touch haptics** — soft tap for small files, triple thud for multi-GB ones
-- **Drill navigation** — click any folder to zoom in, click back to go up
-- **Duplicate detection** — finds identical files by content hash, shows wasted space per group
-- **One-click cleanup** — keep one copy, trash the rest — or delete file by file
-- **File list panel** — sortable tree view beside the chart, toggle to give the chart full width
-- **File type breakdown** — top file types with a searchable list of all types
-- **Move to Trash** — right-click any arc or row to trash it, chart refreshes instantly
-- **CSV export** — dump the full scan as a spreadsheet
-- **Settings** — toggle haptic feedback on/off
+- **Sunburst visualization**: depth rings, color-coded by file type (video, code, images, archives…)
+- **Spotlight hover**: everything else fades when you hover; selected files pulse with a glow
+- **Force Touch haptics**: soft tap for small files, triple thud for multi-GB ones
+- **Drill navigation**: click any folder to zoom in, click back to go up
+- **Duplicate detection**: finds identical files by content hash, shows wasted space per group
+- **One-click cleanup**: keep one copy, trash the rest, or delete file by file
+- **File list panel**: sortable tree view beside the chart, toggle to give the chart full width
+- **File type breakdown**: top file types with a searchable list of all types
+- **Move to Trash**: right-click any arc or row to trash it, chart refreshes instantly
+- **CSV export**: dump the full scan as a spreadsheet
+- **Settings**: toggle haptic feedback on/off
## Screenshots
@@ -47,9 +47,9 @@ MacDirStat scans any folder and turns your filesystem into an interactive sunbur
## Install
-**[Download on the Mac App Store](https://apps.apple.com/app/dirstat/id6766033292?mt=12)** — published as **DirStat**.
+**[Download on the Mac App Store](https://apps.apple.com/app/dirstat/id6766033292?mt=12)**, published as **DirStat**.
-Or grab the DMG directly from **[Releases](https://github.com/Ti-03/MacDirStat/releases)** — open it and drag the app to your Applications folder. It updates itself automatically via the Help menu once installed.
+Or grab the DMG directly from **[Releases](https://github.com/Ti-03/MacDirStat/releases)**. Open it and drag the app to your Applications folder. It updates itself automatically via the Help menu once installed.
**Build from source**
@@ -63,14 +63,14 @@ Requires macOS 14+ and Xcode 15+.
## Tech
-Pure Swift + SwiftUI — no Electron, no web views, no dependencies.
+Pure Swift + SwiftUI: no Electron, no web views, no dependencies.
| Layer | What |
|---|---|
-| Scanner | POSIX `opendir`/`fstatat` with async task groups — parallel, cancellable |
+| Scanner | POSIX `opendir`/`fstatat` with async task groups: parallel, cancellable |
| Layout | Custom sunburst partition algorithm (band-width from view size) |
-| Renderer | SwiftUI `Canvas` — draws 1,000+ arcs at 30 fps |
-| Haptics | `NSHapticFeedbackManager` — intensity scales with file size |
+| Renderer | SwiftUI `Canvas`: draws 1,000+ arcs at 30 fps |
+| Haptics | `NSHapticFeedbackManager`: intensity scales with file size |
| Duplicates | SHA-256 content hashing on a background actor |
## Documentation
diff --git a/docs/adr/0001-compile-time-guard-for-liquid-glass.md b/docs/adr/0001-compile-time-guard-for-liquid-glass.md
index 823a89a..4ef6676 100644
--- a/docs/adr/0001-compile-time-guard-for-liquid-glass.md
+++ b/docs/adr/0001-compile-time-guard-for-liquid-glass.md
@@ -60,8 +60,8 @@ alone type-checked. The runtime `#available` check stays inside the
compile-time branch, so a build made with Xcode 26 still back-deploys
correctly to macOS 14/15 at runtime.
-We also dropped `macos-14` from the CI matrix: its SDK predates both
-`glassEffect` and the `ContentUnavailableView` backport check, so it cannot
+I also dropped `macos-14` from the CI matrix: its SDK predates both
+`glassEffect` and the `ContentUnavailableView` backport check, so it can't
build this project at all regardless of guards, and keeping it would only
produce a permanently red, unfixable job.
@@ -72,9 +72,9 @@ produce a permanently red, unfixable job.
`#available` alone. A future contributor who copies an existing
`#available`-only pattern from elsewhere in SwiftUI code will reintroduce
this bug; there is no compiler warning for it.
-- The CI matrix (`macos-15`, `macos-26`) is now load-bearing: it is the
+- The CI matrix (`macos-15`, `macos-26`) is now load-bearing: it's the
only thing that would have caught this before it shipped, since a
- single-SDK local build cannot reproduce the failure.
+ single-SDK local build can't reproduce the failure.
- Slightly more boilerplate per call site (two nested conditionals instead
of one), in exchange for the app actually compiling on the SDK most
users' Macs currently have.
diff --git a/guide/how-to/read-the-treemap.md b/guide/how-to/read-the-treemap.md
index 6ae0ff1..3af5e47 100644
--- a/guide/how-to/read-the-treemap.md
+++ b/guide/how-to/read-the-treemap.md
@@ -31,9 +31,9 @@ extension:
| Pink | Fonts | `ttf`, `otf`, `woff` |
| Lime | 3D / models | `obj`, `fbx`, `gltf` |
-An extension MacDirStat does not recognize gets a stable color derived from
+An extension MacDirStat doesn't recognize gets a stable color derived from
its name, so the same unknown extension is always the same color across a
-scan. Folders are colored separately from files: each folder gets a muted,
+scan. Folders and files use different color schemes: each folder gets a muted,
low-saturation tint based on its name, so folders never compete visually
with the files inside them. Files also darken slightly with depth, so you
can tell a top-level file from one buried five folders down at a glance.
@@ -55,7 +55,7 @@ can tell a top-level file from one buried five folders down at a glance.
## See also
-- [Scan your first folder](../tutorial/first-scan.md) if you have not run a
+- [Scan your first folder](../tutorial/first-scan.md) if you haven't run a
scan yet.
- [TreemapLayout reference](../reference/treemap-layout.md) for exactly how
angles and colors are computed.
diff --git a/guide/index.md b/guide/index.md
index d384af5..91a1b41 100644
--- a/guide/index.md
+++ b/guide/index.md
@@ -1,11 +1,11 @@
# MacDirStat
-MacDirStat is a native macOS app that scans a folder and shows what is taking up
+MacDirStat is a native macOS app that scans a folder and shows what's taking up
disk space, as an interactive radial treemap.
This guide is for **macOS users who have MacDirStat installed** (or built it
from source) and want to scan a folder, read the resulting chart, or look up
-what a specific API does. You do not need any Swift experience to follow the
+what a specific API does. You don't need any Swift experience to follow the
tutorial or how-to guide; the reference section assumes you are reading the
Swift source.
diff --git a/guide/reference/treemap-layout.md b/guide/reference/treemap-layout.md
index e3c633f..3aef8e1 100644
--- a/guide/reference/treemap-layout.md
+++ b/guide/reference/treemap-layout.md
@@ -40,7 +40,7 @@ let arcAngle = fraction * totalAngle
An arc smaller than `minArcAngle` (about 1 degree) is skipped entirely
rather than drawn as an unreadable sliver. Folders recurse into their own
-children at the next radius band; files do not.
+children at the next radius band; files don't.
## `TreemapCell`
@@ -61,9 +61,9 @@ One cell per arc. `startAngle`/`endAngle` are in radians; `innerRadius`/
## Color assignment
-Colors are not assigned by `TreemapLayout` directly; it delegates to
+Colors aren't assigned by `TreemapLayout` directly; it delegates to
`ExtensionColorMap.color(for:)` for files and computes a muted per-folder
tint by hashing the folder's name for directories. Files also darken by a
fixed amount per depth (from 0% at depth 0 up to 38% at depth 5+), so a
`.mp4` five folders deep is visibly darker than one at the top level, even
-though both are "orange".
+though both are "orange."
diff --git a/guide/tutorial/first-scan.md b/guide/tutorial/first-scan.md
index 2b342f4..e28d020 100644
--- a/guide/tutorial/first-scan.md
+++ b/guide/tutorial/first-scan.md
@@ -34,6 +34,6 @@ proportional to how much disk space it uses.
## Next step
-The colors are not random. Continue to
+The colors aren't random. Continue to
[How to read the treemap](../how-to/read-the-treemap.md) to learn what each
color means and how to change the color scheme.