Empower Apps

Jared Sorge stops by to chat about Arborist, his native Mac command center for Git work trees. We get into running agents in parallel with work trees, when to drop from SwiftUI down to AppKit, and selling a Mac app outside the App Store with Sparkle, Stripe, and RevenueCat, and somehow end up talking about the iPhone Duo.
Guest
Related Links
Related Episodes
Chapters
  • (00:00) - Why Work Trees
  • (06:56) - The Idea Behind Arborist
  • (11:14) - SwiftUI vs. AppKit
  • (17:05) - Picking the Right Tool for the Job
  • (21:27) - From Problem to Product
  • (25:30) - Shipping Outside the Mac App Store
  • (29:59) - Licensing, Sandboxing & Subprocess
  • (36:30) - Agentic Coding & the Product Engineer
  • (45:17) - What's Next for Arborist
Watch
Click here to watch a video of this episode.
Transcript
Support the Show
★ Support this podcast on Patreon ★
Thanks to our supporters: Thanks to our monthly supporters
  • Steven Lipton
Welcome new supporters:
Social Media
Credits
Music from https://filmmusic.io "Blippy Trance" by Kevin MacLeod (https://incompetech.com) License: CC BY (http://creativecommons.org/licenses/by/4.0/)

Creators and Guests

Host
Leo Dion
Swift developer for Apple devices and more; Founder of BrightDigit; husband and father of 6 adorable kids
Guest
Jared Sorge
Christian, husband, dad, developer, batman afficianado. Owner of @taphouseio.Most of my posts are cross posted from my microblog. https://t.co/0vEcvLEJWi

What is Empower Apps?

An exploration of Apple business news and technology. We talk about how businesses can use new technology to empower their business and employees, from Leo Dion, founder of BrightDigit.

[00:00:00]

Why Work Trees
---

Leo Dion (host): Hey everybody, I hope you're doing well. Thank you for joining us for another episode of Empower Apps. Before we get to my interview with Jared, just wanted to give you an update on a few things. I will be speaking at SwiftCon, October 7th through 9th, in Berlin. So if you're there, let me know. I'll be doing a workshop on Swift automation, so from Xcode project to fastlane. So check that out. I just got back from iOSDevUK. It was amazing. I think it's one of my favorite conferences. I did not pick up Welsh, but I did pick up a lot of great tips on development in the Apple space. Paul Hudson, of course, had a great talk on marketing. And Daniel Steinberg had a really great talk on Foundation Models, which I continue to want to spend some time with. What else? Philly's talk on sketchboarding. I definitely want to get into that. And there's so many great talks. I highly recommend, if they do it again, [00:01:00] to go.

Leo Dion (host): I know it's a lot for us Americans, but if you're in Europe, you have no excuse. So definitely check that conference out. I also want to mention the next two episodes. After this one with Jared, I'll be speaking with Stewart Lynch. He's coming back on to talk about how to learn using AI, which I think is gonna be great. Definitely check that out. And then the one after that will be Tim Condon on Vapor 5 and its upcoming release. So check that out as well. If you have any thoughts or things you want to talk about, let me know. I'll be sure to ask them those questions. And then lastly, if you have not had a chance, download the new beta. At least, I am trying to get it out in time for Shipaton. So go ahead and go to the website and get the new TestFlight beta there. I'm hoping to submit by the end of the month. We'll see while I get ready for SwiftCon. That's about it. If you're not a subscriber to the newsletter or the [00:02:00] YouTube channel or the podcast, definitely choose one and take advantage of that, especially when these new episodes and announcements come out.

Leo Dion (host): And then, I don't know, that's about it. If you're looking for a Swift developer, reach out. All right, let's get on with the rest of our show. Welcome to another episode of Empower Apps. I'm your host, Leo Dion. Today I'm joined once again by Jared Sorge. Jared, thank you so much for coming on the show.

Jared Sorge (guest): Thanks for having me. It's great to be back. It's been a while.

Leo Dion (host): It has been a while. Our last episode was on automation. And I don't know if you've followed technology in the last few years, but a lot has changed in that space. So, I know, really. Skynet came and automated everything. Before we get into that, I'll let you go ahead and introduce yourself.

Jared Sorge (guest): Yeah, I'm Jared Sorge. I'm a developer just outside of Seattle, Washington. My day job, I work for Adobe on our iOS apps for Premiere and Firefly. And on the side, I have my own business called Taphouse [00:03:00] Software. I released an app called Arborist earlier this year, which is a developer tool, which I'm sure we'll talk about in a little bit. And actually just yesterday I released a big update for my Lego collecting app called Baseplate, which I'm very happy with how that turned out.

Leo Dion (host): Yeah, that is very cool. I guess we'll start. Before we get into Arborist and why you built it, I think we should maybe do a little explanation about work trees. Because in my world, work trees, I didn't use them until AI. When did you start using work trees? And then how are they useful? How are they especially useful for agentic development?

Jared Sorge (guest): We'll come back to the agentic part, because I think that adds a whole other wrinkle in how people are starting to use them. I first heard about them when I worked at Lyft, I think it was, like six or seven years ago. Because we would, you know, when you're working on a feature, in our case a large monorepo, we had a repo that had both [00:04:00] the driver app and the passenger app and all of our shared Swift packages. And so I'd be working on a feature and then I'd need to fix a bug, or something else would come up, or something would divert my attention from the thing I'm working on. And a lot of times the first instinct is to have another checkout. So you would go git clone and redo your repo. So you'd have two copies of that repo. So that I'm not context switching and having to push and pop stashes and all that stuff, or have so many commits that are working, so I can, you know, pause and move on to something else. And then I heard about work trees as a nice alternative for that, because you don't have to check out again, and it shares the same .git root on your disk.

Jared Sorge (guest): And so it's a really fast way to get another checkout of your sources up and running. And so I would have, you know, the feature that I'm working on, or like our main branch, and I would have the bugs that I need to work on all in separate places. And so it was easier for me to not have to worry about context switching as [00:05:00] much. And so now, fast forward, you know, six years, and we have these AI agents, and at work we're starting to use Copilot. And by default, Copilot spins up a new work tree for everything, which I'm not necessarily a fan of.

Leo Dion (host): Yeah, I didn't either. Yeah, I know. As the audience probably knows, I use a lot of Claude and I use a lot of Cursor. And I got into work trees while getting into it. It wasn't like these agents use work trees. It wasn't until later that I was like, wait, if I'm gonna do parallel stuff, this is where it's super helpful. And recloning is stupid to do. So that's when I was like, okay.

Jared Sorge (guest): Being able to have all of your branches in one view as well. So I use Tower. Tower is how my mind works with Git. And when you have separate clones, you can't cherry pick from one branch to another. You'd have to export a patch and then reapply the patch. And that sucks, [00:06:00] right? But with work trees in Tower, I can just drag from one branch to another. And because it has access to all of my branches, I don't need to worry about that patch exporting. It's a much easier workflow for me.

Leo Dion (host): Yep. So what I found really useful about work trees, and the way I've been doing it, is kind of creating a plan for several GitHub issues slash features, and then having them work on it in parallel, basically. And that's really where I was able to use work trees and take advantage of them.

Jared Sorge (guest): Yeah, I'm the same. It's a similar workflow, right? I would have my feature that I'm working on, and then I need to fix a bug or do a quick update or whatever the case may be, and I don't have to pause entirely what I'm doing in the one spot to save that state and then come back to it. It's really easy to switch between things in our modern tooling.

Leo Dion (host): So let's get into it.

The Idea Behind Arborist
---

Leo Dion (host): How did you come up with the idea for Arborist?

Jared Sorge (guest): It's something [00:07:00] I've had in my head for a while, because in various projects I've been a fan of, first, make. I learned about Makefiles when I was at Lyft, and then a few years later I learned about mise, and they're both like task runners.

Leo Dion (host): I'll put a link to my article on that, so people can know.

Jared Sorge (guest): Yeah. But at Adobe, we don't use either of them. We've started to use make a little bit. But we have these scripts that we need to run that are in arbitrary places in the repo. But when you have multiple work trees, you can't just say, okay, run this script and have it know which work tree you're in without doing a whole lot of git rev-parse kind of things to get to the absolute path of the script you're running. So if we have like tools/sync.py, for instance, I can't hard-code a shell command to run that one, because if I've got multiple checkouts, it's gonna be at any number of locations, right? So the idea for Arborist came [00:08:00] about as, one, I love Mac apps. Mac apps are great. And I wanted something where I could see all of the work trees that I had listed, and then I could combine that with the arbitrary commands that we use in every work tree. So no matter which checkout I'm in, I can click the run button and the right script is gonna run because I know where it's running.

Jared Sorge (guest): And then combine that with: I use the same tools everywhere. I use Xcode, I use Tower. Nova is my IDE of choice. I know people love VS Code. I love Mac apps, not cross-platform whatever. I love Nova. And oftentimes I would get myself confused, because I would have Nova pointed to one work tree, Tower pointed to another, and then maybe Codex pointed to another, and my workspace would get out of sync. And so I wanted a way to easily launch any of those tools in the right work tree. And so I did that. At the top of the main window, you can set up whatever tools you want. And then by clicking [00:09:00] on them, that will open them in the right work tree. So your stuff is always in the right spot. It's been really nice for me. It's an app that I really enjoyed working on. I use it all the time at Adobe on my work machine. And yeah, it's been a really nice addition as like the command center for all of my projects.

Leo Dion (host): Yeah, and I gotta say it's well designed. I'm really impressed with it. People should definitely check it out and try it. And as a big fan of work trees, and we've talked offline about Grove and all that stuff, and I built my own little command to do stuff, and it seems like every other developer in the world has. But yeah, it's really great. I guess we'll get into, like, what did you learn along the way while you were building Arborist? What are some APIs that you didn't know? And I should amend that, because you probably used a coding agent. So maybe you didn't learn and you let the [00:10:00] coding agent do it. But what did you pick up from the work that you had done with Arborist?

Jared Sorge (guest): I do lean heavily on Codex. That's my coding agent of choice. I'd never made a Mac-assed Mac app before, you know, like with a good settings window or being able to change the Dock icon. I learned a little bit about Dock tile plugins.

Leo Dion (host): Have you built a Mac app that's not Mac-assed? Like, did you build an Electron app, or what?

Jared Sorge (guest): So Mac-assed, to me, isn't necessarily the technology that you use to build it. It's how well does it integrate with the platform that it's running in. So how well does it fit in with the overall Mac ecosystem? I think some people probably think Catalyst apps couldn't be Mac-assed, but Baseplate has a Mac version as well. And I've tried to make it fit in with the Mac rather than like just being a port of the iPhone app. It actually has Mac paradigms to it. And you could make [00:11:00] a non-Mac-assed AppKit app, right? I think it's more about the attention to detail that you put into it, like how much keyboard shortcut support is there, how much customization is there, and that kind of thing.

SwiftUI vs. AppKit
---

Jared Sorge (guest): I started the app out mostly, or entirely, in SwiftUI, and I backed out some of that. I went into AppKit more, especially when it came to like sidebar and inspector views. So I learned more about not necessarily like the specific APIs that I was using, but the thought behind when to use the right frameworks to build the Mac app. So for instance, I had a feature request: my inspector view was not super adjustable. Like in Xcode, you can make that inspector view as wide as you want to. I would get crashes, because even though my app was completely SwiftUI, I would get AppKit constraint errors and runtime crashes when my views [00:12:00] would get too wide with my sidebar and my inspector view. And so I ended up backing most of that out and going with an NSSplitView wrapper that, admittedly, Codex helped me put together. But I learned more about the interop between SwiftUI and AppKit, and when to use one versus the other, and to not necessarily lean entirely on SwiftUI, because maybe it's not the best fit for that exact moment.

Leo Dion (host): Quick question, what was your target OS for Arborist?

Jared Sorge (guest): 15.

Leo Dion (host): Wow, okay. Do you think there—

Jared Sorge (guest): There were Tahoe holdouts, so I was holding back, trying to help them out to expand the audience a little bit.

Leo Dion (host): Okay. Do you think you would have gained anything as far as moving over to 26 or 27?

Jared Sorge (guest): Well, 27 just came out, so I couldn't have released it when I did if I had targeted 27. I don't think I lost anything by not going. So I still, like, my work machine was Tahoe, so I still [00:13:00] tested it thoroughly on Tahoe and made it feel at home there as much as I could. I'd built it on Golden Gate, 'cause I had Golden Gate on my personal machine from beta one. You know, I'm not usually the guy who puts beta one on everything, but I kept hearing how good it was, and it was an amazing beta season. I don't know if you ran any of them, but from like day one, Golden Gate was better than Tahoe all summer in most ways, which shocked me. I've never seen that happen before.

Leo Dion (host): Yeah. So as someone who was also trying to build a Mac-assed Mac app, where do you see the line where SwiftUI just doesn't deliver?

Jared Sorge (guest): I was happy to start with SwiftUI, and when I saw problems, to then go into AppKit. So in the crashing case, I tried a few different workarounds. I had this odd thing where I would try to hide the [00:14:00] sidebar and then show the inspector and then show the sidebar again, and it was kind of hacky, and especially that was for macOS 26 workarounds. Didn't happen on 15. It did not happen on 27. So I think my general thought is to start with SwiftUI, and then when I see issues, see if there's an AppKit way to do it that might make the solution a bit more elegant. I found that a little bit with my sidebar as well. Like, I kept fighting with selection state, especially as the app would go into the background or become inactive. And so the sidebar is an outline view instead of a SwiftUI list. SwiftUI lists seem to present some performance issues in Arborist that I was surprised by, but was happy to be able to work around.

Leo Dion (host): Okay. One thing I've seen a lot on social media is that SwiftUI has become less useful in an age of agentic coding. And why [00:15:00] not just have the agent do UIKit or AppKit too? What do you think of that?

Jared Sorge (guest): Everyone's experience is different, and I think it depends. It depends on what your goal is. It depends on where your experience lies. I looked at all the code that the agent was writing and I also had some pretty specific instructions. Like, I make fairly heavy use of some of the Point-Free libraries, not TCA, but I use their SQLiteData, I use their Sharing, I use their Navigation. And so the instructions that I give to the agents is to use these tools heavily. Don't use @AppStorage, use @Shared, for instance. Put most of the state in observable models. And abstracting the state and the logic to those models puts less burden on the views. It makes it easier for me to say, no, let's just experiment and see what this looks like with AppKit, and it doesn't have to put a bunch of [00:16:00] state in the SwiftUI view or the AppKit view. 'Cause it's already in the model. So I think a lot of it depends on how you're prompting and what you're after.

Jared Sorge (guest): And, you know, if your end goal is more the app that you're getting at the other end of it, probably how the agent builds it is less important than the output that you're getting. I remember when SwiftUI first came out, or even Swift first came out, and apps would put in their change notes, rewrote this part of the app in Swift, or rebuilt this part of the UI in SwiftUI as a release note for users. And something that I find all the time is that users don't really care about that stuff. Users care about how the app works, not how you built the app. And so are you building it as an exercise as an engineer? I love doing that stuff. But that doesn't necessarily help the product that you're trying to get out the door. And that's where I'm trying to get myself better and actually ship stuff. Because I've gone a long time without shipping stuff. And then this year has been a lot [00:17:00] of shipping stuff. And the shipping feels really good too.

Leo Dion (host): Yeah. Exactly.

Picking the Right Tool for the Job
---

Leo Dion (host): God, I forgot what I was gonna ask. I hate when that happens.

Jared Sorge (guest): Same.

Leo Dion (host): I know what I'm gonna ask. I'm just trying to phrase it correctly.

Jared Sorge (guest): Sure.

Leo Dion (host): So there's a couple of things. Agentic development makes you care less about the code in some ways, because you're not writing it. I think, maybe not the code, but how you get from A to B. I mean, I care about the code a lot. I look at every PR. But as far as, okay, you wanna use AppKit or SwiftUI? I don't really care. Just make one a preference and the other one... I guess, first of all, did you use NSViewRepresentable, or what was the top of the app? Was it SwiftUI?

Jared Sorge (guest): Representable, yeah.

Leo Dion (host): Okay, so basically the whole app is...

Jared Sorge (guest): SwiftUI. SwiftUI lifecycle.

Leo Dion (host): Yeah. [00:18:00] And then you made AppKit stuff work in SwiftUI.

Jared Sorge (guest): Okay. Yep.

Leo Dion (host): Yeah. And that's like totally legit. And yeah, like you said, users aren't gonna care what technology you used. It really comes down to support and maintenance, really. It's like, okay, if I'm gonna maintain this old Objective-C code, and it's not really supported actively, do I wanna do Objective-C, right? Or do I wanna move the Objective-C code to Swift? It's like that's kind of the question, right?

Jared Sorge (guest): Yeah. I mean, the way that you could phrase stuff like that as important to users is stabilizing the foundations for the future to bring you more cool stuff, you know.

Leo Dion (host): Yeah.

Jared Sorge (guest): It's the same technical thing, but pitched as something that is user-impacting, or something they can look forward to, or enables things that they're gonna get in later releases, right?

Leo Dion (host): Yeah. I think there's a strong [00:19:00] case for migrating from Objective-C to Swift in 2026. I still think it's not super necessary to move your stuff from UIKit to SwiftUI, or AppKit to SwiftUI, and it's even less so with macOS, because SwiftUI is still not fully in, I don't know. It is from your experience, it sounds like. At least if you're gonna go with macOS 15, it's not quite there yet.

Jared Sorge (guest): Yeah, and I think the migration in the UI frameworks comes down to: are you seeing issues that you're gonna try to mitigate by going from one to the other? Like there's known problems with large data sets in SwiftUI lists. So maybe the right choice there is to go to an NSCollectionView or UICollectionView and have that power your collection, and then put a SwiftUI content configuration as the view itself, because that's going to be lighter weight and easier to maintain than all the constraints and everything [00:20:00] you have to do with Auto Layout. But it's really about how do you pick the right tool for the job. At the end of the day, work trees, Swift, SwiftUI, UIKit, AppKit, those are all tools, and how do you best use the tools for the job? What's the best tool for the thing you're trying to accomplish?

Leo Dion (host): The other use case would be a redesign, where you can assess that at redesign, 'cause if you're gonna be doing a new UI, you don't really care what you build it in.

Jared Sorge (guest): Yeah, and that's just what I did with the Lego app, Baseplate. It had a lot of custom pieces in it before, and separate iPad and iPhone layouts, which is now a problem with something like the iPhone Duo. And so even before, I wanted to make it a bit more unified. And so I still have a fair amount of the custom pieces to give it the personality that it had. But my goal was to improve the information architecture and what things are emphasized from the [00:21:00] jump in the app. But I chose, because some Lego collections have hundreds or thousands of sets or pieces, and I didn't want that to be a performance problem, so I chose to keep that in a collection view for the data itself and the management of the cells and reuse and all that, but the views themselves are SwiftUI. And that was a really good balance for my particular app.

Leo Dion (host): Yeah, and it makes total sense.

From Problem to Product
---

Leo Dion (host): We had one question from the audience, from Caleb. He asks, "I'd be curious to hear about selling Mac apps outside the App Store. And while he's developing apps such as Arborist, does he start out with a solid idea on how he wants the app to be, or does he start out with a problem he is having, and he builds the app and he uses it to flesh out ideas and add features before shipping it?"

Jared Sorge (guest): I'll take the second part first. I started Arborist with the problem that I wanted to solve, and saw that there was [00:22:00] maybe a thread of an idea that could become a product. And so my initial thought was, I want something that can be a command runner across my Git work trees. And the thing, you know, I put it into Codex, it gave me an app, and I was like, huh, that could be something. And to actually help flesh the ideas out, back in, what, May or June, or whenever there was Fable included in the monthly Claude plans, I used Fable in Claude Design to help me build the UI and figure out how, like, what might a good layout be. And Claude Design, actually, I've been meaning to write about this. It's really good.

Leo Dion (host): It's really good.

Jared Sorge (guest): And so I'm using Cl—

Leo Dion (host): Go ahead, I'll let you finish.

Jared Sorge (guest): Yeah, you can give it prompts of things that you want to emphasize, or how you want it to fit in, or what platform you're building for, and fine-tune along the way. And then that workflow of, [00:23:00] okay, prepare that for engineering, and it builds out all these documents or whatever, and then I give that over to Codex. And Codex then helps me put the actual code together and get the app running. And it's been a really nice workflow for me as someone who is completely intimidated by Sketch and Figma, 'cause I'm not a designer, and I have very much designer insecurities. I wouldn't try to do it myself.

Leo Dion (host): Yeah, I mean, I could do it, but I don't feel like I have experience, especially when it comes to, like you said, Mac-assed Mac apps. But yeah, I've been using Claude Design and Claude Code and it's been awesome. I should do a video on it, honestly, and show how I did it. I have everything for at least my new watch app, and I have everything I want for the Bushel redesign. And I've been dialoguing between the two, Claude Design and Claude Code, and be like, hey, take a look at this [00:24:00] design. You can export the HTML, right? And then it'll be like, well, based on our specs, you need to change this and this. And then they go back and forth. And like now I'm at a good point where I know exactly what to do for the next beta for the redesign. Or not a redesign, but finishing up the design. And it's really good.

Jared Sorge (guest): Yeah. I love it. It's been a wonderful addition to my workflow. So then, as I'm using the app, I'll see like, this needs to be fixed over in this part of the project, or wouldn't it be cool if it did this? Like I wanted to add drag and drop support for repos. And drag and drop is somewhat tricky sometimes, but Codex was able to nail it. And the initial implementation was like you dragged it onto the sidebar. And I was like, no, no, no. I want to be able to drag a repo or a work tree to any part of the window, and the app will just ingest it and pop it right on the sidebar. And it did it. And it's so good.

Leo Dion (host): [00:25:00] It's so good.

Jared Sorge (guest): I love the drag and drop first-run experience, when you have a clean window and you drag a work tree in. It crawls from the work tree to the root .git and then finds all the spawned work trees, no matter where they are on your system. It's really cool to see it work. And then... Sorry, go ahead.

Leo Dion (host): No, you go ahead.

Jared Sorge (guest): I didn't know if you had something that you want— I was gonna go back to the first part.

Leo Dion (host): Yeah, yeah. Go ahead.

Jared Sorge (guest): Okay.

Shipping Outside the Mac App Store
---

Jared Sorge (guest): Then when it comes to building for outside the Mac App Store, this is the first time I've done that. Because Arborist runs arbitrary shell commands, it can't be sandboxed. So I can't send it through the app store. Which meant that I had to build out the entire, like, delivery system, licensing, and beta channels and Sparkle and all of that. And I use Cloudflare to host all of the binaries. I've paid them nothing so far, which is great. That's exactly what I'm [00:26:00] after. But I've got a workflow that's plugged into my fastlane infrastructure. So I can run my Arborist build command, and it will build a beta, send it up to Cloudflare, and update the Sparkle appcast and all that. And I'm trying to generalize it so that I could do this in the future with other apps if I want to, but, you know, that's all for future Jared.

Leo Dion (host): So yeah, I use fastlane for App Store stuff. I've never used fastlane for non-App Store stuff, but I'm happy that it works perfectly fine with Sparkle or Cloudflare or wherever it's uploading stuff.

Jared Sorge (guest): Yeah, with fastlane, it's just Ruby at the end of the day. So I have my own custom lanes. And my usual fastlane architecture is that I have one main Fastfile, but that's actually a very small file, and I have sub-Fastfiles for each of my apps. [00:27:00] And then I have environment configurations for each of my apps. I don't use Appfile or Matchfile or any of those bespoke files from fastlane, and I use environments all the time. I'm a very heavy environment user.

Leo Dion (host): And I have thoughts why, but I want to hear your opinion.

Jared Sorge (guest): So I'm in my own monorepo. I have Arborist and Baseplate and all my apps that are on the store and that are my own internal things. They're all just in one spot. So it makes it really easy.

Leo Dion (host): Sounds like a lot. Why? I mean, so does the workflow run if you make a change to any app, rather than just putting it in its own repo? Yeah, that's what I meant, sorry.

Jared Sorge (guest): So for CI, I use GitHub Actions, and then I use path filters on the actions. So if an app's content changes, the workflow runs. If it doesn't, it doesn't.

Leo Dion (host): That's interesting. So your commit history for [00:28:00] an app will have code changes for another app?

Jared Sorge (guest): Yes. The upshot is that I have one licensing package that handles all my RevenueCat stuff. And it's used by both Arborist and Baseplate and it works great.

Leo Dion (host): Well, you don't use match. Do you use match at all? So I have my app certificates in their own repo that's shared. But I assume you put the app certificates in that repo?

Jared Sorge (guest): I have one repo for my signing credentials that has the certificates and profiles.

Leo Dion (host): Okay, cool.

Jared Sorge (guest): Yeah. And when I decide to have to add a new app, the process for that is actually really straightforward to get it hooked into my CI. I create the new folder for the app. I create its environment and fill in its variables, and I create a quick little build lane for that app that plugs into my regular infrastructure, and it just works.

Leo Dion (host): Yeah. I will say, I shiver a little bit at the [00:29:00] monorepo idea, but I will say what I need to do is take a lot of my BrightDigit stuff and put it in its own repo for, like, a template, and then set up a script for each other project, and, hey, you need to keep in sync with the new script updates and stuff like that. That part I can totally see is nice about having all your projects in one repo.

Jared Sorge (guest): Yeah. So I have two monorepos. I have one for all my app code and one for my back-end website. The Taphouse website and Taphouse API service are just a couple Vapor things. Those are live in separate repos. I may eventually make them one giant monorepo, because the Baseplate API does share some code with the app, so I'll have to figure that out. But it's an evolving process.

Leo Dion (host): And they're all just tools. Honestly, not that big of a deal if you ever wanted to go the other way, right?

Jared Sorge (guest): Yeah.

Licensing, Sandboxing & Subprocess
---

Jared Sorge (guest): As [00:30:00] for the licensing part, that was a fun little journey, because I didn't want to spin up a license key service or a license validation something. I wanted it to be easy for me to implement. And what I ended up doing was I went with Stripe and RevenueCat. And Stripe, I use their managed payments service. Because I don't want to be the merchant of record on these things and handle like taxes and all that stuff. RevenueCat, I initially was going to use their web billing product, which would make me the merchant of record. And I didn't know what that term actually meant when I first started this, and I ended up learning that. So one of the APIs I learned was about taxes.

Leo Dion (host): Right?

Jared Sorge (guest): The tax code of the world is one of the APIs. But then I found out about Stripe's Managed Payments service and ended up using them, and they plug right into RevenueCat. And RevenueCat, you give an identifier, and the identifier I ended up using was the user's iCloud [00:31:00] identifier for a CloudKit container that I spun up that has no data in it. It'll never have data in it. I'm only using it for licensing. But that's worked really well. And...

Leo Dion (host): There's the thing in the app where you can switch the Apple ID, right? If you want to purchase it through a different Apple ID. Yeah.

Jared Sorge (guest): Exactly. So I use this myself. On my work machine, I don't use my personal Apple ID. I have an Apple ID tied to my work email address. Yeah, so I logged in on my work laptop with my personal email, to just the website, to get my container identifier, and then it checked with RevenueCat, said, yep, you're licensed, and it transferred automatically.

Leo Dion (host): Awesome.

Jared Sorge (guest): The idea is that I wanted to enable people to not just buy a license and it's good on one machine. I wanted it to be transferable to other of their machines.

Leo Dion (host): Yeah.

Jared Sorge (guest): And their iCloud identifier is one that naturally they carry along with them and can go anywhere. There's no restore [00:32:00] button once you're logged in. And if you got a matching identifier, you're automatically licensed. It's really cool how fast it works. And it's been a really nice process, and the RevenueCat folks are fantastic.

Leo Dion (host): Yes, I agree. So my live streaming app, Heartwitch, I ended up only doing Stripe, because there was no real StoreKit for the watch at the time. And so there's a website for it, so you can then upgrade on the website, not on the watch. And I ended up doing all the Stripe stuff myself with Vapor. There's a Stripe library in Vapor that you can use. It worked out well. This is pre-AI, by the way, but it worked out well. I know. This was back then, like before the pandemic killed humanity and AI took over. Yeah, and it was a good experience, but going forward, when I rewrite that app someday, I'd totally go with RevenueCat on [00:33:00] it, because that's a great example of something where it both has a website component and an App Store component. And then with Bushel, I probably would stick with the Mac App Store, I think, because it's Apple-targeted.

Leo Dion (host): And then, at least since I'm doing it for Shipaton, I have to put it through RevenueCat. So yeah, I'm working with RevenueCat on that and it's pretty nice. So, yeah, I wanna talk about that. And this gets into another topic I wanted to talk about: the reason you couldn't put it in the App Store is because it's not sandboxed, because you're running commands. Is that correct? Let's talk about that a little bit. What commands are you running? And it takes user input?

Jared Sorge (guest): Okay. For any Git repository, there's a command center in the middle, and there's a section at the top where your [00:34:00] user-entered commands live. And you can click edit and add a new one, and it will run whatever. It's like Anchorman, right? It'll read whatever the teleprompter says, and it'll run that command from within the selected work tree. So there's not a limit. Like, you could do rm -rf / and it would try to run that. So there's definitely some danger on the user side. You need to be able to trust what you're typing into the box. And then a feature that I added after, in 1.2, was it picks up mise tasks and make tasks as well.

Leo Dion (host): So if you drop in a repo—

Jared Sorge (guest): Yeah, I'm so happy with how that turned out. If you drop in a repo that has either of those, it'll populate it with those tasks automatically. And in the 1.3 beta, I've added support for just and npm as well. So it'll add those as supported task runners too.

Leo Dion (host): Nice. Yeah. So you recently migrated to the Subprocess [00:35:00] library, is that correct?

Jared Sorge (guest): I was using it from the start. I've built my own process runners before. I do some client work, and for them we have a CLI, and I built out a wrapper around the Process API that was usable but kind of clunky. And then I think it was a year ago that Subprocess came out, pre-1.0. And so Arborist 1.0 and 1.1 used the early pre-release versions of Subprocess, and then in 1.2 I migrated to the Subprocess 1.0 package.

Leo Dion (host): Okay. Were there any hiccups along the way?

Jared Sorge (guest): For the pre-1.0, I had to have some pieces in place around cancellation. They really beefed up how cancellation propagates through processes, and so I was able to clean up a lot of that. But overall, no, it's been very smooth. It's been a really nice little library to work with.

Leo Dion (host): Yeah, [00:36:00] I had a library that used Process. And yeah, I moved to Subprocess as soon as I could. I know one, and I assume this is the case, but one big headache was what OSes it supports, as far as what versions. Did you run into any issues with that?

Jared Sorge (guest): No. I don't know how far back it goes, but it supports 15 at least. I would have gotten build failures if it didn't. So that's kind of what I'm leaning on for that.

Agentic Coding & the Product Engineer
---

Leo Dion (host): So I wanna talk a little bit more about your development workflow, especially with Taphouse stuff like Arborist, and maybe we could talk about your development flow at Adobe, but how has that changed with the advent of agentic coding?

Jared Sorge (guest): I think when I'm trying to form my prompts, you know, I've gotten better over the last year and a half, because I think about the details that are needed for a good prompt more, not just, hey, I want [00:37:00] this thing, build it. And if you don't give it as much detail... I think about it more like I'm writing a product requirement document. And a really thorough prompt reads a lot like those that I've read over the years at Adobe or Lyft or Zulily, where we're building out a feature and it's got all these details, because the tool that you're typing into doesn't have the context that's in your head. And by putting the context that's in your head out into the prompt, or give it a Markdown document, or put it in your own wiki and point it at the wiki to go read this and then ask questions, however it is that you decide to do it, the more detail and instruction you give the agent, the better the output you're gonna get.

Jared Sorge (guest): Not just in the code itself, but how does the code run? What's the experience when you're actually running what it outputs? And so, there's a new term that we're starting to use at Adobe when we hire [00:38:00] people or bring people in. It's product engineer. And I think I'm more understanding what that term means than when I first heard it. Because there's software engineer, and that's a title that I've had before. I think I'm actually a computer scientist at Adobe. I have no CS degree, so I don't know why I'm a computer scientist, but whatever. But the idea of being a product engineer, about thinking about it from the product side and not just how are we building it, if you can blend those two together, you're gonna get better output from the tools that you're working with. And some of that might just be, the starting prompt is what needs all that context, because once you've got your long chat history, I don't need to tell it every time, hey, I'm working in this app or hey I'm working in that app. It knows from the context that it's in. But to start it and seed it with that thorough, well-thought-out idea, I think gives you a lot of benefit at the end of it.

Jared Sorge (guest): You're gonna get better output.

Leo Dion (host): I like the idea of product engineer. That [00:39:00] makes a lot of sense, because the thing is, you know, I was talking about this at iOSDevUK last week. It's at a university, right? And the thing I was thinking about is, I have a computer science degree as well, and computer science is really one abstraction layer above computer engineering, which is like one abstraction layer above electrical engineering. And it almost feels like what we're doing is the next abstraction level above computer science. You don't have to get into the nitty-gritty of the code. It's good to know how it works, but you need to know how to talk to an AI and give it the right context in order to move forward with it. That's what it kind of feels like. Does that make sense?

Jared Sorge (guest): Absolutely.

Leo Dion (host): Yeah, 'cause it's like, if you're a computer scientist, you don't care. The great thing about Swift and a lot of programming languages, unless you do C, is you don't have to care about memory management [00:40:00] or which register you're gonna put a number in. You don't care. But there are people who know.

Jared Sorge (guest): 'Cause you're using the unsafe APIs, or it's things where you're on purpose doing this thing that could be dangerous.

Leo Dion (host): Right. Yeah, and it's like, it's good to know how the code works, but to be productive, you can go a lot faster by using an AI, I guess, and it does that for you.

Jared Sorge (guest): I go back and forth on all of this all the time. It feels like AI has created a fairly consistent existential crisis for me, because there are some people who are "this was not created with AI," or fully anti-AI, and that's kind of the purity test that they might use. My 10-year-old is kind of like that. He's like, it's just AI slop. AI slop. It's like, come on, man. It's a tool. [00:41:00] How are you using the tool to accomplish the end that you're happy with? That's really what's important, anyways.

Leo Dion (host): My 10-year-old has been using my Claude Code license to build games. But unfortunately, most of his games just come from, "I just played Pokémon, I wanna make a Pokémon." And it's like, and I don't know how to teach a 10-year-old this. I think it's impossible, but it's like, and it's boring, it's like, you gotta, buddy, you gotta make a PRD document. And yeah, he doesn't want to do that. But one of the things I'm saying is, and we talked about this with Joe when he was on, part of it is being able to put into words exactly what you want. And I think that's really the skill. That's really the skill that's part of the product engineer skill, right?

Jared Sorge (guest): Yeah. My 10-year-old, a couple of years ago, wanted to do YouTube videos, and I would say, okay, that's great. What are you making? And he's like, I don't know, just turn the camera on. Like, you don't know? We need to have a plan [00:42:00] for this, because it's all about how is it planned out? What are you gonna be doing? And then a little bit later, you know, that fizzled out. But he wanted to make video games, similar thing. And he has some great ideas for storytelling, and he could be a great D&D dungeon master someday, 'cause he's made these tabletop games with different quests that you go on to have a full story and everything. But he wants to make a video game and he doesn't know what he wants and he doesn't know how to plan it out. And I'm like, well, you're not gonna— if you don't know what you're going for, you're not gonna get what you want because you don't know what you want. And it's wild how much pre-production and planning needs to go into a movie, a TV show, an app, or a feature of an app in order to make it successful.

Jared Sorge (guest): And all of that is gonna make for better work, better output, when you're using especially these coding agents.

Leo Dion (host): With any project, we always [00:43:00] think about the fun stuff we get to do, and never the paperwork and planning and all that. No one wants to think about the non-fun stuff. And I don't know, you know, going back to AI, I don't know yet, and maybe there is a skill for this, where it'll ask you the questions that need to be asked in order to know how to build that PRD, you know what I mean?

Jared Sorge (guest): If there's not a skill, you could make a skill.

Leo Dion (host): It's— we did the episode with Donny. There's that grill-me skill that's useful, and I've used it and I love it. But that's not exactly a planning skill per se. Did you— what do you use, sorry, for Arborist, what do you use for your like, work items?

Jared Sorge (guest): My work items? Like a to-do checklist, or... I've gone back and forth. I want to use something like GitHub issues and have milestones and have things easily accessible to an agent to say, read this GitHub issue and ask me questions [00:44:00] about it, and then go build the thing. What I really use is a list in Things. I have a project for an app release. I'll have each item be an idea. I've started using sections, section headers, so that I can know what are my ideas versus what are things I've heard from customers or user feedback. But it's the thing that provides me the least amount of friction, and that I just fall back to. I would like to have something that's a bit more connected and feels more professional, but whenever I try that, it doesn't stick for whatever reason.

Leo Dion (host): I like GitHub issues. That's a good compromise instead of going down the rabbit hole of Kanban boards and stuff like that. But I could see that would be an issue for you. No pun intended, because you have a monorepo, right? So you're gonna have all your issues in one repo, right?

Jared Sorge (guest): I mean, I've got the different tags for the [00:45:00] different apps, so I could filter based on the app that I'm working on. You know, monorepos are not the be-all end-all, and I understand why people don't like them. For me, it's the tool that does the job that I want it to do, so it works for me, but your mileage may vary.

Work Trees in Practice & What's Next for Arborist
---

Leo Dion (host): We had a question from the audience about work trees and where they fit. It's from Twitter. The question is, "With agents doing most of the typing, what does the work tree layout look like in practice? One per agent, one per feature? I ended up running everything serially on a single tree, and I suspect that was the wrong instinct." So I'll just say, I have one agent per work tree, per issue, per agent. So that's the way I would do it. But I also ask the agent the best way to do that. Because there may be parts that need to be done serially or where there's dependence. But yeah, that's it.

Jared Sorge (guest): How many work trees do you have at a given moment?

Leo Dion (host): Five at most.

Jared Sorge (guest): Okay.

Leo Dion (host): So basically I have [00:46:00] a GitHub issue. Excuse me. So basically I have a GitHub issue, and then I'll have a branch attached to that issue, and then I'll have an agent attached to that work tree for that branch.

Jared Sorge (guest): Okay.

Leo Dion (host): Does that make sense?

Jared Sorge (guest): Yeah. The way that I do stuff is I generally have a work tree per app. So I'll have an Arborist dev work tree, a Baseplate dev work tree. I might have a work tree that I spin up temporarily to do one thing, like make a release or work on a bug or what have you. But one of the things that I like about Arborist is that it shows me in the inspector view all the chats I've had in that work tree. Which, if you're in a one-work-tree-per-issue or one-work-tree-per-chat, that's gonna be a list of one, which is fine, right? There's no right or wrong way. It's really about how is the workflow working for you and the tools that you use. At Adobe, we have Premiere [00:47:00] and Firefly in one repo, and sharing a bunch of code with each other through Swift packages. And I'm much more loosey goosey there. Like, I've got a work tree for the Xcode 27 work I'm doing. I've got a work tree for a bug that I might be trying to fix or this feature that I'm trying to add.

Jared Sorge (guest): And it's less regimented in a way, but the work in my day job feels a lot more chaotic than the side stuff because I'm not in control over there, right? I'm one member of the team, whereas with my own stuff, I am the team. So I get to run things how I want to. And that's one of the nice things about having side projects.

Leo Dion (host): 100%, yes. What are your future plans for Arborist? What is the future you really want to get in?

Jared Sorge (guest): I've got a long list of ideas. One of the things that I want to do, that I hope is lowish-hanging fruit, is stuff like command input. So right now it's a non-interactive [00:48:00] shell. You type in the command you want it to run and it will run precisely that command. But for instance, I have a mise task on my blog repo for making a new post that could take in a post name. And right now I have to run that from a command prompt, because Arborist will just no-op on it. It'll say this exited with code one. So I'd like to support things like that. I also would like to... So you introduced me to the concept of Grove, or these tools where, when you create a new repo, it runs a certain set of tasks. I'd like to have some sort of way to say, on a new work tree creation, automatically run these commands or these tasks from Arborist. I think that would be really nice to have.

Leo Dion (host): And then, a .arborist file or something, so you can put your preferences in there?

Jared Sorge (guest): Having a .arborist file would then depend on knowing how the... Like, then I would almost make Arborist [00:49:00] my own command runner, in a way. I'm thinking through this in real time, because I hadn't considered a .arborist file. I was thinking about it more in how can I do this in the UI? And maybe that's the right way to go. I don't know. I don't know how that's actually gonna work out yet. And then the other thing is, I've got a client who has two repos that I work in all the time. One is their apps monorepo, and another is the CLI that makes the tools that support that apps repo. I would like to have a higher-level collection, so that Arborist can run commands from not just... Or accept in the sidebar not just a .git repo, but some other collection of repos to run commands in.

Leo Dion (host): Okay, that makes sense.

Jared Sorge (guest): That's all down the road, though.

Leo Dion (host): Was there anything else you wanted to mention about Arborist?

Jared Sorge (guest): [00:50:00] I think we've pretty well covered it.

Leo Dion (host): Okay, cool. How are you doing on time? I wanna respect your time.

Jared Sorge (guest): I could do a couple more minutes. I need to wrap up pretty soon though.

Leo Dion (host): Okay. Before we close out, do you have any thoughts on the iPhone event?

Jared Sorge (guest): Oh man, so I'm excited to get to see the 18 Pro. My wife's got one coming today. She's on like we kind of alternate iPhone years, so this is her year. I'm also... It's strange. I'm not as tempted by the 18 Pro as I thought I might be. I have a 17 Pro.

Leo Dion (host): I like the 17 Pro. Yeah, I am envious of you. I want the orange.

Jared Sorge (guest): Orange has been my favorite color for a long time, and so I don't know that I would have bought one, just because I don't want the orange to go away. And then they brought out the Duo. And I've been so torn on what to do for the Duo. Because it's the most intriguing iPhone that they brought out since at least the 10, but maybe...

Leo Dion (host): [00:51:00] Since the first one?

Jared Sorge (guest): Just because of that giant screen on the inside. It's so thin when it's unfolded.

Leo Dion (host): Yeah.

Jared Sorge (guest): And then software-wise, as soon as they showed that book cover expanding effect when they opened it up, oh my goodness, I want to play with that so badly.

Leo Dion (host): Yeah.

Jared Sorge (guest): And then as a developer, I really want to see how my apps will work with the screen open. I thankfully don't use a lot of custom stuff anymore, especially in Baseplate, so that should work just fine. I've been working really hard to get the adaptability pieces.

Leo Dion (host): Yeah, I think so.

Jared Sorge (guest): Yeah.

Leo Dion (host): But I know... I wanna see it. I don't have I don't have the budget for an iPhone Duo. By the way, if people are interested, join my Patreon or sponsor me on GitHub. Just saying. No, [00:52:00] that's totally unrelated. But yeah, hopefully by the time we release this episode, we have a beta for 27.1. We have the beta for 27.2 for some reason, which doesn't include the simulator for Duo, but...

Jared Sorge (guest): Yeah. Just how locked down the device was from everybody. Like people knew it was coming, but no one had access to any of it internally because it was like a totally separate release train internally.

Leo Dion (host): It's kinda wild. That's what you've heard from the grapevine? Yeah. Oh, well. We'll see.

Jared Sorge (guest): 27.2 is interesting, though.

Leo Dion (host): In what way?

Jared Sorge (guest): At least on the Xcode side, we have a whole new project format.

Leo Dion (host): Oh yeah, that's amazing.

Jared Sorge (guest): It's like it's 2019.

Leo Dion (host): It's wonderful. Glad we're there. I'm glad I was able to get all my content about Xcode project generators out like six years ago, before they clobbered it all with this new JSON format. I mean, I think there's still a big use [00:53:00] case for Tuist and XcodeGen in a lot of ways. 'Cause that JSON format, I haven't looked at it yet, but I can't imagine it's pretty.

Jared Sorge (guest): Yeah, I haven't looked at it yet either, very thoroughly. It only came out like a day or two ago.

Leo Dion (host): Jared, thank you so much for coming on the show. I'm really happy to have you back on. We'll be talking more about AI and how to learn AI in the next episode with Stewart Lynch. So be on the lookout for that. Subscribe if you aren't a subscriber on the podcast or YouTube channel. And then, if you build your website using Swift and you use Vapor, well, we're gonna have Tim Condon on to talk about Vapor 5. So be on the lookout. Yeah, we'll post a link to the blog posts just to get you interested. But yeah, so be a subscriber. Check it out so you can check these episodes out when they come. Jared, do you wanna plug where you're at and Arborist?

Jared Sorge (guest): Yeah, I'm @jsorge on Mastodon. My website is jsorge.net, and you can [00:54:00] check out my apps at taphouse.io, or Arborist is at macarborist.com.

Leo Dion (host): Awesome. Thank you so much. And I look forward to talking to everybody next time. Bye.

Jared Sorge (guest): See you later.