# Tiledown Conventions The Swift coding conventions for Tiledown. They keep the codebase consistent, testable, and portable. Read this before opening a PR. When in doubt, read the surrounding files and match what is already there: consistency with existing code outranks personal preference. These are the **Swift domain** conventions: the language- and platform-specific craft. They sit on top of a vendored **core spine** of cross-cutting engineering discipline (the optimal-path ethic, proof and verification, testing discipline, systematic debugging, commits, and git process). That spine lives in [`core/`](core/); read it first, the Swift rules build on it and do not repeat it. This page is the overview. The full per-area Swift rules live at the repository root (index at [`README.md`](README.md)), and the cross-cutting rules in [`core/`](core/). ## Language Swift for everything. The single exception is JavaScript, and only where it is intrinsic to the output: client-side tiles (Mermaid, charts, forms, polls) emit HTML and JS that run in the visitor's browser. JS is never used for build logic or tooling. ## Engineering principles 1. **Optimal over fast.** Respect existing code and idioms. Clarify ambiguity before coding rather than assuming requirements. When a real trade-off exists, surface two or three options instead of guessing. 2. **Progressive architecture.** Start with the simplest thing that works. A protocol is earned by a real cross-target seam or by multiple implementations; do not wrap a lone same-target concrete type preemptively. Generalise only when a pattern has actually emerged. 3. **Make impossible states unrepresentable.** Use exhaustive enums with associated values. Never force-unwrap (`!`, `try!`) in shipping code. Errors carry both a human-readable reason and an actionable recovery path. 4. **Testable by design.** Inject every collaborator through the initialiser. Test behaviour through the public API, not implementation details. 5. **Profile, then optimise.** Value semantics by default. Pick the right data structure first. Optimise only with a profile in hand. ## Dependency injection - **No singletons.** No `static let shared`, no process-wide config reached through static accessors. Every dependency appears at the `init` site so coupling is visible, testable, and removable. - **Every external collaborator goes through `init`.** Not method parameters at the call site, not static fallbacks, not environment lookup. Pure free functions that compute from their arguments are fine. - **Cross-module seams are protocols, not concrete imports.** A library module does not reach into another module's concrete types. The executable (the composition root) is the only place that wires concretes together. - **No closure typealiases for named cross-module contracts.** Use a named protocol. Closures as ordinary method parameters (`onProgress:`) are fine. ## Namespacing and file layout - **Every public implementation type lives under an `enum`/`struct` namespace that mirrors its folder.** The only top-level public contracts are protocols and supporting value/null/preview types in the foundation-only `SharedProtocols` target, where the module itself is the namespace. Reading `Module.Sub.Leaf` should tell you where every other type lives and what it does. - **Namespace anchors are caseless `enum`.** Use `struct` only when the type is also a value. Never `class` for a namespace. Shared mutable state is an `actor` or an injected value, never a `class`. - **Drop redundant context.** Under `Availability`, the error is `Availability.Error`, not `AvailabilityError`. - **Concrete types are declared via extensions** on the leaf namespace. Conformances may be separate extensions on the qualified path. - **One non-private type per file.** Private helper types may co-locate when they exist only to support the main type. - **File naming.** A file declaring `extension Foo.Bar { public struct X }` is named `Foo.Bar.X.swift` (dots, matching the qualified name). Anchor files contain only the namespace declaration, no implementation. ## Concurrency - Swift 6 strict concurrency is on. Types crossing concurrency boundaries are `Sendable`; prove it, do not silence it with `@unchecked` unless you have a documented reason. - Shared mutable state goes in an `actor`. UI-affine state is `@MainActor`. - Use structured concurrency. No arbitrary `Task.sleep` to paper over ordering. ## Cross-platform Tiledown builds and runs on Apple platforms and Linux, with Windows where explicitly noted. The TileKit engine itself targets macOS and Linux; the broader cross-platform discipline (and the CI matrix in [cross-platform.md](cross-platform.md)) extends to Windows for code that opts into it. - Guard platform-divergent code and abstract platform-specific dependencies behind a protocol seam: one implementation per platform, wired by the composition root. Subprocess use is allowed. - Prefer pure-Swift implementations over Foundation-only conveniences in the core. - On the server side, prefer cross-platform libraries (for example AsyncHTTPClient over URLSession, swift-log over os.log). ## Testing - Use the **Swift Testing** framework for unit and integration tests: `@Test`, `@Suite`, `#expect`. Use XCTest only where the platform harness requires it, notably XCUITest, or while migrating an existing suite that cannot move in one change. - One behaviour per test. Descriptive names. Deterministic data. No live network or filesystem dependence; inject test doubles. - One test target per source target, named `Tests`. - Use parameterised tests (`@Test(arguments:)`) for families of similar cases. ## Verification before "done" The discipline lives in [`core/verification.md`](core/verification.md): no claim of done, fixed, or passing without fresh command output in the same response. For Swift, match the command to the claim: | Claim | Command | |---|---| | Build succeeds | `swift build` | | Tests pass | `swift test` (cite the count, e.g. `42 / 42`) | | Bug fixed | the test that reproduced it, now passing | "Looks good" and "should pass" are not evidence. ## Commits, branches, PRs See [`core/commits.md`](core/commits.md) and [`core/git-discipline.md`](core/git-discipline.md), and the project's [CONTRIBUTING.md](CONTRIBUTING.md). In short: Conventional Commits, one focused change per PR, a `CHANGELOG.md` entry for any change touching shipping source, and no AI attribution or em dashes in any committed text.