diff --git a/Sources/KitchenSyncApp/Design/KSTokens.swift b/Sources/KitchenSyncApp/Design/KSTokens.swift
index c0149c9..b110d1b 100644
--- a/Sources/KitchenSyncApp/Design/KSTokens.swift
+++ b/Sources/KitchenSyncApp/Design/KSTokens.swift
@@ -47,7 +47,7 @@ enum KS {
/// Text drawn ON lime.
static let onInk = Color(hex: 0x0A0D07)
static let amber = Color(hex: 0xFF9D3B)
- /// The stopped label — salmon, not grey.
+ /// The stopped label — salmon, not gray.
static let ember = Color(hex: 0xFF7A6B)
static let ledFill = LinearGradient(colors: [Color(hex: 0xCAFF5A), Color(hex: 0x9BE32A)],
@@ -75,7 +75,7 @@ enum KS {
// (the BPM readout). All three are SIL OFL 1.1 and SHOULD be bundled to keep
// the identity 1:1. They are not bundled yet — these are the documented
// fallbacks. Note the design direction's rule: if DSEG7 is absent, the tempo
-// glass must DROP its ghost layer, because a grey duplicate of a non-segmented
+// glass must DROP its ghost layer, because a gray duplicate of a non-segmented
// font reads as a rendering bug rather than unlit segments.
extension Font {
diff --git a/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift b/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift
index c90785e..fb0a1cf 100644
--- a/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift
+++ b/Sources/KitchenSyncApp/Discovery/DeviceMatch.swift
@@ -26,7 +26,7 @@ enum DeviceMatch {
return matchesKnownHostname(serviceName)
}
- /// The FALLBACK. A heuristic, and deliberately labelled as one — it exists to keep un-updated
+ /// The FALLBACK. A heuristic, and deliberately labeled as one — it exists to keep un-updated
/// units working, not because it is correct.
///
/// The firmware advertises generic `_http._tcp` with no TXT record, so the hostname is all a
diff --git a/Sources/KitchenSyncApp/Discovery/DiscoveryStatus.swift b/Sources/KitchenSyncApp/Discovery/DiscoveryStatus.swift
index f6751e6..82ee692 100644
--- a/Sources/KitchenSyncApp/Discovery/DiscoveryStatus.swift
+++ b/Sources/KitchenSyncApp/Discovery/DiscoveryStatus.swift
@@ -30,7 +30,7 @@ enum DiscoveryStatus: Equatable {
/// The browser died. `NWBrowser` does not come back from `.failed` on its own —
/// somebody has to restart it.
case failed
- /// Cancelled, because we asked.
+ /// Canceled, because we asked.
case stopped
/// `kDNSServiceErr_PolicyDenied`. The one signal that means "the user has to act".
@@ -42,7 +42,7 @@ enum DiscoveryStatus: Equatable {
self = .starting
case .ready:
self = .browsing
- case .cancelled:
+ case .cancelled: // spelling: ok (NWBrowser.State, Apple API)
self = .stopped
case .waiting(let error):
// A permission denial ARRIVES AS A WAIT, which is precisely the trap: it looks
diff --git a/Sources/KitchenSyncApp/Discovery/KitchenSyncDiscovery.swift b/Sources/KitchenSyncApp/Discovery/KitchenSyncDiscovery.swift
index cbd169e..e1e5f8c 100644
--- a/Sources/KitchenSyncApp/Discovery/KitchenSyncDiscovery.swift
+++ b/Sources/KitchenSyncApp/Discovery/KitchenSyncDiscovery.swift
@@ -67,7 +67,7 @@ final class KitchenSyncDiscovery {
}
func stop() {
- browser?.stateUpdateHandler = nil // cancelling is not news; don't report .stopped
+ browser?.stateUpdateHandler = nil // canceling is not news; don't report .stopped
browser?.cancel()
browser = nil
}
diff --git a/Sources/KitchenSyncApp/Discovery/SetupNetwork.swift b/Sources/KitchenSyncApp/Discovery/SetupNetwork.swift
index 8ba2011..aab40b3 100644
--- a/Sources/KitchenSyncApp/Discovery/SetupNetwork.swift
+++ b/Sources/KitchenSyncApp/Discovery/SetupNetwork.swift
@@ -16,7 +16,7 @@ enum SetupNetwork {
/// - `KitchenSync-Setup` — the P4 (`wifi_link.c` `AP_SSID`)
/// - `KSTouch-Config` — the Touch (`KitchenSyncTouch.ino` `WiFi.softAP`)
/// Matched as a **family of prefixes**, not one string, so a new product's setup AP is
- /// recognised without an app change — the same reason `DeviceMatch` matches host prefixes.
+ /// recognized without an app change — the same reason `DeviceMatch` matches host prefixes.
static let ssidPrefixes = ["kitchensync-setup", "kstouch-config"]
/// True if `ssid` is one of our devices' setup APs. Case-insensitive: a WiFi SSID's case is
diff --git a/Sources/KitchenSyncApp/Info.plist b/Sources/KitchenSyncApp/Info.plist
index 4a46c60..c04aa35 100644
--- a/Sources/KitchenSyncApp/Info.plist
+++ b/Sources/KitchenSyncApp/Info.plist
@@ -18,6 +18,8 @@
$(MARKETING_VERSION)
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
+ ITSAppUsesNonExemptEncryption
+
NSBonjourServices
_http._tcp
@@ -26,5 +28,18 @@
KitchenSync finds your devices on the local network to show their status and control their MIDI clock.
UILaunchScreen
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
diff --git a/Sources/KitchenSyncApp/Models/TransportLaunchState.swift b/Sources/KitchenSyncApp/Models/TransportLaunchState.swift
index dec1bea..e6e4124 100644
--- a/Sources/KitchenSyncApp/Models/TransportLaunchState.swift
+++ b/Sources/KitchenSyncApp/Models/TransportLaunchState.swift
@@ -3,7 +3,7 @@ import Foundation
/// Mirrors `TransportLaunchState` (`link-devices/X32Link/transport_launch.h`) as
/// reported per output in `/status`'s `launch[4]` array. Quantized: Play arms,
/// waits for the next bar line, then runs — Stop is immediate. That asymmetry is
-/// deliberate (it's Ableton's own launch behaviour), so drive UI straight from
+/// deliberate (it's Ableton's own launch behavior), so drive UI straight from
/// this value rather than re-deriving an "armed" state on the client — the
/// device already computes and reports it every poll.
enum TransportLaunchState: Int, Decodable {
diff --git a/Sources/KitchenSyncApp/ViewModels/DeviceDetailViewModel.swift b/Sources/KitchenSyncApp/ViewModels/DeviceDetailViewModel.swift
index fd444fb..03eab62 100644
--- a/Sources/KitchenSyncApp/ViewModels/DeviceDetailViewModel.swift
+++ b/Sources/KitchenSyncApp/ViewModels/DeviceDetailViewModel.swift
@@ -29,7 +29,7 @@ final class DeviceDetailViewModel: ObservableObject {
func start() {
guard pollTask == nil else { return }
pollTask = Task { [weak self] in
- while !Task.isCancelled {
+ while !Task.isCancelled { // spelling: ok (Apple API)
await self?.refreshStatus()
try? await Task.sleep(for: self?.pollInterval ?? .seconds(1))
}
diff --git a/Sources/KitchenSyncApp/ViewModels/DeviceListViewModel.swift b/Sources/KitchenSyncApp/ViewModels/DeviceListViewModel.swift
index 94c4f20..a2e091e 100644
--- a/Sources/KitchenSyncApp/ViewModels/DeviceListViewModel.swift
+++ b/Sources/KitchenSyncApp/ViewModels/DeviceListViewModel.swift
@@ -64,7 +64,7 @@ final class DeviceListViewModel: ObservableObject {
}
discovery.start()
pollTask = Task {
- while !Task.isCancelled {
+ while !Task.isCancelled { // spelling: ok (Apple API)
await refreshAll()
try? await Task.sleep(for: .seconds(2))
}
diff --git a/Sources/KitchenSyncApp/Views/DeviceListView.swift b/Sources/KitchenSyncApp/Views/DeviceListView.swift
index 1012203..7892ff2 100644
--- a/Sources/KitchenSyncApp/Views/DeviceListView.swift
+++ b/Sources/KitchenSyncApp/Views/DeviceListView.swift
@@ -251,7 +251,7 @@ private struct DeviceRow: View {
// "0 PEERS" for a device that isn't answering is a lie dressed as
// a measurement. If there's no status, we don't know the peer count.
// And "NO SIGNAL" is wrong for a device that's answering fine — it's
- // signalling perfectly, in a dialect we can't read.
+ // signaling perfectly, in a dialect we can't read.
Text(peersCaption)
.font(.ksMono(10.5))
.tracking(1.6)
diff --git a/Sources/KitchenSyncApp/Views/DeviceLiveSettingsSheet.swift b/Sources/KitchenSyncApp/Views/DeviceLiveSettingsSheet.swift
index 9131624..38bff1b 100644
--- a/Sources/KitchenSyncApp/Views/DeviceLiveSettingsSheet.swift
+++ b/Sources/KitchenSyncApp/Views/DeviceLiveSettingsSheet.swift
@@ -10,7 +10,7 @@ import SwiftUI
/// **No surface ever mixes live and reboot controls.** The device's own web page puts the metronome
/// ENABLE toggle and the metro-accent toggle forty pixels apart, as the identical component, in the
/// same box — one applies instantly and the other restarts the device mid-set, with nothing on
-/// screen to tell them apart. Better labelling would not have fixed that. Not being adjacent does.
+/// screen to tell them apart. Better labeling would not have fixed that. Not being adjacent does.
///
/// Sections appear only for hardware the device actually reports (`link-devices` ESP-030): no
/// speaker, no click section; no strip, no LED section. Solder a strip onto a Touch and flip one
@@ -162,7 +162,7 @@ struct DeviceLiveSettingsSheet: View {
}
KSField(prefix: "BEAT") {
- ColorPicker("Beat colour", selection: Binding(
+ ColorPicker("Beat color", selection: Binding(
get: { Color(hex: l.beatColor) },
set: { onEdit(.ledBeatColor($0.rgb24)) }
), supportsOpacity: false)
@@ -171,7 +171,7 @@ struct DeviceLiveSettingsSheet: View {
}
KSField(prefix: "ACCENT") {
- ColorPicker("Accent colour", selection: Binding(
+ ColorPicker("Accent color", selection: Binding(
get: { Color(hex: l.accentColor) },
set: { onEdit(.ledAccentColor($0.rgb24)) }
), supportsOpacity: false)
diff --git a/Sources/KitchenSyncApp/Views/FirmwareUpdateSheet.swift b/Sources/KitchenSyncApp/Views/FirmwareUpdateSheet.swift
index 39b7100..504a093 100644
--- a/Sources/KitchenSyncApp/Views/FirmwareUpdateSheet.swift
+++ b/Sources/KitchenSyncApp/Views/FirmwareUpdateSheet.swift
@@ -8,7 +8,7 @@ import UniformTypeIdentifiers
/// the device — the firmware doesn't expose its chip target, so the app cannot check.
/// A filename convention is not a safety mechanism, but an unlabelled "Flash" button
/// is worse. So the confirmation names the device and requires an explicit
-/// acknowledgement of the target. The proper fix is firmware-side (expose the target
+/// acknowledgment of the target. The proper fix is firmware-side (expose the target
/// in /status or /update) and belongs with T-011.
///
/// The reassurance is real and worth stating plainly: dual-slot OTA (P4-017) means a
@@ -40,7 +40,7 @@ struct FirmwareUpdateSheet: View {
picker
if binary != nil {
- targetAcknowledgement
+ targetAcknowledgment
}
if let failure {
@@ -124,7 +124,7 @@ struct FirmwareUpdateSheet: View {
/// The app cannot verify the chip target, so the user must. Not a rubber stamp —
/// it's the only gate that exists.
- private var targetAcknowledgement: some View {
+ private var targetAcknowledgment: some View {
HStack(alignment: .top, spacing: 10) {
Toggle("", isOn: $acknowledgedTarget)
.toggleStyle(KSSwitchStyle())
diff --git a/Sources/KitchenSyncApp/Views/KSControls.swift b/Sources/KitchenSyncApp/Views/KSControls.swift
index 6c4f939..2ec3a2a 100644
--- a/Sources/KitchenSyncApp/Views/KSControls.swift
+++ b/Sources/KitchenSyncApp/Views/KSControls.swift
@@ -214,7 +214,7 @@ struct KSLiveSlider: View {
debounce?.cancel()
debounce = Task {
try? await Task.sleep(for: .milliseconds(60))
- guard !Task.isCancelled else { return }
+ guard !Task.isCancelled else { return } // spelling: ok (Apple API)
onCommit(Int(new.rounded()))
}
}
@@ -234,7 +234,7 @@ struct KSLiveSlider: View {
// Let the device win once it has answered — never leave a slider showing a
// number the device doesn't have.
.onChange(of: value) { _, new in
- if debounce?.isCancelled ?? true { local = nil }
+ if debounce?.isCancelled ?? true { local = nil } // spelling: ok (Apple API)
_ = new
}
.accessibilityLabel(label)
diff --git a/Sources/KitchenSyncApp/Views/KSGlass.swift b/Sources/KitchenSyncApp/Views/KSGlass.swift
index 52e4866..9ccd777 100644
--- a/Sources/KitchenSyncApp/Views/KSGlass.swift
+++ b/Sources/KitchenSyncApp/Views/KSGlass.swift
@@ -5,7 +5,7 @@ import SwiftUI
/// The web UI draws a 7-segment display with an all-segments-lit "188.8" ghost
/// behind the live value, imitating the unlit segments of a real LED display.
/// That trick REQUIRES the DSEG7 Classic face. It isn't bundled yet, so per the
-/// design direction the ghost layer is deliberately DROPPED: a grey duplicate of
+/// design direction the ghost layer is deliberately DROPPED: a gray duplicate of
/// a non-segmented font doesn't read as unlit segments, it reads as a rendering
/// bug. Bundle DSEG7 and the ghost comes back — see `ghost` below.
struct KSGlass: View {
@@ -45,10 +45,10 @@ struct KSGlass: View {
}
}
-/// The `.pwr` LED — breathes while the device is answering, goes dead grey when
+/// The `.pwr` LED — breathes while the device is answering, goes dead gray when
/// it isn't. The web page never needs an offline state; the app needs one
/// constantly, and it's the first thing a user checks. (Proper unreachable
-/// modelling — consecutive-failure thresholds — is T-010.)
+/// modeling — consecutive-failure thresholds — is T-010.)
struct KSPowerLED: View {
let alive: Bool
diff --git a/Tests/KitchenSyncAppTests/DeviceListViewModelTests.swift b/Tests/KitchenSyncAppTests/DeviceListViewModelTests.swift
index 9e89fa5..4ad6ca2 100644
--- a/Tests/KitchenSyncAppTests/DeviceListViewModelTests.swift
+++ b/Tests/KitchenSyncAppTests/DeviceListViewModelTests.swift
@@ -113,7 +113,7 @@ final class DeviceListViewModelTests: XCTestCase {
/// One dropped request on a busy LAN is NOISE, not a disconnection. At a 2s poll,
/// flapping a red badge on every missed packet would be worse than the old
- /// behaviour. A single miss keeps the device reachable and keeps its last status.
+ /// behavior. A single miss keeps the device reachable and keeps its last status.
func test_a_single_missed_poll_does_not_declare_the_device_gone() async {
StubURLProtocol.reset()
StubURLProtocol.routes["GET /status"] = .init(body: Self.statusJSON)
diff --git a/Tests/KitchenSyncAppTests/DeviceMatchTests.swift b/Tests/KitchenSyncAppTests/DeviceMatchTests.swift
index eee753f..796eda9 100644
--- a/Tests/KitchenSyncAppTests/DeviceMatchTests.swift
+++ b/Tests/KitchenSyncAppTests/DeviceMatchTests.swift
@@ -21,7 +21,7 @@ final class DeviceMatchTests: XCTestCase {
/// and X32Link use. hasPrefix is case-sensitive, so `"KitchenSync".hasPrefix(
/// "kitchensync")` is false and the P4 was invisible. A Bonjour name's case is not
/// meaningful; the match must be case-insensitive.
- func test_the_p4_is_matched_despite_a_capitalised_instance_name() {
+ func test_the_p4_is_matched_despite_a_capitalized_instance_name() {
XCTAssertTrue(DeviceMatch.isKitchenSync(serviceName: "KitchenSync", txt: nil))
XCTAssertTrue(DeviceMatch.isKitchenSync(serviceName: "KSTouch-DFD0", txt: nil))
}
diff --git a/Tests/KitchenSyncAppTests/DiscoveryStatusTests.swift b/Tests/KitchenSyncAppTests/DiscoveryStatusTests.swift
index e63322d..e378f1b 100644
--- a/Tests/KitchenSyncAppTests/DiscoveryStatusTests.swift
+++ b/Tests/KitchenSyncAppTests/DiscoveryStatusTests.swift
@@ -49,9 +49,9 @@ final class DiscoveryStatusTests: XCTestCase {
XCTAssertEqual(DiscoveryStatus(browserState: .failed(.posix(.ENETDOWN))), .failed)
}
- func test_setup_and_cancelled_are_not_errors() {
+ func test_setup_and_canceled_are_not_errors() {
XCTAssertEqual(DiscoveryStatus(browserState: .setup), .starting)
- XCTAssertEqual(DiscoveryStatus(browserState: .cancelled), .stopped)
+ XCTAssertEqual(DiscoveryStatus(browserState: .cancelled), .stopped) // spelling: ok (NWBrowser.State, Apple API)
}
/// Only `permissionDenied` is the user's to fix. Everything else is ours or the
diff --git a/Tests/KitchenSyncAppTests/RealTouchStatusTests.swift b/Tests/KitchenSyncAppTests/RealTouchStatusTests.swift
index db114a5..9204d03 100644
--- a/Tests/KitchenSyncAppTests/RealTouchStatusTests.swift
+++ b/Tests/KitchenSyncAppTests/RealTouchStatusTests.swift
@@ -86,7 +86,7 @@ final class RealTouchStatusTests: XCTestCase {
/// THE BUG, reduced to one assertion.
///
/// `link_owns` is a LIVE claim. `ks_status.h`: "when link_owns is true the manual
- /// PLAY/STOP buttons are ignored, so the UI greys them". The app obeys that contract
+ /// PLAY/STOP buttons are ignored, so the UI grays them". The app obeys that contract
/// — `TransportAppearance.acceptsTap = !linkOwned` — and so it refused to even SEND
/// the tap.
///
@@ -107,7 +107,7 @@ final class RealTouchStatusTests: XCTestCase {
linkOwned: s.linkOwnsTransport)
XCTAssertTrue(button.acceptsTap,
- "the device obeys play/stop — a UI that greys the button is lying to the user")
+ "the device obeys play/stop — a UI that grays the button is lying to the user")
}
/// ...and it must be able to stop it again.
@@ -127,14 +127,14 @@ final class RealTouchStatusTests: XCTestCase {
/// then `link_owns:true` is true, its manual buttons really are ignored, and greying
/// them is correct: a button that does nothing is worse than a button that says so.
/// The Touch's mistake was CLAIMING that while obeying anyway.
- func test_a_device_that_really_does_defer_to_link_still_greys_the_button() throws {
+ func test_a_device_that_really_does_defer_to_link_still_grays_the_button() throws {
let s = try JSONDecoder().decode(KsStatus.self, from: Self.lyingLinkOwnsJSON)
let button = TransportAppearance.appearance(for: s.launch[0],
linkOwned: s.linkOwnsTransport)
XCTAssertFalse(button.acceptsTap,
- "link_owns is a LIVE claim; honouring it is right. The fix belongs in whoever LIES.")
+ "link_owns is a LIVE claim; honoring it is right. The fix belongs in whoever LIES.")
}
/// The Touch has no Link-phase block (`xf*`), so `phaseHealth` must be nil rather
diff --git a/Tests/KitchenSyncAppTests/SetupNetworkTests.swift b/Tests/KitchenSyncAppTests/SetupNetworkTests.swift
index aa755e5..448007c 100644
--- a/Tests/KitchenSyncAppTests/SetupNetworkTests.swift
+++ b/Tests/KitchenSyncAppTests/SetupNetworkTests.swift
@@ -1,10 +1,10 @@
import XCTest
@testable import KitchenSyncApp
-/// T-026: a device in SoftAP setup mode isn't on the LAN, so the app recognises it by its
+/// T-026: a device in SoftAP setup mode isn't on the LAN, so the app recognizes it by its
/// setup SSID (to guide/join the AP) and reaches it at the fixed setup address.
final class SetupNetworkTests: XCTestCase {
- func test_the_fleet_setup_aps_are_recognised() {
+ func test_the_fleet_setup_aps_are_recognized() {
XCTAssertTrue(SetupNetwork.isSetupAP(ssid: "KitchenSync-Setup")) // the P4
XCTAssertTrue(SetupNetwork.isSetupAP(ssid: "KSTouch-Config")) // the Touch
}
diff --git a/Tests/KitchenSyncAppTests/TransportAppearanceTests.swift b/Tests/KitchenSyncAppTests/TransportAppearanceTests.swift
index 9efd70c..9ecd531 100644
--- a/Tests/KitchenSyncAppTests/TransportAppearanceTests.swift
+++ b/Tests/KitchenSyncAppTests/TransportAppearanceTests.swift
@@ -17,7 +17,7 @@ final class TransportAppearanceTests: XCTestCase {
let a = TransportAppearance.appearance(for: .stopped, linkOwned: false)
XCTAssertEqual(a.label, "STOPPED")
- // Ember, not grey: grey is what DISABLED looks like, and a stopped
+ // Ember, not gray: gray is what DISABLED looks like, and a stopped
// output is the opposite of disabled — it's loaded and ready to fire.
XCTAssertEqual(a.face, .ember)
XCTAssertFalse(a.blinks)
@@ -27,7 +27,7 @@ final class TransportAppearanceTests: XCTestCase {
/// The one that matters. Play is quantized — a tap arms the output and the
/// bar line may be most of a bar away. The blink is the ONLY evidence the
/// tap registered. The device's own web UI gets this wrong: its `.tgl.arming`
- /// is a static amber with a 150ms colour transition and no motion at all.
+ /// is a static amber with a 150ms color transition and no motion at all.
func test_armed_is_amber_and_blinks() {
let a = TransportAppearance.appearance(for: .armed, linkOwned: false)
diff --git a/Tests/KitchenSyncAppTests/UnsupportedDeviceTests.swift b/Tests/KitchenSyncAppTests/UnsupportedDeviceTests.swift
index ed02e8e..4d8a7f8 100644
--- a/Tests/KitchenSyncAppTests/UnsupportedDeviceTests.swift
+++ b/Tests/KitchenSyncAppTests/UnsupportedDeviceTests.swift
@@ -72,7 +72,7 @@ final class UnsupportedDeviceTests: XCTestCase {
XCTAssertEqual(vm.reachability(of: "kstouch-dfd0.local"), .unsupported)
}
- /// And the T-010 behaviour still holds: a device that genuinely stops answering IS unreachable.
+ /// And the T-010 behavior still holds: a device that genuinely stops answering IS unreachable.
func test_a_device_that_stops_answering_is_still_unreachable() async {
StubURLProtocol.routes["GET /status"] = .init(body: Self.realTouchStatusJSON)
let vm = makeListVM()
diff --git a/Tools/make_app_icon.swift b/Tools/make_app_icon.swift
index ffa3563..484e1cb 100644
--- a/Tools/make_app_icon.swift
+++ b/Tools/make_app_icon.swift
@@ -110,7 +110,7 @@ ctx.setMiterLimit(10)
// concentric banding, a contour map. Neither is a glow, because a glow FALLS OFF and
// a stroke does not.
//
-// A shadow does. Zero offset + a blur radius + the LED colour is a real Gaussian
+// A shadow does. Zero offset + a blur radius + the LED color is a real Gaussian
// bloom around the path. Two passes, because one is too timid to see on charcoal.
for (blur, alpha) in [(S * 0.075, 0.75), (S * 0.030, 0.85)] as [(Double, Double)] {
ctx.saveGState()
diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md
index 52f0b0d..55e88ee 100644
--- a/docs/architecture/overview.md
+++ b/docs/architecture/overview.md
@@ -36,7 +36,7 @@ Models (pure) KsStatus, KsConfig, KsLiveEdit, TransportLaunchStat
KsStatusTempoSource, TransportAppearance, DeviceMatch …
```
-- **Models are pure and testable.** State→appearance mapping, tempo-source labelling,
+- **Models are pure and testable.** State→appearance mapping, tempo-source labeling,
transport summary, and device-match logic are pure functions with unit tests —
never buried in a view.
- **The firmware is the source of truth for the models.** Field names, value grammars,
diff --git a/docs/design/2026-07-14-view-layer-design-direction.md b/docs/design/2026-07-14-view-layer-design-direction.md
index 1fe11af..7b03dcc 100644
--- a/docs/design/2026-07-14-view-layer-design-direction.md
+++ b/docs/design/2026-07-14-view-layer-design-direction.md
@@ -29,7 +29,7 @@ exactly the things this app exists to fix.
```
Eight colors, three faces. Near-black background, one lime LED, one amber, one warm off-white
-ink, one grey. Everything else in both files is a tint of those.
+ink, one gray. Everything else in both files is a tint of those.
### The signature moves, in order of how much they matter
@@ -62,7 +62,7 @@ ten uses. Port it.
.write:active{transform:translateY(4px);box-shadow:0 1px 0 #5e8a16}
```
-A key with 6px of travel that bottoms out when you press it. Labelled **Write & Reboot**.
+A key with 6px of travel that bottoms out when you press it. Labeled **Write & Reboot**.
**4. The transport toggle** (`ks_web.cpp:107-113`) — one control, three states, state *is* the
label and the color:
@@ -74,7 +74,7 @@ label and the color:
.tgl:disabled{opacity:.45;cursor:not-allowed}
```
-**Stopped is not neutral grey — it's an ember**: dark maroon `#2A1512 → #1C0F0D` with salmon
+**Stopped is not neutral gray — it's an ember**: dark maroon `#2A1512 → #1C0F0D` with salmon
`#FF7A6B` text. That's a good decision. It reads "loaded, not firing," and it distinguishes
stopped from disabled (which is just `opacity:.45`).
@@ -148,7 +148,7 @@ All three faces are SIL OFL 1.1; bundle them and keep the identity 1:1.
| Mono | DM Mono 400/500 | bundle; fallback `.system(design: .monospaced)` | every value, every field, every pill |
| Segment | DSEG7 Classic | bundle — **required** for the ghost trick | the BPM readout only |
-If DSEG7 can't be bundled, fall back to DM Mono heavy and **delete the ghost layer**. A grey
+If DSEG7 can't be bundled, fall back to DM Mono heavy and **delete the ghost layer**. A gray
duplicate of a non-segmented font doesn't read as unlit segments; it reads as a rendering bug.
Two non-negotiables: **`.monospacedDigit()` on every number** (a 3-digit BPM must not reflow a
@@ -191,8 +191,8 @@ in display 800 at 17pt, `.tracking(2)`, uppercase.
**The three states, ported literally:**
-- `.stopped` — `KS.emberFill`, 1pt `KS.line` stroke, `KS.ember` label. Keep the ember. Grey would
- be the obvious choice and it's the wrong one: grey is what *disabled* looks like, and a stopped
+- `.stopped` — `KS.emberFill`, 1pt `KS.line` stroke, `KS.ember` label. Keep the ember. Gray would
+ be the obvious choice and it's the wrong one: gray is what *disabled* looks like, and a stopped
output is the opposite of disabled — it's cocked.
- `.armed` — `KS.amberFill`, **1.5pt** `KS.amber` stroke, `KS.amber` label, **and it blinks.**
- `.running` — `KS.ledOn`, `KS.ledEdge` stroke, `KS.onInk` label, plus
@@ -552,7 +552,7 @@ press:
.animation(.easeOut(duration: 0.06), value: pressed)
```
-Labelled, in the device's own words: **WRITE & REBOOT**. Behind an alert that **names the
+Labeled, in the device's own words: **WRITE & REBOOT**. Behind an alert that **names the
device** — `"Write & Reboot kitchensync-a4f2?"` — because you probably have three of them on the
bench.
@@ -609,11 +609,11 @@ genuinely dark, which is exactly what the hardware is doing), everything else di
`.opacity(0.35)`, and a mono caption reads `REBOOTING…`. Polling continues. The instant `/status`
answers, snap back and the beat dot starts flashing. This is precisely what the device's own page
does — `send_result(..., reboot: true)` renders `ui_result_page` with poll-until-alive so the
-browser isn't stranded on `/save` (`ks_web.cpp:560-563, 611`). Same behaviour, ported.
+browser isn't stranded on `/save` (`ks_web.cpp:560-563, 611`). Same behavior, ported.
**Firmware sheet (T-008), briefly:** same reboot vocabulary, same consequence band, plus the
reassurance the task asks for — *"A failed flash does not brick the device. Dual-slot OTA means it
-stays on the current firmware."* — and the target acknowledgement, since nothing in the client
+stays on the current firmware."* — and the target acknowledgment, since nothing in the client
validates the binary against the chip. Progress in `KS.ledOn` on `KS.panel`. `.fileImporter`
limited to `.data` with a `.bin` extension check.
diff --git a/docs/plans/2026-07-14-performance-settings-split-design.md b/docs/plans/2026-07-14-performance-settings-split-design.md
index b3724bb..c94a38c 100644
--- a/docs/plans/2026-07-14-performance-settings-split-design.md
+++ b/docs/plans/2026-07-14-performance-settings-split-design.md
@@ -40,7 +40,7 @@ Applies instantly; the field pulses lime to prove it.
- Per output: enable, CABLE, RATE, follow-Link
- Click: volume, voice, accent — **only if a speaker is fitted**
-- LED: brightness, mode, fade, colours — **only if a strip is wired**
+- LED: brightness, mode, fade, colors — **only if a strip is wired**
- Diagnostics (tick / phase health)
- A door at the bottom → **Device Setup**
@@ -64,7 +64,7 @@ this app exists to fix, and it is fixed *structurally*, not with labels:
declared reboot-required.
Merging everything into one Settings sheet would have re-created that adjacency with better
-labelling. Better labelling is not the same as impossible.
+labeling. Better labeling is not the same as impossible.
## Capability still drives visibility
diff --git a/tasks/T-001.md b/tasks/T-001.md
index 935c773..d92b5d2 100644
--- a/tasks/T-001.md
+++ b/tasks/T-001.md
@@ -47,7 +47,7 @@ Done — Xcode 26.6, Swift 6.3.3, xcodegen 2.45.4.
**The scaffold compiled clean on the first real build.** Zero Swift errors, zero warnings, even
under `SWIFT_STRICT_CONCURRENCY: complete` (verified applied via `-showBuildSettings`). None of
-the three predicted failure modes above materialised — the Sendable diagnostics in particular
+the three predicted failure modes above materialized — the Sendable diagnostics in particular
never appeared. The only build failure was the expected missing `_main`.
Added `Sources/KitchenSyncApp/KitchenSyncApp.swift`: `@main` + a deliberately inert `RootView`
diff --git a/tasks/T-003.md b/tasks/T-003.md
index 27964b7..7e78aba 100644
--- a/tasks/T-003.md
+++ b/tasks/T-003.md
@@ -37,7 +37,7 @@ wraps it with that output's config summary (cable, ppqn, phase, swing).
## Notes
**Play is quantized; Stop is immediate.** A tap on `.stopped` arms the output, which waits for
-the next bar line before it runs. That asymmetry is Ableton's own launch behaviour and it is
+the next bar line before it runs. That asymmetry is Ableton's own launch behavior and it is
deliberate. The blinking `.armed` state is therefore load-bearing UI, not decoration — it is the
only feedback that the tap registered, and it may persist for most of a bar.
diff --git a/tasks/T-007.md b/tasks/T-007.md
index e3ceb04..7add965 100644
--- a/tasks/T-007.md
+++ b/tasks/T-007.md
@@ -39,7 +39,7 @@ device** — mid-set, that is a destructive act, and the UI has to say so.
own web form, which won't echo a saved password back either. `WifiSlot` exposes only
`passwordIsSet`. New passwords ride in `WifiCredentialEdit`, and `saveFormFields` omits the
`wifi_pass` field entirely when the string is empty — that's `ks_config_set`'s documented no-op
-for "keep current". So the empty-means-keep behaviour is already correct in the model layer; this
+for "keep current". So the empty-means-keep behavior is already correct in the model layer; this
task is about making sure the *user* understands it.
`wifiEdits` is keyed by slot `id`, not array position. If the view filters or reorders the slots
diff --git a/tasks/T-010.md b/tasks/T-010.md
index 3e34997..e866d9d 100644
--- a/tasks/T-010.md
+++ b/tasks/T-010.md
@@ -35,7 +35,7 @@ default.
Don't just surface the error — decide the threshold. At a 2s poll, one dropped request on a busy
LAN is noise, not a disconnection; flapping a red badge on every missed packet is worse than the
-current behaviour. Something like "3 consecutive failures" is the shape.
+current behavior. Something like "3 consecutive failures" is the shape.
This one is load-bearing for T-007 and T-008: both deliberately reboot the device, and neither
has an honest way to show "it's coming back" until unreachability is a state the app can model.
diff --git a/tasks/T-014.md b/tasks/T-014.md
index 76d9543..b384eae 100644
--- a/tasks/T-014.md
+++ b/tasks/T-014.md
@@ -45,7 +45,7 @@ reflashed (`link-devices` ESP-031). Matching on TXT *only* would make every exis
disappear — a worse bug than the one being fixed. So: TXT when present, hostname prefix otherwise,
and widen the prefix set to include `kstouch-`.
-The prefix fallback stays a heuristic and should be labelled as one in the code. It exists to keep
+The prefix fallback stays a heuristic and should be labeled as one in the code. It exists to keep
un-updated units working, not because it is correct.
`NWBrowser` surfaces TXT via the result's `metadata` (`.bonjour(NWTXTRecord)`). Extract the decision
diff --git a/tasks/T-016.md b/tasks/T-016.md
index f07ea62..7823028 100644
--- a/tasks/T-016.md
+++ b/tasks/T-016.md
@@ -42,7 +42,7 @@ dead network) — this is the identical mistake, one endpoint over.
- [ ] The list shows something honest for it — e.g. `UNSUPPORTED`, not `UNREACHABLE`
- [ ] The detail screen explains the real cause: this device speaks a different `/status`, its
firmware needs updating (`link-devices` ESP-029), and points at the OTA path
-- [ ] A genuinely absent device still reads `UNREACHABLE` (T-010's behaviour is preserved)
+- [ ] A genuinely absent device still reads `UNREACHABLE` (T-010's behavior is preserved)
- [ ] A fixture built from the REAL Touch `/status` response is in the test suite
- [ ] Written test-first per the /tdd rule
diff --git a/tasks/T-018.md b/tasks/T-018.md
index ebc67fc..47d396b 100644
--- a/tasks/T-018.md
+++ b/tasks/T-018.md
@@ -61,7 +61,7 @@ disconnection (T-010). Now: a lifetime counter is not a live fault. Every one of
shape — **two different situations collapsed into one signal, and the user sent to fix the wrong
thing.**
-Worth generalising the rule rather than fixing it a fifth time: *before showing a warning, ask what
+Worth generalizing the rule rather than fixing it a fifth time: *before showing a warning, ask what
the user would DO about it. If two causes need different actions, they need different signals.*
`maxStepMicros` is deliberately still un-warned — nobody has told us a threshold, and inventing one
diff --git a/tasks/T-021.md b/tasks/T-021.md
index 648439c..ea3f6c2 100644
--- a/tasks/T-021.md
+++ b/tasks/T-021.md
@@ -60,7 +60,7 @@ translations.
This is the recurring bug of the codebase in structural form: the same decision, made in more than
one place, drifts. Every one of T-009 / T-010 / T-016 / T-018 was "two situations collapsed into one
-signal." Centralising the classification makes the fifth instance harder to write by accident.
+signal." Centralizing the classification makes the fifth instance harder to write by accident.
Source: architecture review 2026-07-14 (candidate 02). Pairs naturally with T-020 — both are
"a decision spread across call sites, give it one home." Do T-020 first (higher leverage).
diff --git a/tasks/T-024.md b/tasks/T-024.md
index 43927de..5aa4863 100644
--- a/tasks/T-024.md
+++ b/tasks/T-024.md
@@ -55,7 +55,7 @@ out for a unit test, with one caller and an interface larger than its body, is s
definition; naming that is the point of the review, even when the verdict is "leave it."
No firmware/protocol risk here — `state == .stopped` is app-side transport intent, and the quantized
-arm/fire behaviour is unaffected (this only decides which *request* a tap sends).
+arm/fire behavior is unaffected (this only decides which *request* a tap sends).
Source: architecture review 2026-07-14 (candidate 05).