§ · Release notes
Changelog.
Mirrors the GitHub CHANGELOG. Keep a Changelog format; Semantic Versioning.
All notable changes to napkin are documented here. Format follows Keep a Changelog; versioning follows Semantic Versioning.
[Unreleased]
Added
AGENTS.mdat the repo root — conventions for AI coding agents working in the codebase (napkin folder layout, isolation patterns, listener protocols,dispatch { }vsTask { }, file naming, iCloud-Drive artifact warning).getnapkin.to/llms.txtand/llms-full.txt— emerging conventions for LLM-readable site maps. The full variant is generated at deploy time from the DocC.mdfiles so it stays in sync with the catalog.Sources/napkin/napkin.docc/Articles/TutorialBuildingALoginFlow.md— guided walkthrough of the runnable example app (Napkin's Rib House) through the napkin pattern, end to end. Uses@TabNavigator,@Row/@Column, asides, and@Links(visualStyle: detailedGrid).- "Step 7: Snapshot testing the views" section in the tutorial — covers declaring the package, target wiring, record-then-verify workflow, and the two ways to opt into re-record mode.
Examples/RibHouse/SnapshotTests/— Point-Free swift-snapshot-testing target. Reference PNGs under__Snapshots__/pin each view's appearance. The same PNGs are committed toSources/napkin/napkin.docc/Resources/andTools/site/so the tutorial and homepage display the exact images CI compares against.- New "§ 04 · See it in motion" section on the homepage — auto-cycling 4-step tutorial: iPhone-framed snapshot fades between LoggedOut and LoggedIn while code blocks stagger their lines in like an editor typing them out. Pure CSS animation + ~30 lines of vanilla JS;
prefers-reduced-motionstacks the four steps statically.
Changed
Examples/LaunchNapkinApprenamed toExamples/RibHouse("Napkin's Rib House") with a barbecue-themedAuthServicemock. Display name isNapkin's Rib House; bundle ID iscom.napkin.example.RibHouse.- The example app's source tree is now organized one-napkin-per-folder (
App/,Shared/,LaunchNapkin/,LoggedOutNapkin/,LoggedInNapkin/). UI test file renamed toRibHouseUITests.swift. - The example app's
.xcodeprojis now tracked in source control — the project opens viaopen Examples/RibHouse/RibHouse.xcodeprojwithout an XcodeGen step..gitignoreun-ignores the project explicitly while still ignoring user state. - Example app architecture pivoted from the prior Counter + Quote demo to a login/logout flow that exercises service injection: a headless
LaunchNapkinholds anAuthService(declared in the dependency protocol) and swaps aLoggedOutNapkin(single Login button) for aLoggedInNapkin(user name + barbecue food list) on tap. TheUserobject flows through the full interactor → router → builder → child router chain. - LoggedOut and LoggedIn views restyled with editorial vocabulary matching the marketing site — kicker (
§ 00 · WELCOME), serif-italic headlines, hairline rules, spec-list pattern for the foods, ink + ghost buttons. Palette tokens inShared/Palette.swiftare derived from the site's OKLCH design tokens. Sources/napkin/napkin.docc/theme-settings.jsonlink + intro-accent colors flipped from blue to moss-green so the docs and homepage read as the same design system.social-preview.svgnow hasrole="img"+<title>+<desc>+aria-labelledbyso screen readers reading the inline SVG get the same description as sighted users.Gemfile.lock:activesupport8.1.2 → 8.1.3 (clears 3 moderate Dependabot alerts on Rails CVEs innumber_to_delimited,SafeBuffer#%, and number helpers — fastlane transitive dep)..github/workflows/Documentation.yml:actions/checkoutuses shallow + tags + explicitref: main, fixing the APFS case-collision that masked deploys whenci/...namespaced branches existed alongside the staleCIbranch..github/workflows/Release.yml: narrowsremote.origin.fetchtomainbefore fastlane'sgit_pull(only_tags: true)to avoid the same case-collision..github/workflows/CodeQL.yml: switched fromautobuildto manualswift buildafter autobuild repeatedly tripped on a stale SPM cache reference toswift-docc-plugin.CONTRIBUTING.mdpaths updated for the RibHouse rename and the tracked-xcodeproj workflow (xcodegen is now optional).
Removed
Examples/LaunchNapkinApp/(renamed; see Changed).- The "Counter" and "Quote" child napkins from the example app (replaced by LoggedOut and LoggedIn).
- Stale local-only
Sources/napkin/PresentableInteractor.swiftorphan — a pre-rearchitecture class-based interactor that referenced types removed in 2.0.0. Was untracked but present in iCloud-synced clones. - The original
CI,Adding-Templates,Fastlane,TemplateFix, andswift-concurrency-rearchitecturebranches on origin (stale, all predate the v2 rearchitecture).
Infrastructure
- Migrated public site to a custom domain: https://getnapkin.to/. Apex A records target GitHub Pages' anycast IPs;
www.getnapkin.toCNAMEs towikipediabrown.github.io; HTTPS enforced; cert covers both apex andwww.wikipediabrown.github.io/napkin/301s to the custom domain. - Enabled GitHub Sponsors button via
.github/FUNDING.yml. - Enabled repository security analysis: secret scanning, push protection, Dependabot security updates, and private vulnerability reporting.
- Added branch protection on
mainanddevelop(block force-push and deletions; regular pushes still allowed). - Repo About panel: homepage URL now
https://getnapkin.to/; obsoletecombinetopic dropped;swift-6andstructured-concurrencytopics added. - New CI workflow
.github/workflows/CodeQL.ymlruns weekly + on push/PR for Swift static analysis on themacos-26runner.
[2.0.8]
Changed
- Homepage footer reflows onto two lines: framework attribution on the first line, copyright + license on the second. The version is now a single hyperlink to the GitHub release page; the duplicate "view release on GitHub" link has been dropped.
[2.0.7]
Added
Tools/site/— version stamping mechanism. The Documentation workflow resolves the latest git tag viagit describe --tags --abbrev=0and substitutes__NAPKIN_VERSION__placeholders inindex.htmlat deploy time so the footer always reflects the currently-shipping release.
Changed
- Homepage footer rewritten with framework attribution, copyright, license link, and "view release" link. Replaces the placeholder footer shipped in 2.0.6.
Examples/LaunchNapkinApp/consolidated: the app shell (AppDelegate/SceneDelegate/Info.plist) and the napkin implementations (Launch/Counter/Quote) now live side by side inSources/rather than in separate sub-trees. Build product paths inproject.ymlupdated accordingly.
[2.0.6]
Added
Tools/site/index.html,styles.css,napkin-icon*.png— hand-crafted homepage that replaces DocC's missing rootindex.htmlwith a real landing page (nav bar, hero, feature grid, footer). The Documentation workflow copies this into./docs/after the DocC build.Sources/napkin/napkin.docc/header.html— top-nav injected into every DocC page via--experimental-enable-custom-templates, so symbol and article pages share the homepage's navigation chrome.
[2.0.5]
Changed
- Replaced the ASCII-art isolation map in
IsolationModel.mdwith a hand-drawn SVG diagram. Ships a light variant and a dark variant; DocC selects the appropriate one based on the reader's color scheme.
[2.0.4]
Added
- Hero metadata on the
napkin.mdlanding page (@PageImage,@CallToAction,@Availabledirectives) so the docs root renders with a hero card on the modern DocC renderer. theme-settings.jsonenabling DocC'squickNavigation(cmd-K fuzzy-search across symbols) and pinning the accent color to the napkin blue palette.
Changed
- Two article pages (
IsolationModel.md,WorkingWithCombine.md) use@Row/@Columnfor side-by-side before/after code snippets. DefiningAFeature.mdadopts@Snippet(path:)for the five major Profile code blocks instead of inlining the Swift source.
[2.0.3]
Changed
- Corrected the etymology of the name "napkin" in the
GettingStartedarticle — the framework is named after the noun "napkin" (as in back-of-the-napkin sketches of a feature's architecture), not a verb.
[2.0.2]
Added
.github/workflows/Documentation.yml— builds the DocC catalog withswift package generate-documentationand deploys the static site to GitHub Pages viaactions/deploy-pages. Site is live athttps://wikipediabrown.github.io/napkin/documentation/napkin/.
[2.0.1]
Added
Sources/napkin/napkin.docc/— DocC catalog with a landing page (napkin.md) and seven articles inArticles/: GettingStarted, DefiningAFeature, IsolationModel, RouterTree, ComponentsAndScopes, TestingAsyncFeatures, WorkingWithCombine.- Comprehensive inline DocC comments on every public type and method in
Sources/napkin/.
[2.0.0]
Major rearchitecture: native Swift 6.2 concurrency, Combine removed.
This release breaks every public API surface and raises the deployment floor to iOS 26 / macOS 26. There is no incremental migration path; consumers re-adopt against the new shape.
Added
Interactableprotocol — feature interactors are nowfinal actortypes conforming to it instead of subclasses of anInteractorbase class. Swift actors cannot be subclassed (SE-0306); protocol composition replaces inheritance.InteractorLifecyclehelper — singlefinal class @unchecked Sendablethat holds mutex-protected lifecycle state. EachInteractabledeclaresnonisolated let lifecycle = InteractorLifecycle(). Default protocol extensions delegateactivate/deactivate/task(_:)/isActive/isActiveStreamto it.PresentableInteractableprotocol — refinesInteractableand adds anonisolated var presenter: PresenterType { get }requirement. Replaces the oldPresentableInteractor<PresenterType>base class.Interactor.task(_:)helper — spawns aTaskwhose lifetime is bound to the active scope (cancelled automatically indeactivate()). Napkin's analog of upstream RIBs'sdisposeOnDeactivate.dispatch(_:)helper —@MainActorfunction for forwarding async work from synchronous view callbacks (SwiftUI button handlers, UIKit@objcactions) into aTask.Examples/LaunchNapkinApp— minimal runnable iOS app generated by XcodeGen. Verified working on iPhone 17 / iOS 26.4.1 simulator.isolated deinitonRouter(SE-0371, Swift 6.2) — synchronous teardown on the main actor's executor withoutTask.detachedworkarounds.
Changed
- Deployment floor: iOS 13.0+ / macOS 10.15+ → iOS 26.0+ / macOS 26.0+.
Router,ViewableRouter,LaunchRouter,Routing: all@MainActor-isolated.attachChild/detachChild/load/loaded/launchare nowasync.loaded() asynconRouterreplaces thelifecycle: AnyPublisher<RouterLifecycle, Never>Combine publisher. TheRouterLifecycleenum has been removed.Presenter:@MainActor @Observable open class. Subclasses' stored properties are observable to SwiftUI views via@Bindableand to UIKit views viaObservations { presenter.foo }. ThePresentableprotocol is@MainActor.Component:Synchronization.MutexreplacesNSRecursiveLockfor shared-instance storage.ComponentandEmptyComponentare@unchecked Sendable.Builder,ComponentizedBuilder,MultiStageComponentizedBuilder,Buildable:Sendable. Concretebuild(...)overloads should beasync @MainActorwhen they construct a view controller.Listener/Routing/Presentableprotocol methods areasync— every cross-isolation call (interactor → router, interactor → presenter, parent's listener) goes through an explicitawait.Dependencyprotocol is nowSendable.- Xcode templates rewritten to emit code matching the new API —
final actorinteractors,@MainActorrouters, async listener/presentable protocols, thelifecycledeclaration.
Removed
- All Combine usage. No
import Combinein the framework, tests, examples, or templates. Interactoropen class. Replaced byInteractableprotocol +InteractorLifecyclehelper.PresentableInteractor<P>open class. Replaced byPresentableInteractableprotocol.RouterLifecycleenum,RouterScope.lifecyclepublisher,InteractorScope.isActiveStream(Combine variant). TheisActiveStreamgetter still exists but now returnsAsyncStream<Bool>.- The recursive
bindSubtreeActiveStatecascade inRouter. Activation now flows explicitly throughattachChild/detachChild.
Migration guide
For each feature:
1. Replace final class HomeInteractor: PresentableInteractor<HomePresentable> with final actor HomeInteractor: PresentableInteractable. 2. Add nonisolated let lifecycle = InteractorLifecycle() and nonisolated let presenter: HomePresentable as stored properties; remove the super.init(presenter:) call. 3. Drop override keyword from didBecomeActive / willResignActive (they're protocol default implementations now, not class overrides). Mark them async. 4. Replace cancellables and Combine .sink { … } subscriptions with task { for await … in Observations { … } } inside didBecomeActive. The task is auto-cancelled on deactivate. 5. Mark routing protocol methods async. Remove every Task { @MainActor in } wrapper from routing method bodies — routers are already @MainActor. 6. Mark listener and presentable protocol methods async. Conform listener protocols to Sendable. 7. SwiftUI views: replace @ObservedObject var viewModel: HomeViewModel with @Bindable var presenter: HomePresenter. Replace event handlers with dispatch { await listener?.didTapX() }. 8. Builder.build(...) becomes async @MainActor when it constructs a view controller.
See Examples/LaunchNapkinApp and the rewritten README.md for working reference code.
Divergence from upstream Uber RIBs-iOS
Uber's RIBs-iOS PR #49 unifies the framework on @MainActor (Interactor included). napkin deliberately keeps Interactors off the main actor so business logic is not pinned to the main thread. The cost is an explicit await at every cross-layer call; the benefit is enforced clean-architecture isolation of business logic.
[0.0.18] and earlier
See GitHub Releases for prior changelog entries (auto-generated from git history).