Video #226: Composable Navigation: Unification
Episode: Video #226 Date: Mar 13, 2023 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep226-composable-navigation-unification

Description
Let’s prepare to delete a lot of code. The navigation APIs we’ve built so far to drive alerts, dialogs, and sheets all have more or less the same shape. We can unify them all in a single package that can also be applied to popovers, fullscreen covers, and more!
Video
Cloudflare Stream video ID: a91dad29eac84d2cf84f5f5a05b22206 Local file: video_226_composable-navigation-unification.mp4 *(download with --video 226)*
References
- Discussions
- Composable navigation beta GitHub discussion
- 0226-composable-navigation-pt5
- Brandon Williams
- Stephen Celis
- Mastodon
- GitHub
- CC BY-NC-SA 4.0
- source code
- MIT License
Transcript
— 0:05
So this is all looking really incredible. We have substantially improved the sheet navigation tool we built previously by making sure that child effects are automatically torn down when the child feature goes away, and we provided a new tool that allows child features to dismiss themselves in a really lightweight way. It can be entirely encapsulated in the child feature. The parent doesn’t need to know about it at all.
— 0:27
And on top of that we dipped our toes into non-exhaustive testing. This tool is becoming more and more important because we keep making it easier to compose features together, and so there are going to be more times we want to write high level tests on how features interact with each other without needing to assert on literally everything happening in each feature. Brandon
— 0:46
There are even more powerful features we could continue adding to these presentation APIs, and we will soon, but let’s also take a moment to remember how we got here. A few episodes back we first dipped our toes into the waters of new navigation APIs by creating some tools for alerts. And since then we have basically copied and pasted code a bunch of times, first for confirmation dialogs and then again for sheets.
— 1:09
Let’s finally start unifying these APIs because soon we will want to generalize them even further for popovers, fullscreen covers, and navigation links, and I don’t think we want to copy-and-paste code 3 more times. Alert, dialog, sheet unification
— 1:23
Let’s start by unifying the action wrapper types. We now have AlertAction , ConfirmationDialogAction , and SheetAction , and they are all basically the same. Let’s unify them all under one banner, and let’s choose the name PresentationAction since they are all things that can be “presented”: enum PresentationAction<Action> { case dismiss case presented(Action) } extension PresentationAction: Equatable where Action: Equatable {}
— 2:05
With a few quick renames things are compiling, and we are on our way to unifying our APIs.
— 2:16
The next place we have a lot of code duplication is in our reducer operators. For example, the alert and confirmationDialog methods take basically the same arguments have have basically the same implementation: extension Reducer { func alert<Alert>( state keyPath: WritableKeyPath< State, AlertState<Alert>? >, action casePath: CasePath< Action, PresentationAction<Alert> > ) -> some ReducerOf<Self> { … } } extension Reducer { func confirmationDialog<DialogAction>( state keyPath: WritableKeyPath< State, ConfirmationDialogState<DialogAction>? >, action casePath: CasePath< Action, PresentationAction<DialogAction> > ) -> some ReducerOf<Self> { … } }
— 2:46
All they need to do is clear out the optional state whenever a presentation action comes through. There’s no other logic to layer on top of that.
— 2:59
Then there’s the sheet method, which also has very similar arguments, but needs a third argument for the reducer to run on the optional child state: extension Reducer { func sheet<ChildState: Identifiable, ChildAction>( state stateKeyPath: WritableKeyPath< State, ChildState? >, action actionCasePath: CasePath< Action, PresentationAction<ChildAction> >, @ReducerBuilder<ChildState, ChildAction> child: () -> some Reducer<ChildState, ChildAction> ) -> some ReducerOf<Self> { … } } This extra reducer is needed because unlike alerts and dialogs, when a sheet is open there is additional logic and behavior that can take place in that UI.
— 3:14
The implementation of sheet is a lot more complicated than for alerts and dialogs, but at their core they all have two responsibilities:
— 3:25
Detect when a presentation action comes through and layer on additional logic, whether that been clearing state, such as the case for alerts, or handling effect cancellation and dismissal, as is the case for sheets. Run the parent reducer when a non-presentation action comes through.
— 3:42
So, while their implementations are wildly different, philosophically they are quite aligned, and that makes me think we could even unify these operators.
— 3:52
Let’s give it a shot.
— 3:53
We are going to copy-and-paste the sheet method defined on Reducer so that we can play with some ideas without having to worry about the entire application building.
— 4:06
We are going to rename the method to be less domain specific. Something that can be reasonably used for alerts, dialogs and sheets at the same time. But what can we use?
— 4:19
Well, there is actually a name for a reducer operator that has been in the library since the very beginning of the Composable Architecture, for nearly 3 years, and we think it’s the perfect name: func ifLet<ChildState: Identifiable, ChildAction>(
— 4:30
The ifLet operator has been the go-to way to safely unwrap optional state and run a reducer on it. However, it has also been grossly underpowered since its first implementation. It makes it all too easy for child features to leave effects alive after dismissing, and it made child feature dismissal a manual and error prone process.
— 4:53
We think the unified tool we are building now is simply the better version of ifLet that we could have shipped with the library from day 1 had we had all of this insight in the beginning.
— 5:05
So, this will be the one single reducer method that will work for sheets, like it currently does, but also for alerts and dialogs. But before even getting to any of that let’s see how this will change the call site of our feature.
— 5:20
Currently we have 3 different APIs for domain specific things: var body: some ReducerOf<Self> { Reduce<State, Action> { state, action in … } .alert(state: \.alert, action: /Action.alert) .confirmationDialog( state: \.confirmationDialog, action: /Action.confirmationDialog ) .sheet(state: \.addItem, action: /Action.addItem) { ItemFormFeature() } }
— 5:29
What if all of this could be unified with the ifLet method: var body: some ReducerOf<Self> { Reduce<State, Action> { state, action in … } .ifLet(state: \.alert, action: /Action.alert) {} .ifLet( state: \.confirmationDialog, action: /Action.confirmationDialog ) {} .ifLet(state: \.addItem, action: /Action.addItem) { ItemFormFeature() } }
— 5:40
We technically have to provide trailing closures for each use of ifLet , but for alerts and dialogs it can just be empty because there is no logic we need to run on the unwrapped state. They simply display for a short amount of time and the first time you interact with it it goes away. But, with that done this actually compiles! It of course won’t work correctly, but at least the API design is compiling.
— 6:02
It’d also be really nice if we could even drop the trailing closure for those situations: var body: some ReducerOf<Self> { Reduce<State, Action> { state, action in … } .ifLet(state: \.alert, action: /Action.alert) .ifLet( state: \.confirmationDialog, action: /Action.confirmationDialog ) .ifLet(state: \.addItem, action: /Action.addItem) { ItemFormFeature() } }
— 6:08
Further, maybe we can drop the external state argument label so that it becomes even more concise and clear what state we are actually “if-let” unwrapping: .ifLet(\.alert, action: /Action.alert) .ifLet( \.confirmationDialog, action: /Action.confirmationDialog ) .ifLet(\.addItem, action: /Action.addItem) { ItemFormFeature() }
— 6:19
It is possible to get this compiling, and doesn’t require too much work.
— 6:28
First we can drop the external argument label for the state key path: _ stateKeyPath: WritableKeyPath<State, ChildState?>,
— 6:31
And next we’d like to provide a default of EmptyReducer for the child reducer argument: func ifLet<ChildState: Identifiable, ChildAction>( _ stateKeyPath: WritableKeyPath<State, ChildState?>, action actionCasePath: CasePath< Action, PresentationAction<ChildAction> >, @ReducerBuilder<ChildState, ChildAction> child: () -> some Reducer<ChildState, ChildAction> = { EmptyReducer() } ) -> some ReducerOf<Self> { … }
— 6:46
But also we only want this default for child features that are alerts or confirmation dialogs, and that isn’t really possible with default arguments. We just need an overload to handle this. We can copy and paste the signature to make a quick overload where we don’t require a child reducer: func ifLet<ChildState: Identifiable, ChildAction>( _ stateKeyPath: WritableKeyPath<State, ChildState?>, action actionCasePath: CasePath< Action, PresentationAction<ChildAction> > ) -> some ReducerOf<Self> { self.ifLet(stateKeyPath, action: actionCasePath) { } }
— 7:16
We will soon see how we can special case this overload to only apply to alerts and confirmation dialogs.
— 7:24
Now everything compiles, even the theoretical syntax we sketched over in the inventory feature, and the logic works great for sheets, but it’s missing some things to make it work for alerts and dialogs, and also has more than what is necessary for alerts and dialogs.
— 7:46
To see this plain as day we can simply run our test suite.
— 7:52
We’ve got a bunch of test failures now that we are using the ifLet reducer method rather than the alert or confirmationDialog one.
— 8:09
For example, when deleting an item we are now getting a failure that an effect is still running: testDelete(): An effect returned for this action is still running. It must complete before the end of the test. …
— 8:31
That’s happening because now suddenly alerts are getting that long-living dismissal effect created for it, even though it doesn’t need any of that power.
— 8:52
Further, it’s no longer true that when a button is tapped in the alert that the state is automatically cleared out, as this test failure is showing: testDelete(): A state change does not match expectation: … InventoryFeature.State( addItem: nil, − alert: nil, + alert: AlertState( + title: #"Delete "Headphones""#, + actions: [ + [0]: ButtonState( + role: .destructive, + action: .send( + .confirmDeletion( + id: UUID( + 1DD811C0-5B96-42EE-BB5A-CB778CBFC275 + ) + ), + animation: Animation.easeInOut + ), + label: "Delete" + ) + ], + message: """ + Are you sure you want to delete this item? + """ + ), confirmationDialog: nil, items: [] ) (Expected: −, Actual: +)
— 9:02
To fix these we to add additional logic that is just for alerts and confirmation dialogs. If we detect that the state being presented is AlertState or ConfirmationDialogState , then we know we don’t need to do anything fancy with effect cancellation or dismiss, and further whenever a presentation action comes through we want to automatically clear out the child state.
— 9:25
Take for example where we check if we need to cancel effects due to the child feature being dismissed: let cancelEffect: Effect<Action> if let childBefore, childBefore.id != childAfter?.id { cancelEffect = .cancel(id: childBefore.id) } else { cancelEffect = .none }
— 9:38
Well, we only need to do this if the thing being presented is not an alert or dialog.
— 9:43
So, maybe we would hope we could do something like this to check if the child feature being presented is an alert: if !(ChildState.self is AlertState.Type), … { … }
— 9:52
Well sadly that doesn’t work because AlertState has a generic. Further we can’t do something like this: !(ChildState.self is AlertState<Any>.Type),
— 10:05
…even though it compiles. This will never evaluate to true because Swift does not support variance in generics, outside of a few special, hard coded cases such as arrays and optionals. This means we don’t get to say things like “ AlertState of A is a subtype of AlertState of B if A is a subtype of B ”.
— 10:27
The only way to accomplish this is to cook up a protocol that only AlertState conforms to, which gives us a really easy way to see if a generic type is secretly an AlertState under the hood.
— 10:40
This protocol can even be private and we will even underscore it to make sure it’s clear it is not meant for outside consumption: private protocol _AlertState {} extension AlertState: _AlertState {}
— 10:54
With that we can now do: if !(ChildState.self is _AlertState.Type), … { … }
— 11:07
But while we are here let’s go ahead and make this work for ConfirmationDialogState too. I’ll rename the protocol to be something a bit more generic, say _EphemeralState , to represent that this works for iOS system dialogs that have no logic or behavior on the inside. All they do is show a couple of buttons for the user to tap on: protocol _EphemeralState {} extension AlertState: _EphemeralState {} extension ConfirmationDialogState: _EphemeralState {}
— 11:41
And with that we can now check like this: if !(ChildState.self is _EphemeralState.Type), … { … }
— 12:03
There are a few more spots we need to check. For example, if the child state is not of the _EphemeralState type, then we don’t need to kick off the first appear effect that allows us to tie together the dismiss effect: if !(ChildState.self is _EphemeralState.Type), let childAfter, childBefore?.id != childAfter.id { onFirstAppearEffect = .run { send in … } }
— 12:21
If the child feature being presented is of type _EphemeralState and a presented action comes through, then we know we can clear out the child state: case ( .some(var childState), .presented(let childAction) ): defer { if ChildState.self is _EphemeralState.Type { state[keyPath: stateKeyPath] = nil } } …
— 13:02
And finally we will restrict the overload of ifLet that allows eliding the child reducer so that it only applies to ephemeral child state: func ifLet<ChildState: Identifiable, ChildAction>( _ stateKeyPath: WritableKeyPath<State, ChildState?>, action actionCasePath: CasePath< Action, PresentationAction<ChildAction> > ) -> some ReducerOf<Self> where ChildState: _EphemeralState { self.ifLet(stateKeyPath, action: actionCasePath) { } }
— 13:40
That’s all it takes, and with that our full test suite is back to passing. But now we can delete a ton of code, including the alert , confirmationDialog and sheet reducer methods:
— 14:19
Things are feeling really great now!
— 14:22
We have unified 3 seemingly different forms of navigation under one single API, at least as far as the feature logic and behavior is concerned. If you need to show an alert, confirmation dialog, or sheet from a feature, all you have to do is model it as an optional, invoke the reducer operator to isolate the child domain from the parent domain, and the navigation tools takes care of the rest, including effect cancellation and state management.
— 14:57
You still have a bit more work to do in the view layer by invoking the alert , confirmationDialog or sheet view modifiers, but that’s OK. Those APIs probably shouldn’t be unified because SwiftUI has chosen not to unify them. We will keep following their lead in the view layer and keep all of those things separate. Popovers Stephen
— 15:15
Now that we’ve unified all of our reducer navigation tools, lets begin expanding things to types of navigation beyond just alerts, confirmation dialogs and sheets.
— 15:24
Let’s start with popovers and covers because they act very similar to sheets, and so I think we can get a quick win.
— 15:33
Suppose we wanted a more advanced UI for duplicating an item rather than bringing up a simple confirmation sheet. What if instead we wanted to bring up a popover that shows the item’s current data so that it could be edited before confirming to duplicate it.
— 15:47
First of all, we will no longer hold onto simple ConfirmationDialogState and instead hold onto full blown ItemFormFeature.State : // var confirmationDialog: ConfirmationDialogState< // Action.Dialog // >? var duplicateItem: ItemFormFeature.State?
— 15:59
Next we’ll update the action to take a full PresentationAction of the item form feature’s action: // case confirmationDialog(PresentationAction<Dialog>) case duplicateItem( PresentationAction<ItemFormFeature.Action> )
— 16:06
Next there are compilation errors in the reducer. At the bottom of the reducer we can use the ifLet reducer operator to single out the “duplicate item” domain, and when it becomes non- nil we will run the ItemFormFeature reducer: // .ifLet( // \.confirmationDialog, // action: /Action.confirmationDialog // ) .ifLet(\.duplicateItem, action: /Action.duplicateItem) { ItemFormFeature() }
— 16:38
Next, when wanting to show the popover we will mutate the new duplicateItem state: // state.confirmationDialog = .duplicate(item: item) state.duplicateItem = ItemFormFeature.State( item: item.duplicate() )
— 17:05
Finally, we can’t handle these confirmationDialog actions anymore, so let’s just replace with matching on .duplicateItem and doing nothing: case .duplicateItem: return .none // case let .confirmationDialog( // .presented(.confirmDuplication(id: id)) // ): // guard // let item = state.items[id: id], // let index = state.items.index(id: id) // else { // return .none // } // state.items.insert(item.duplicate(), at: index) // return .none // // case .confirmationDialog(.dismiss): // return .none
— 17:34
That’s mostly everything for the logic and behavior of the feature, but there will be more to do in a bit.
— 17:40
But before we can know what else we need to add we need to implement the view layer.
— 17:45
We no longer want to invoke the confirmationDialog view modifier: // .confirmationDialog( // store: self.store.scope( // state: \.confirmationDialog, // action: InventoryFeature.Action.confirmationDialog // ) // )
— 17:51
Instead we’d love if there was a popover view modifier that looked similar to the sheet one that takes a store: .popover( store: self.store.scope( state: \.duplicateItem, action: InventoryFeature.Action.duplicateItem ) ) { store in … }
— 18:14
We can actually accomplish this quite simply by copying and pasting the sheet view modifier we added a few episodes back and making a few small changes: extension View { func popover<ChildState: Identifiable, ChildAction>( store: Store< ChildState?, PresentationAction<ChildAction> >, @ViewBuilder child: @escaping (Store<ChildState, ChildAction>) -> some View ) -> some View { WithViewStore( store, observe: { $0?.id } ) { viewStore in self.popover( item: Binding( get: { viewStore.state.map { Identified($0, id: \.self) } }, set: { newState in if viewStore.state != nil { viewStore.send(.dismiss) } } ) ) { _ in IfLetStore( store.scope( state: returningLastNonNilValue { $0 }, action: PresentationAction.presented ) ) { store in child(store) } } } } }
— 18:41
There are even a few more arguments that are specific to popovers that we could support in this method, such as anchor and arrow, but we aren’t going to worry about that right now.
— 18:50
Now we can pass a store to the popover view modifier that has isolated some optional state and presentation actions: .popover( store: self.store.scope( state: \.duplicateItem, action: InventoryFeature.Action.duplicateItem ) ) { store in NavigationStack { ItemFormView(store: store) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { viewStore.send(<#???#>) } } ToolbarItem { Button("Add") { viewStore.send(<#???#>) } } } .navigationTitle("Duplicate item") } } We just have to figure out what actions we want to send in the toolbar buttons.
— 19:16
Let’s be very explicit for right now and name these actions after canceling or confirming duplication of the item: ToolbarItem(placement: .cancellationAction) { Button("Cancel") { viewStore.send(.cancelDuplicateItemButtonTapped) } } ToolbarItem { Button("Add") { viewStore.send(.confirmDuplicateItemButtonTapped) } }
— 19:27
That means we need to add these actions to the reducer’s Action enum: enum Action: Equatable { … case cancelDuplicateItemButtonTapped case confirmDuplicateItemButtonTapped … }
— 19:37
And we need to start handling those actions in the reducer: case .cancelDuplicateItemButtonTapped: state.duplicateItem = nil return .none case .confirmDuplicateItemButtonTapped: defer { state.duplicateItem = nil } guard let item = state.duplicateItem?.item else { return .none } state.items.append(item) return .none
— 20:04
And while we don’t expect to receive confirmDuplicateItemButtonTapped when the duplicateItem state is nil , we can add a runtime warning to catch this invalid behavior should it ever occur. guard let item = state.duplicateItem?.item else { XCTFail("Can't confirm duplicate when item is nil") return .none } state.items.append(item) return .none
— 20:44
And with those few changes everything is compiling and should work. We can tap the duplicate button, make a few edits, and then tapping “Add” dismisses the popover and the new item is added to the list.
— 21:00
Technically we have lost the logic that inserts the duplicate item where the original item appears in the list. It is possible to recover this, though, and we have a couple exercises to do just that. Covers
— 21:47
And while we’re here, let’s go ahead and support fullscreen covers. We can copy-and-paste the popover view modifier we implemented a moment ago, and make a few small tweaks to it so that it handles fullScreenCover s: extension View { func fullScreenCover< ChildState: Identifiable, ChildAction >( store: Store< ChildState?, PresentationAction<ChildAction> >, @ViewBuilder child: @escaping (Store<ChildState, ChildAction>) -> some View ) -> some View { WithViewStore( store, observe: { $0?.id } ) { viewStore in self.fullScreenCover( item: Binding( get: { viewStore.state.map { Identified($0, id: \.self) } }, set: { newState in if viewStore.state != nil { viewStore.send(.dismiss) } } ) ) { _ in IfLetStore( store.scope( state: returningLastNonNilValue { $0 }, action: PresentationAction.presented ) ) { store in child(store) } } } } }
— 22:08
And with that little view helper added we can swap out our popover in the inventory feature to be a fullscreen cover: // .popover( .fullScreenCover(
— 22:22
…and it already works. It doesn’t require any changes on the reducer side of the feature because the reducer doesn’t care if you show the ItemFormFeature in a sheet, popover or cover, it’s all the same to it. Only the view cares about that.
— 23:01
But, let’s go back to the popover for now since that is what we did when we originally built the inventory app long ago.
— 23:09
So, this screen now supports multiple forms of navigation, including an alert, sheet, and popover. And we now have tools provided that can additionally handle confirmation dialogs and full screen covers.
— 23:21
And amazingly, the tools provided by the library to handle the 5 different forms of navigation have all been unified and basically look the same. You just do a little bit of upfront work to model your feature’s domain, and then tell SwiftUI how to interpret the data. It’s pretty amazing. Tests
— 23:35
But, no matter how amazing things look we want to always make sure we are not accidentally making testing more difficult. Testing is the top priority of the library, and we never want to add features that make testing difficult.
— 23:45
So, let’s quickly see how this affects tests. Things aren’t compiling now because our test for the duplication behavior is currently thinking everything is done through a confirmation dialog, but now it’s a popover. So, for example, when the duplicateButtonTapped action is sent, we expect the duplicateItem to be populated in order to drive the sheet: await store.send(.duplicateButtonTapped(id: item.id)) { $0.duplicateItem = ItemFormFeature.State( item: Item( id: UUID( uuidString: "00000000-0000-0000-0000-000000000000" )!, name: item.name, color: item.color, status: item.status ) ) }
— 24:26
And then we will confirm the duplication of the item and assert that the item was added to the end of the list: await store.send(.confirmDuplicateItemButtonTapped) { $0.duplicateItem = nil $0.items = [ item, Item( id: UUID( uuidString: "00000000-0000-0000-0000-000000000000" )!, name: "Headphones", color: item.color, status: item.status ), ] }
— 24:48
And the test now passes!
— 24:55
But now since we are now running a whole ItemFormFeature in this popover now, let’s flex those muscles a bit by editing the name of the item: await store.send( .duplicateItem( .presented(.set(\.$item.name, "Bluetooth Headphones")) ) ) { $0.duplicateItem?.item.name = "Bluetooth Headphones" }
— 25:38
And confirming that the updated name is inserted in the list await store.send(.confirmDuplicateItemButtonTapped) { $0.duplicateItem = nil $0.items = [ item, Item( … name: "Bluetooth Headphones", … ), ] }
— 25:47
We can also see what happens if we test a nonsensical flow, like confirming duplication when the duplicate item is nil : await store.send(.confirmDuplicateItemButtonTapped)
— 25:47
We get a test failure from the runtime warning we added to our reducer: testDuplicate(): Can’t confirm duplicate when item is nil
— 26:08
But let’s undo that and get back to a passing test.
— 26:21
This test passes, and it basically looks the same as the testAddItem test, which proves that we can added an item via a form presented in a sheet. So it’s pretty amazing to see how easy it is to write these tests, and how easy it to model different forms of navigation in basically the same way. Next time: links
— 26:55
OK, things are starting to look really amazing. We now have the tools to model 5 different types of navigation in our domains, alerts, dialogs, sheets, popovers, and covers, and the tools to hook up those domains to SwiftUI. And these tools help us out with lots of sharp edges and subtleties, such as cancelling inflight effects when a feature is dismissed. Brandon
— 27:14
But there is a huge, gaping hole in our tools for navigation, and it has to do with the kind of navigation that probably all of our viewers think of when we say the word “navigation.” And that’s “drill-down” navigation.
— 27:26
This is the style of navigation that occurs when you tap a button in the UI, and the screen transitions to the next screen. And you get some niceties built into iOS, such as a back button in the top-left, or even the ability to perform a swipe gesture to go back.
— 27:41
The APIs to accomplish this style of navigation have recently gone through some big changes in iOS 16. In particular, many of the navigation APIs that were available in SwiftUI on day 1 were deprecated, and all new tools were added in iOS 16.
— 27:55
We are going to first get our feet wet with drill-down navigation by building the tools on top of the older, deprecated APIs. That may seem weird, but also the vast majority of people right now are still targeting versions of iOS less than 16, and so these tools are still very much necessary. And luckily understanding the older APIs will help us with the new ones too, so it’s a win-win.
— 28:20
So, let’s see what it takes to add a drill-down navigation to our application…next time! References Composable navigation beta GitHub discussion Brandon Williams & Stephen Celis • Feb 27, 2023 In conjunction with the release of episode #224 we also released a beta preview of the navigation tools coming to the Composable Architecture. https://github.com/pointfreeco/swift-composable-architecture/discussions/1944 Downloads Sample code 0226-composable-navigation-pt5 Point-Free A hub for advanced Swift programming. Brought to you by Brandon Williams and Stephen Celis . Content Become a member The Point-Free Way Beta previews Gifts Videos Collections Free clips Blog More About Us Community Slack Mastodon Twitter BlueSky GitHub Contact Us Privacy Policy © 2026 Point-Free, Inc. All rights are reserved for the videos and transcripts on this site. All other content is licensed under CC BY-NC-SA 4.0 , and the underlying source code to run this site is licensed under the MIT License .