§ · 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
- Per-page Open Graph cards: every server-rendered page (about, FAQ, recipes, blog index, changelog, when-to-use guide, and the three compare pages) now ships its own 1280×640
og.pngwith the page's title rendered in the site style. Generated byTools/site/blog-og.py, which grew a generic page renderer alongside the original blog-post one.
[2.1.6] - 2026-07-16
> Cumulative section: also includes changes that landed across releases > 2.0.9–2.1.6, a span during which the changelog was not cut per patch > release.
Added
- Comparison pages at
/compare/napkin-vs-ribs/,/compare/napkin-vs-tca/, and/compare/napkin-vs-viper/, plus a/when-to-use-napkin/decision guide — every external claim verified against primary sources and cited on-page; comparison tables date-stamped. /about/page andPersonauthor metadata (structured data) on the landing page and all blog posts; the rating-lessSoftwareApplicationblock was removed (its Google rich result requires a review/rating that an OSS framework cannot honestly supply).- Three FAQ entries: "Is RIBs dead?", "How is napkin different from The Composable Architecture (TCA)?", and "Should I use napkin for my app?" (now 21 questions, HTML and FAQPage JSON-LD in sync).
- "How napkin compares" table in the README linking the cited compare pages.
MARKETING.md— off-site discoverability playbook: ready-to-post drafts (Show HN, r/swift, iOS Dev Weekly), Google Search Console / Bing verification steps, and an awesome-swift entry held until the 15-star gate..spi.ymlexternal_links.documentationpointing the Swift Package Index docs link at getnapkin.to (replaces SPI auto-hosting a duplicate DocC copy).
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
- Docs version picker: shows the actual current release as its default label (was the literal word "version") and lists the latest patch of each minor in the current major plus the newest release of each previous major (was: the last five tags, i.e. five patches of one minor).
- DocC site title set via
theme-settings.jsonso rendered doc pages read "Page | napkin".
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).
Fixed
- Docs version picker no longer opens and instantly closes: the
<select>was wrapped in a<label>, whose forwarded activation click closed the native dropdown immediately (worst in Safari). The wrapper is now a<span>with the accessible name on the select itself.
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
- Documentation deploys now build DocC with
--experimental-transform-for-static-hosting-with-content(detected at build time, falls back cleanly), putting each doc page's real title and full text into its static HTML — readable by crawlers that don't execute JavaScript. sitemap.xmlandrobots.txtare generated at deploy time (Tools/site/gen_sitemap.py,gen_robots.py, with unit tests): the sitemap covers every page including all DocC routes (~125 URLs, was a hand-maintained 12), and robots.txt welcomes AI crawlers by name while blocking stale versioned doc copies from indexing.- Every deploy pings IndexNow (Bing) with the full URL list.
- 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 directly (rebind with@Bindableinbodyfor two-way bindings) and 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 lifecycle-bound tasks inside didBecomeActive: task { for await … in service.stream() { … } } for service streams, or — for @Observable state — task { @MainActor [weak self] in for await … in Observations({ … }) { … } }. The observation loop must be bound to the actor that owns the state; iterating Observations directly inside the nonisolated task {} closure crashes the current Swift 6 compiler (swiftlang/swift#90370). Tasks are 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 weak var presenter: HomePresenter?, reading its properties directly and rebinding with @Bindable inside body only when a two-way binding is needed. Replace event handlers with dispatch { await listener?.didTapX() }. 8. Builder.build(...) becomes async @MainActor when it constructs a view controller.
See Examples/RibHouse 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).