Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d0797f1
Add Test Plans
Dfowj Jul 12, 2025
8f29fbf
Fix scheme naming in VerifyChanges.yaml
Dfowj Jul 12, 2025
fa30361
Fix gitignore
Dfowj Dec 31, 2025
e6e8512
Update to the latest build scripts, github workflows, and documentati…
Dfowj Jan 2, 2026
ed365cb
Add DevFoundation dependency, update DevTesting dependency
Dfowj Jan 2, 2026
c297f4d
Add Architecture & Implementation plan for guiding agents
Dfowj Jan 3, 2026
245b156
Generate CLAUDE.md, add Scripts/format, and update README
Dfowj Jan 3, 2026
229b621
Reorganize Architecture/Implementation Plan
duncan-daydream Jan 3, 2026
496572f
Introduce swift-configuration dependency
duncan-daydream Jan 3, 2026
c6ee212
Revisions to Architecture/Implementation plan.
duncan-daydream Jan 6, 2026
50cb399
Fix unit test
duncan-daydream Jan 6, 2026
fae16ec
Fix VerifyChanges.yaml
duncan-daydream Jan 6, 2026
edbe774
Fix VerifyChanges.yaml
duncan-daydream Jan 6, 2026
7bdc469
Revised architecture: remove ConfigurationDataSource from scope, cons…
duncan-daydream Jan 6, 2026
de1872b
Add VariablePrivacy to planning docs
duncan-daydream Jan 7, 2026
f53ce86
Change Double -> Float64
duncan-daydream Jan 7, 2026
dead737
Leave note about handling change observation with `watch()`
duncan-daydream Jan 7, 2026
1f0267c
Fix reference to codebase name
duncan-daydream Jan 7, 2026
c706f5f
Reorganize Slice 1 plan.
duncan-daydream Jan 7, 2026
eedcb7f
Introduce ConfigVariable
duncan-daydream Jan 7, 2026
11bd8ac
Introduce VariablePrivacy
duncan-daydream Jan 7, 2026
72a2d92
Introduces the StructuredConfigReading protocol
duncan-daydream Jan 7, 2026
86c2eb8
Make executable Scripts/format
duncan-daydream Jan 7, 2026
625752d
Introduces StructuredConfigReader, with basic support for reading con…
duncan-daydream Jan 7, 2026
07f8661
StructuredConfigReader uses variable privacy to provide the `isSecret…
duncan-daydream Jan 7, 2026
fed3d46
Introduces telemetry for access reporting
duncan-daydream Jan 8, 2026
d9291d0
Refactor StructuredConfigReader initializer
duncan-daydream Jan 8, 2026
a49acd4
Expand StructuredConfigReader's documentation
duncan-daydream Jan 8, 2026
8f334a3
Adjust telemetry events
duncan-daydream Jan 13, 2026
750f449
Introduce ConfigVariable subscript access functions to StructuredConf…
duncan-daydream Jan 13, 2026
28227ba
Rename ConfigVariable.fallback to ConfigVariable.defaultValue
duncan-daydream Jan 13, 2026
5b41e81
Fix merge conflicts
Feb 16, 2026
76c228e
Add fetch and watch variants, do a bunch of renames, etc.
Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(swift build:*)",
"Bash(grep:*)"
]
}
}
171 changes: 171 additions & 0 deletions Documentation/MVVMForSwiftUI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# MVVM for SwiftUI

This document defines the MVVM (model-view-view model) architecture pattern we use to write
SwiftUI code in the DevConfiguration codebase. For more background on this MVVM pattern, see
[MVVMForSwiftUIBackground.md](MVVMForSwiftUIBackground.md).


## Overview

Our SwiftUI architecture is composed of thrre related types: a \*View, a \*ViewModeling, a
and \*ViewModel. Screens in the app will often have each of these three types, though it
depends on the screen’s complexity and requirements. Additional types for dependency
injection are also common.

The following sections will demonstrate the types we would create for a "ItemList" screen:


### ItemListViewModeling

We begin by defining a protocol that describes the minimal interface for the view to perform its
function. In this case, we want the view to display a list of items. We use the `Observable`
protocol to allow the view to react to changes in the view model.

@MainActor
protocol ItemListViewModeling: Observable {
var items: [ListItem] { get }
func addRow()
}


### ItemListView

Next we define the view, which has no business logic and simply reflects the state of its single
property, `viewModel`. The view uses a generic parameter named `ViewModel` that conforms to the
`ItemListViewModeling` protocol. The `viewModel` property is marked as `@State` so that the view
can react to changes in the view model.

struct ItemListView<ViewModel>: View where ViewModel: ItemListViewModeling {
@State var viewModel: ViewModel

var body: some View {
List {
ForEach(viewModel.items) { item in
Text(item.name)
}
}
}
}


### ItemListViewModel

Finally, we create a concrete representation of our view model by implementing the
`ItemListViewModeling` protocol. This is the type that will be used by the view to display the
list of items.

**Note**: We must declare our view model as `@Observable` to enable Swift’s property observation
mechanism. Protocols may declare `Observable` conformance as a convenience for consuming code, but
it does not confer any special behavior on conforming type itself.

@Observable
final class ItemListViewModel: ItemListViewModeling {
var items: [ListItem]
}


### ItemListViewModelDependencyProviding/Provider

If the view model requires dependencies to perform its function, we follow the guidance in the
[Dependency Injection](DependencyInjection.md) guide. In this case, the view model requires a data
source to fetch the list of items. The Dependencies Struct pattern would be appropriate for this
case.

@Observable
final class ItemListViewModel: ItemListViewModeling {
struct Dependencies {
let itemFetcher: any ItemFetching
}

var items: [ListItem]

init(dependencies: Dependencies) {
self.items = dependencies.itemFetcher.fetchItems()
}
}


### Putting It All Together

Typically, a parent view is responsible for creating the child view, using the parent view model to
create the child view model. The parent view model creates the child view model by instantiating it
with a dependency provider.

We see this below with the `HomeView`/`HomeViewModel` pair.

@MainActor
protocol HomeViewModeling: Observable {
associatedtype SomeItemListViewModel: ItemListViewModeling
func makeItemListViewModel() -> SomeItemListViewModel
}


@Observable
struct HomeViewModel: HomeViewModeling {
func makeItemListViewModel() -> ItemListViewModel {
return ItemListViewModel(
dependencies: .init(
itemFetcher: StandardItemFetcher()
)
)
}
}


struct HomeView<ViewModel>: View where ViewModel: HomeViewModeling {
@State var viewModel: ViewModel

var body: some View {
ItemListView(viewModel: viewModel.makeItemListViewModel())
}
}


### Making State Changes

View models are responsible for managing changes to the application’s state. Typically, they do so
by providing parameterless functions that the view can call to trigger state changes.

@Observable
final class ItemListViewModel: ItemListViewModeling {
var items: [ListItem]


init(items: [ListItem]) {
self.items = items
}


func addRow() {
items.append(ListItem(name: "New Item"))
}
}

The view can then call the `addRow` function to add a new row to the list.

struct ItemListView<ViewModel>: View where ViewModel: ItemListViewModeling {
@State var viewModel: ViewModel


var body: some View {
List {
ForEach(viewModel.items) { item in
Text(item.name)
}
}
.toolbar {
Button {
viewModel.addRow()
} label: {
Image(systemName: "plus")
}
}
}
}


## Misc. Instructions for AI

### Import Requirements

- Always include `import SwiftUI` in view files.
102 changes: 102 additions & 0 deletions Documentation/MVVMForSwiftUIBackground.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# MVVM for SwiftUI

SwiftUI is a great way to build great looking apps for Apple platforms. Unfortunately, Apple’s
example code doesn’t demonstrate good architecture. In this doc, we’ll outline some of our thoughts
on how to use the MVVM (model-view-view model) architecture in SwiftUI applications.


## Architectural Goals

Before we begin, let’s discuss our goals. We’re interested in writing code that is:

- *Correct*: the code should implement requirements with as few bugs as possible.
- *Robust*: the code should handle unexpected situations gracefully.
- *Adaptable*: within reason, we should be able to adapt our code for use in situations that we
didn’t know about when we originally wrote it.
- *Maintainable*: code is more often read than written, and thus should be easy to understand and
reason about. It should be relatively easy to make a change without introducing bugs.
- *Testable*: building correct, adaptable, and maintainable code is very difficult without a large
suite of automated tests. Thus we need to architect our code so that it is easy to test using
standard unit and integration testing techniques.
- *Portable*: while we primarily write apps that target iOS, that may not always be the case. We
may support other Apple platforms—tvOS, watchOS, or macOS—or we may ship different kinds of
apps, e.g., iMessage extensions or command-line tools for testing. Our code should be portable
enough to enable these use cases.
- *Efficient*: our code should always aim to efficiently use a device’s resources. We should avoid
using any more CPU, memory, battery, or network data allocations than is needed.

Any architectural pattern that we use should support these goals. The portability goal in particular
is one that the iOS community doesn’t often emphasize, but that we believe is very important. When
reasoning about MVVM, you will make better architectural decisions if you don’t make assumptions
about the types of UIs your code will support. Keep that in mind as you read this document.

## A Quick Overview of MVVM

It’s helpful to do a quick refresher of MVVM to make sure we all understand the roles of different
components in the architecture.

- *Models* represent your application’s concepts and operations. That is, they “model” the problem
domain of your app, independent of any particular UI representation. The model layer is where
the real work of your app happens. Because it’s independent of the UI, it should be largely
reusable and portable. For example, the business logic involved in performing a restaurant
search, fetching a menu, and adding a menu item to a cart should work the same, regardless of
what your UI looks like.
- *Views* represent the user interface of your application. While this obviously includes
traditional parts of your UI, like buttons and screens, it also includes non-visual UI, like
speech and textual interfaces. It could go so far as to include a scripting interface since that
is just a way to interface with your application. The big idea here is that the views present
information to users and receive user input. They provide an interface for users to interact
with your model layer and reflect application state.
- *View Models* mediate between models and views. They have properties for view state, which the
view uses to render its information. When a view model receives a change from the model layer,
it translates the change into a view state change, which triggers a re-rendering of the view.
View models also have functions that are used by the view to trigger business logic in response
to user input.

## SwiftUI and MVVM

The core challenge with SwiftUI is that views are very difficult to test. They are structs that
produce a view body using a declarative DSL, making them hard to test and unsuitable for complex
logic. As such, we must make logic inside of views simple, with minimal branching, data
transformations, etc. View models should be the central location for view logic. Models in MVVM are
no different than models in (correct) MVC: they should contain all UI-agnostic logic.

In SwiftUI, a view has a reference to one or more view models, which in turn have references to one
or more models. View models do not have a reference to their views, and models do not have a
reference to their view models. To propagate information from a model to a view model, models can
either return values from properties or functions (manual propagation) or publish them via Combine
Publishers, Async Streams, Notifications, or Delegate protocols (automatic propagation). View
models propagate information to views almost entirely through published properties. View models
should very rarely have functions exposed to views that return a value.

Views use generic parameters to specify their view model types, avoiding existential types for
better performance and type safety. Child view models within view modeling protocols are represented
as associated types rather than protocol existentials.

In our SwiftUI app architecture, each view is composed of three related types: a \*View, a
\*ViewModeling, and a \*ViewModel. The \*ViewModel type will often have either a nested Dependencies
struct or nested DependencyProviding and DependencyProvider types. See our
[Dependency Injection](DependencyInjection.md) guide for more details.

- *View*: The view code. The view has no business logic and simply reflects the state of its view
model, which is a generic parameter named `ViewModel` that conforms to the \*ViewModeling protocol.
- *ViewModeling*: A protocol that contains the minimal interface for the \*View to perform its
function. Each contains properties that are used to store the view’s state, as well as enums
that govern the view’s modals and alerts.

All properties of a view are either simple data types with no behavior or associated types that
represent child view models. Any child view models are defined as associated types (named
`Some*ViewModel`) that conform to their corresponding `*ViewModeling` protocol, rather than
using existential types.

Functions defined on the view model typically take no parameters. These functions either perform
some action using the view model’s underlying model and/or update a state variable to, e.g.,
show a modal or an alert.

Where possible, these protocols and their supporting types are modeled such that impossible
states do not compile. That is, if a button should only appear when a view is in a particular
state, that action function for that button should only be available when the view is in that
state. This can be achieved using algebraic data types (enums with associated values).
- *ViewModel*: A concrete implementation of the \*ViewModeling protocol. Often, these view models
have delegate protocols that are used to communicate changes between parent and child views
models.
Loading