1
00:00:00,050 --> 00:00:02,319
I feel like our bar as an
industry needs to be higher.

2
00:00:02,319 --> 00:00:05,889
And I, I honestly think that web
development is to blame for some of that.

3
00:00:05,929 --> 00:00:09,299
That's my spicy take because it makes
it easy for us to throw up our hands.

4
00:00:09,880 --> 00:00:13,050
And say, I can't go past
that layer of abstraction.

5
00:00:13,240 --> 00:00:15,650
Whereas in the native
world, you have a problem.

6
00:00:15,670 --> 00:00:19,310
You can dig into those, the C binaries.

7
00:00:19,350 --> 00:00:22,980
it might be hard, but you have the
power to go in and change things.

8
00:00:22,980 --> 00:00:25,210
Whereas on the web, it's
like, it just works that way.

9
00:00:25,210 --> 00:00:25,709
It sucks.

10
00:00:26,070 --> 00:00:26,890
Like too bad.

11
00:00:26,970 --> 00:00:28,600
I think we need aim, aim higher.

12
00:00:28,740 --> 00:00:30,870
Welcome to the local-first FM podcast.

13
00:00:31,130 --> 00:00:34,049
I'm your host, Johannes Schickling,
and I'm a web developer, a

14
00:00:34,049 --> 00:00:37,180
startup founder, and love the
craft of software engineering.

15
00:00:37,610 --> 00:00:41,400
For the past few years, I've been on a
journey to build a modern, high quality

16
00:00:41,400 --> 00:00:43,379
music app using web technologies.

17
00:00:43,570 --> 00:00:47,510
And in doing so, I've been falling down
the rabbit hole of local-first software.

18
00:00:48,030 --> 00:00:50,970
This podcast is your invitation
to join me in that journey.

19
00:00:51,570 --> 00:00:55,820
In this episode, I'm speaking to James
Long, the creator of the local-first app.

20
00:00:56,105 --> 00:01:00,335
Actual Budget and the absurd-sql
project, which helped to pave the way

21
00:01:00,335 --> 00:01:02,275
to bring back SQLite to the browser.

22
00:01:02,765 --> 00:01:06,385
In this conversation, we go deep on
his journey, building Actual Budget,

23
00:01:06,625 --> 00:01:10,704
including implementing a syncing
solution from scratch and expanding

24
00:01:10,705 --> 00:01:14,725
from an Electron app to mobile and the
web while reusing most of the code.

25
00:01:15,385 --> 00:01:18,455
Before getting started, also a
big thank you to Expo and Crab

26
00:01:18,455 --> 00:01:20,155
Nebula for supporting this podcast.

27
00:01:20,755 --> 00:01:22,755
And now my interview with James.

28
00:01:24,059 --> 00:01:24,899
Hey, James.

29
00:01:25,009 --> 00:01:25,849
So good to have you.

30
00:01:26,589 --> 00:01:28,132
Hey,  Thanks for having me.

31
00:01:28,702 --> 00:01:32,612
So I've been a long time
fan of your prior work.

32
00:01:32,662 --> 00:01:36,582
I think this has really been like
the first time where I've seen an

33
00:01:36,872 --> 00:01:39,352
Actual local-first app pun intended.

34
00:01:39,372 --> 00:01:43,802
You've been working on the Actual
Budget app in the past, and which

35
00:01:43,802 --> 00:01:48,355
has led to quite a few, technical
innovations, particularly for the web.

36
00:01:48,695 --> 00:01:53,005
So that's what I'm looking forward to
exploring today, but I'm curious what has

37
00:01:53,005 --> 00:01:57,675
led you to exploring local-first and what
has led you to work on Actual Budget.

38
00:01:58,019 --> 00:01:58,369
Sure.

39
00:01:58,399 --> 00:02:01,709
So first of all, I'll just state
that Actual Budget is, uh, I

40
00:02:01,709 --> 00:02:03,109
don't actually work on it anymore.

41
00:02:03,119 --> 00:02:05,029
And I open sourced it about two years ago.

42
00:02:05,029 --> 00:02:08,258
And so the community has taken it
over and done a great job with it too.

43
00:02:08,569 --> 00:02:11,349
Um, but I started it around 2017.

44
00:02:11,358 --> 00:02:16,089
And back then I just really wanted
a local app, just the web back

45
00:02:16,089 --> 00:02:19,509
then was even worse than it is
now, just like development-wise.

46
00:02:19,529 --> 00:02:22,259
And I don't know, I, it
just, It didn't excite me.

47
00:02:22,349 --> 00:02:23,629
It's a personal finance manager.

48
00:02:23,659 --> 00:02:24,999
It felt like it should be local.

49
00:02:24,999 --> 00:02:30,029
It felt like I should be able to throw raw
SQL queries at it and get my data back.

50
00:02:30,189 --> 00:02:32,198
So it just felt like a very good fit.

51
00:02:32,449 --> 00:02:35,359
And using Electron back then was
amazing because I could just like

52
00:02:35,378 --> 00:02:41,179
load up SQLite and load a SQLite
database from a local disk and use

53
00:02:41,179 --> 00:02:43,069
the native SQLite C bindings, right?

54
00:02:43,069 --> 00:02:44,449
It was, it was fantastic.

55
00:02:44,509 --> 00:02:48,229
So I built a basic local app just
because I wanted it to be local

56
00:02:48,259 --> 00:02:50,309
and I just didn't want to deal
with like hosting it somewhere and

57
00:02:50,309 --> 00:02:51,699
I was like, this is just for me.

58
00:02:51,699 --> 00:02:53,799
This is just a fun thing
for me for certain apps.

59
00:02:53,809 --> 00:02:55,089
I've always liked the idea of it.

60
00:02:55,089 --> 00:02:56,698
Just like being super local.

61
00:02:56,918 --> 00:02:58,449
Uh, you, you own everything.

62
00:02:58,458 --> 00:03:02,419
It's not dependent on anything else and
that you just have raw access to the data.

63
00:03:02,858 --> 00:03:05,469
Obviously, at some point, I hit this
problem where it's like, well, shoot, if

64
00:03:05,469 --> 00:03:07,434
I drop my laptop, All my data is gone.

65
00:03:07,474 --> 00:03:07,824
Right.

66
00:03:07,894 --> 00:03:11,814
or like my wife just wants to check
one charge somewhere and she's on

67
00:03:11,814 --> 00:03:13,134
her laptop and she can't do that.

68
00:03:13,464 --> 00:03:16,873
And so, uh, eventually I was like,
well, crap, if I want to make a

69
00:03:16,873 --> 00:03:20,104
business out of this, especially I got
to solve the collaboration problem.

70
00:03:20,234 --> 00:03:22,013
And so that's what led
me down into syncing.

71
00:03:22,014 --> 00:03:24,494
And so I investigated a couple of things
and it worked out really, really well.

72
00:03:24,564 --> 00:03:27,554
And so I went down that path from
there and I just continually.

73
00:03:27,889 --> 00:03:28,709
Invested into it.

74
00:03:28,809 --> 00:03:29,349
And it was fun.

75
00:03:29,479 --> 00:03:32,979
That is super impressive, particularly
given that you've already started

76
00:03:32,979 --> 00:03:38,659
working on that in 2017, just for
reference, the official local-first essay

77
00:03:38,659 --> 00:03:41,519
by Inc&Switch, uh, came out in 2019.

78
00:03:41,648 --> 00:03:46,819
So it looks like you've been on the same
journey, uh, maybe aware, maybe unaware

79
00:03:46,819 --> 00:03:51,569
that other people have been also exploring
the ideas of local-first, and then you've

80
00:03:51,569 --> 00:03:54,459
just arrived on very similar conclusions.

81
00:03:54,809 --> 00:03:59,729
So I'm curious to learn more about
the technical challenges that

82
00:03:59,739 --> 00:04:03,829
you've been facing, really, uh,
innovating on so many fronts there

83
00:04:03,839 --> 00:04:05,959
to make this, this vision a reality.

84
00:04:06,059 --> 00:04:10,049
And I would like to better understand
also one aspect you've been mentioning

85
00:04:10,088 --> 00:04:12,654
that the, uh, App should work local.

86
00:04:12,704 --> 00:04:17,154
Are you referring to an app here as a
desktop app from something like Electron?

87
00:04:17,234 --> 00:04:21,524
Or are you also talking about a
more of like a progressive web

88
00:04:21,524 --> 00:04:23,784
app that runs also in the browser?

89
00:04:23,984 --> 00:04:27,263
So back then at the time, I was mostly
thinking just like a desktop app.

90
00:04:27,263 --> 00:04:28,833
I want to see the icon in the doc.

91
00:04:28,834 --> 00:04:31,134
I want, I wanted it to just be native.

92
00:04:31,214 --> 00:04:32,164
Native-ish.

93
00:04:32,184 --> 00:04:35,364
I know we're kind of faking that
with Electron, but I wanted just

94
00:04:35,384 --> 00:04:38,824
an app that I can click and I
open my finances and I search and

95
00:04:38,824 --> 00:04:40,404
then I do command Q and it's gone.

96
00:04:40,444 --> 00:04:42,284
So back then it was more a desktop app.

97
00:04:42,354 --> 00:04:44,473
And eventually I did port
everything to the web.

98
00:04:44,474 --> 00:04:47,723
And that was a whole nother thing because
it's really hard to compete with the web.

99
00:04:47,903 --> 00:04:51,494
I mean, just not only
distribution, uh, but just like,

100
00:04:51,494 --> 00:04:52,624
that's just where everybody is.

101
00:04:52,714 --> 00:04:56,029
So it's evolved a little bit more
to now mean probably Like when I

102
00:04:56,029 --> 00:04:59,859
say local app, it could totally be
a web app as well, or a mobile app.

103
00:04:59,939 --> 00:05:01,999
Like all of it should just be
something that can work locally.

104
00:05:01,999 --> 00:05:04,969
I think the web still needs to catch
up in a lot of ways to get there.

105
00:05:05,089 --> 00:05:07,408
But at this point, I, when
I say it, I mean like a.

106
00:05:07,819 --> 00:05:09,519
It could be a web app or a desktop app.

107
00:05:09,819 --> 00:05:14,409
So before diving a little bit more into
the nitty gritties of the technological

108
00:05:14,409 --> 00:05:17,899
choices that you've made, you've
already mentioned that you've been

109
00:05:17,909 --> 00:05:20,489
choosing SQLite for the data layer.

110
00:05:20,659 --> 00:05:23,739
So I think the, the web world is.

111
00:05:23,909 --> 00:05:28,749
Sometimes like divided across
front-end and back-end, I think using

112
00:05:28,769 --> 00:05:33,909
a SQL database is much more common
on the, on the back-end and on the

113
00:05:33,909 --> 00:05:39,029
front-end, I think you're more used
to things like Redux, MobX, et cetera.

114
00:05:39,619 --> 00:05:43,229
And given your use case of
an Electron app, this is sort

115
00:05:43,229 --> 00:05:45,569
of like a murky in between.

116
00:05:45,829 --> 00:05:49,309
So I'm very curious to hear
more about your intuition.

117
00:05:49,319 --> 00:05:53,849
What led you to wanting to use
a SQLite database that I guess

118
00:05:53,999 --> 00:05:58,578
in the realm of Electron rather
falls into the front-end realm.

119
00:05:59,028 --> 00:06:01,669
So I'm curious what led
that design decision.

120
00:06:01,919 --> 00:06:05,529
Well, like I said, I mean, for the data
layer itself, it was just so obvious

121
00:06:05,539 --> 00:06:07,449
to me that it's a small set of data.

122
00:06:07,559 --> 00:06:11,418
Some, some people worry about SQLite not
scaling well to like millions of rows.

123
00:06:11,689 --> 00:06:15,208
I think I have at this point, nine years
of my transactions in there and I, I'd

124
00:06:15,208 --> 00:06:18,789
have to count, but it's, I think it's
in the like tens of thousands, uh, aims.

125
00:06:18,798 --> 00:06:20,458
So this is like not a large set of data.

126
00:06:20,469 --> 00:06:22,589
SQLite would just seem like
such the perfect fit for it.

127
00:06:22,824 --> 00:06:27,544
But then once you have SQLite and it's
a local app and then you're writing in

128
00:06:27,544 --> 00:06:32,214
front of components, it seems silly to
have to design this API layer that's

129
00:06:32,214 --> 00:06:37,994
either like a WebSockets messaging layer
or like an HTTP like URL based API.

130
00:06:38,193 --> 00:06:43,344
Like it seems purely you're going
through the motions to have an API

131
00:06:43,554 --> 00:06:47,114
that literally just is intercepted
locally and then runs the data locally.

132
00:06:47,404 --> 00:06:48,934
So once and then like you get that.

133
00:06:49,209 --> 00:06:50,379
Data back as like JSON.

134
00:06:50,389 --> 00:06:53,839
So you, you query something with
like an HTTP or like web socket

135
00:06:53,949 --> 00:06:55,089
API, and then you get the data back.

136
00:06:55,359 --> 00:06:59,899
It's like you, why not just go ahead
and just like write SQL queries, right?

137
00:06:59,999 --> 00:07:00,989
In those components.

138
00:07:01,249 --> 00:07:04,469
It literally makes no difference
because it's intercepted locally.

139
00:07:04,639 --> 00:07:07,764
So then I started kind of,
Exposing SQLite even more and more.

140
00:07:07,894 --> 00:07:11,954
And I ended up coming up with my own
little data, data querying language,

141
00:07:11,954 --> 00:07:15,933
because when you're doing really
quick stuff, it is nice to, it's not

142
00:07:15,933 --> 00:07:19,044
an ORM at all, but it's basically
like a query builder type thing.

143
00:07:19,094 --> 00:07:21,634
It's, it's a pretty common thing
that I think a lot of people do.

144
00:07:21,634 --> 00:07:23,834
I think there's a library called Knex.

145
00:07:23,854 --> 00:07:26,819
K N E X that like helps you
build sort of like that.

146
00:07:26,819 --> 00:07:30,019
But I, I built it because I like
building stuff myself and it was, it

147
00:07:30,019 --> 00:07:32,389
was a pretty interesting thing, but it
does basically let you construct SQL

148
00:07:32,858 --> 00:07:35,599
things and then it hands that to the
backend and it knows how to execute it.

149
00:07:35,608 --> 00:07:37,798
The other thing that it knows how
to do is it knows how to do like

150
00:07:38,058 --> 00:07:40,769
live queries, which you don't get
with like raw SQL queries, unless

151
00:07:40,769 --> 00:07:43,999
you parse the query or something,
but it knows which tables to watch.

152
00:07:44,289 --> 00:07:47,879
And so as other syncing messages come in,
the data will just automatically update.

153
00:07:47,929 --> 00:07:50,599
And so, yeah, it just felt
like a really nice fit.

154
00:07:51,079 --> 00:07:52,389
Since everything is local anyway.

155
00:07:52,659 --> 00:07:54,149
That sounds super compelling.

156
00:07:54,179 --> 00:07:59,538
And I think we've arrived at a few
similar conclusions given that with my

157
00:07:59,539 --> 00:08:04,589
work on Riffle and LiveStore, I've also
built some similar aspects such as like

158
00:08:04,589 --> 00:08:06,699
the reactivity that you've pointed at.

159
00:08:06,879 --> 00:08:10,009
I'm curious, like how you actually
went about implementing that since

160
00:08:10,439 --> 00:08:15,449
for the listeners who might not
be as aware of all the SQLite

161
00:08:15,559 --> 00:08:20,569
internals, SQLite itself doesn't
really help you much with reactivity.

162
00:08:20,579 --> 00:08:24,819
There is a few hook points, but,
uh, you get to roll quite a bit

163
00:08:24,889 --> 00:08:26,608
of the stuff yourself on top.

164
00:08:26,609 --> 00:08:28,258
So I'm curious how you went about that.

165
00:08:28,378 --> 00:08:29,098
Yeah, that part.

166
00:08:29,118 --> 00:08:30,958
Honestly, it's not super innovative.

167
00:08:30,998 --> 00:08:34,518
I actually remember around 2017
when I started doing this kind

168
00:08:34,518 --> 00:08:38,738
of a stuff, I went really deep on
SQLite internals and there's a hook.

169
00:08:38,938 --> 00:08:42,598
I forget what it's called, but
it's like SQLite underscore pre

170
00:08:42,638 --> 00:08:44,618
post update or something like that.

171
00:08:44,618 --> 00:08:47,838
There's a hook that you get that gets
called whenever, whenever an update

172
00:08:47,838 --> 00:08:51,508
happens and you get like the data
before and the data after, but it turned

173
00:08:51,508 --> 00:08:52,998
out to just be a really weird hook.

174
00:08:53,178 --> 00:08:55,858
When you want to do live
reactive stuff, you, you want

175
00:08:55,858 --> 00:08:57,118
it to be somewhat fine grained.

176
00:08:57,129 --> 00:08:59,723
It doesn't have to be super fine
grained, but if, if you want A change

177
00:08:59,723 --> 00:09:03,583
happens, then the, the entire, like
all of the data on the app re renders.

178
00:09:03,853 --> 00:09:06,903
SQL is fast and it's
local, but it's still slow.

179
00:09:06,903 --> 00:09:10,153
If you're going to re render every
single time you change one little thing.

180
00:09:10,183 --> 00:09:13,723
And so you want to make sure that you'd
somewhat scoped to like the table that

181
00:09:13,723 --> 00:09:15,563
changed or something like that, at least.

182
00:09:15,783 --> 00:09:19,523
And that hook, like it
provided back like weird IDs.

183
00:09:19,553 --> 00:09:22,423
Like I could never figure out how
to actually get the data itself

184
00:09:22,423 --> 00:09:24,593
that changed, like this column.

185
00:09:24,808 --> 00:09:28,938
And this row changed from X
to Y it was like, I couldn't

186
00:09:28,948 --> 00:09:30,238
even get like that basic stuff.

187
00:09:30,238 --> 00:09:31,558
And maybe I was just doing it wrong.

188
00:09:31,798 --> 00:09:34,208
There are some hooks in
there that seem promising.

189
00:09:34,308 --> 00:09:37,878
But I think from what other people are
doing, they're using like, like the wall

190
00:09:37,878 --> 00:09:39,378
file to do like really interesting things.

191
00:09:39,378 --> 00:09:41,808
I think that seems like a
more reasonable approach.

192
00:09:41,888 --> 00:09:44,998
But overall, actually, honestly,
it's pretty, it's pretty basic.

193
00:09:45,178 --> 00:09:47,088
All updates have to go through an API.

194
00:09:47,128 --> 00:09:52,418
And so unfortunately, if you open up the
SQLite file locally, you can update it.

195
00:09:52,738 --> 00:09:54,968
But those messages will
not be synced around.

196
00:09:55,268 --> 00:09:59,138
You'll have to like reset the file
across all devices, which it's possible.

197
00:09:59,148 --> 00:10:01,758
And people have done that before, if they
really want to like mess with something.

198
00:10:02,048 --> 00:10:05,278
But, um, it's not a thing where
you can open up the SQL file and

199
00:10:05,318 --> 00:10:06,468
update things directly from there.

200
00:10:06,468 --> 00:10:11,308
You have to call the update function,
which then creates a bunch of like CRDT

201
00:10:11,318 --> 00:10:13,418
stuff, sends it out, and then actually.

202
00:10:13,643 --> 00:10:14,963
I take that back a little bit.

203
00:10:15,153 --> 00:10:19,033
You need to call the update function to
generate the CRDT, like the sync messages.

204
00:10:19,243 --> 00:10:21,913
And they go out to the server
and they get applied locally.

205
00:10:22,123 --> 00:10:24,773
It's the part of the pipeline
that applies the messages,

206
00:10:24,773 --> 00:10:26,743
which does, does the reactivity.

207
00:10:26,863 --> 00:10:28,953
That allows me to kind of
watch for changes, right?

208
00:10:28,953 --> 00:10:33,853
Because All mutations through the system,
even if they're just local mutations, go

209
00:10:33,853 --> 00:10:36,233
through this like CRDT messages system.

210
00:10:36,533 --> 00:10:41,923
And so you can see when column X
on table transactions changes, it's

211
00:10:41,923 --> 00:10:44,923
changing from this value and I have
the before value and after value.

212
00:10:45,173 --> 00:10:49,383
And so when those messages get pumped
through is when it fires off all

213
00:10:49,383 --> 00:10:52,963
of the listeners that are listening
for that small piece of data.

214
00:10:53,123 --> 00:10:54,863
And yeah, that's basically how it works.

215
00:10:55,213 --> 00:10:55,663
Got it.

216
00:10:55,673 --> 00:10:59,123
So you've captured most of that
and probably in, in JavaScript.

217
00:10:59,523 --> 00:11:03,943
And since you're all using, you're
building the library, you're building the

218
00:11:03,953 --> 00:11:06,023
app, you know how to use it correctly.

219
00:11:06,423 --> 00:11:09,953
So you've built a lightweight
query builder on top.

220
00:11:10,233 --> 00:11:12,073
How are you using that query builder then?

221
00:11:12,103 --> 00:11:14,903
Um, have you like wrapped
those in React hooks?

222
00:11:15,513 --> 00:11:20,313
Or how are you wiring up your data
into the UI directly at some point?

223
00:11:20,813 --> 00:11:25,513
Yeah, there's a useLiveQuery() hook,
and then there's a usePageQuery() hook.

224
00:11:25,573 --> 00:11:28,523
The page query one is interesting
because it allows you to like page

225
00:11:28,523 --> 00:11:31,873
in results, and it returns an object
that has like a dot next function.

226
00:11:32,073 --> 00:11:35,823
I mean, it just knows how to automatically
add the limit and, and, and offset.

227
00:11:35,973 --> 00:11:37,553
And it does some other
pretty fancy things too.

228
00:11:37,553 --> 00:11:40,588
And so they're just hooks
that Knows how to rerender the

229
00:11:40,588 --> 00:11:42,588
component when the data changes.

230
00:11:43,298 --> 00:11:43,678
Got it.

231
00:11:44,268 --> 00:11:49,378
I mean, uh, I've gone through a similar
journey of where I used like some

232
00:11:49,388 --> 00:11:52,858
state management libraries and react
front-ends in the past, and then,

233
00:11:53,218 --> 00:11:57,198
uh, actually being able to use and
embrace SQLite for all its benefits and

234
00:11:57,198 --> 00:12:01,258
the front-end is quite magical, like
little tricks that I found to be super

235
00:12:01,288 --> 00:12:03,458
compelling is that I can actually like.

236
00:12:03,513 --> 00:12:08,163
Touch the SQLite file, whether I can
like look at it and see the values being

237
00:12:08,173 --> 00:12:12,713
updated by the app, or also like go to
it and like delete it, reload the app.

238
00:12:13,033 --> 00:12:14,463
And I'm starting from scratch.

239
00:12:14,673 --> 00:12:17,493
Those little things, they're
just super compelling and make

240
00:12:17,493 --> 00:12:19,033
it super fun to work on the app.

241
00:12:19,373 --> 00:12:24,563
Is there some things like that, that
you found that just gave you like a

242
00:12:24,593 --> 00:12:28,903
really nice boost in your development,
like velocity and productivity and fun?

243
00:12:29,023 --> 00:12:29,373
Sure.

244
00:12:29,373 --> 00:12:29,693
Yeah.

245
00:12:29,693 --> 00:12:30,103
I mean, it's.

246
00:12:30,278 --> 00:12:33,998
It's totally true that it kind of
forces it to be like, if you're not

247
00:12:33,998 --> 00:12:37,828
using Postgres and you're using SQLite
instead, and they're all files, it's

248
00:12:37,838 --> 00:12:40,478
so easy to just like move things
around and create like a fresh file.

249
00:12:40,488 --> 00:12:42,858
The, the demo app literally
just copies like a demo.

250
00:12:42,858 --> 00:12:46,328
SQLite file into the
app's like database file.

251
00:12:46,328 --> 00:12:49,008
And if you're using Postgres, that
kind of stuff just gets a lot harder

252
00:12:49,048 --> 00:12:52,148
because it's so like process oriented
and like process based, you can't

253
00:12:52,148 --> 00:12:53,758
just easily do that kind of stuff.

254
00:12:53,818 --> 00:12:56,768
But like, and yeah, being, being
able to dig into things and do

255
00:12:56,828 --> 00:12:59,138
SQLite queries, it felt really fast.

256
00:12:59,408 --> 00:13:01,833
I think you, You know, if you're
developing the app, you have

257
00:13:01,833 --> 00:13:03,063
access to the Postgres database.

258
00:13:03,063 --> 00:13:04,913
So you can do that kind of stuff as well.

259
00:13:04,973 --> 00:13:08,033
But one of the things that was, does
come to mind is when I was building

260
00:13:08,033 --> 00:13:10,613
the Electron app, I had this strategy.

261
00:13:10,613 --> 00:13:12,943
And I think I wrote a post
about this, where there is a,

262
00:13:12,963 --> 00:13:14,143
there's actually two windows.

263
00:13:14,153 --> 00:13:17,193
One window is for the front-end and
one window is actually for the backend.

264
00:13:17,193 --> 00:13:21,003
So the backend does not run
in a, like just a node process

265
00:13:21,033 --> 00:13:23,263
that is, you know, Invisible.

266
00:13:23,363 --> 00:13:27,593
It actually runs in another Electron
window with like node integration set

267
00:13:27,603 --> 00:13:30,013
to true, and it can access node APIs.

268
00:13:30,213 --> 00:13:32,633
What's cool about that is
that I can open the dev tools.

269
00:13:32,713 --> 00:13:35,383
I expose a bunch of stuff
to the top level, and then I

270
00:13:35,383 --> 00:13:37,063
can like query around stuff.

271
00:13:37,063 --> 00:13:38,603
I can query the database directly.

272
00:13:38,693 --> 00:13:41,193
I can get objects back and
get it that really nice Chrome

273
00:13:41,193 --> 00:13:42,543
dev tools, like object viewer.

274
00:13:42,843 --> 00:13:44,073
Um, I can track performance.

275
00:13:44,083 --> 00:13:46,173
So I can click the performance
tab and click start.

276
00:13:46,293 --> 00:13:48,733
Start performance recording,
do a bunch of stuff on the UI.

277
00:13:49,033 --> 00:13:51,173
And then like, I can set,
um, stop the performance.

278
00:13:51,173 --> 00:13:52,503
You can do performance tracing.

279
00:13:52,733 --> 00:13:55,523
So you can actually start tracing
how long the SQL queries took.

280
00:13:55,553 --> 00:13:59,943
So being able to access the backend dev
tools or the Chrome dev tools for the

281
00:13:59,953 --> 00:14:03,313
backend, and like being able to interact,
like directly interact with, with the

282
00:14:03,313 --> 00:14:05,163
database was like super, super great.

283
00:14:05,493 --> 00:14:07,403
Yeah, that, that sounds amazing.

284
00:14:07,543 --> 00:14:12,683
So you've been using SQLite with Electron,
but you've already hinted at before

285
00:14:12,773 --> 00:14:18,173
that at some point you realize, okay, a
single Electron app is not quite enough.

286
00:14:18,353 --> 00:14:19,633
We also carry phones.

287
00:14:19,633 --> 00:14:21,163
We might carry other devices.

288
00:14:21,163 --> 00:14:24,143
Um, What if your, your
single device gets lost?

289
00:14:24,173 --> 00:14:25,893
What about all of your app state?

290
00:14:26,293 --> 00:14:31,103
So I'm curious at which point
you've then found your way to, to

291
00:14:31,103 --> 00:14:35,583
also implementing collaboration or
syncing and how you went about that.

292
00:14:35,853 --> 00:14:40,653
So I, I can't remember when it
was exactly, maybe 2018, 2019, I

293
00:14:40,653 --> 00:14:42,823
started looking into this and I.

294
00:14:43,253 --> 00:14:47,263
Went about it by just hearing things
that my friends were talking about

295
00:14:47,393 --> 00:14:48,293
that were kind of interesting.

296
00:14:48,293 --> 00:14:52,443
And so they were, they were doing like
Raft and some of those protocols in

297
00:14:52,443 --> 00:14:55,453
the backend and kind of just, they
were deep into that kind of area.

298
00:14:55,543 --> 00:14:58,393
And so I heard, honestly,
I almost gave up.

299
00:14:58,443 --> 00:15:01,173
I was like, this just, all of this
stuff seems way, way too complicated.

300
00:15:01,173 --> 00:15:02,443
I do not have time to do this.

301
00:15:02,443 --> 00:15:05,563
But then I ended up just poking
around to see if there was any

302
00:15:05,563 --> 00:15:07,053
possibilities of things I was doing.

303
00:15:07,053 --> 00:15:10,283
I had this like a really initial
implementation, which was super naive.

304
00:15:10,573 --> 00:15:13,793
I, there's a gist that I explained
it somewhere and it was like

305
00:15:13,793 --> 00:15:14,783
really, really easy to pick up.

306
00:15:15,288 --> 00:15:15,828
Pick it apart.

307
00:15:15,828 --> 00:15:18,208
Once I started implementing it and
like running a test against it, like

308
00:15:18,208 --> 00:15:22,148
there was like, I think it was sort of
operational transform based, but it just

309
00:15:22,148 --> 00:15:23,998
like was, it fell apart way too easily.

310
00:15:24,118 --> 00:15:25,788
And I was like, this is really hard stuff.

311
00:15:25,848 --> 00:15:30,048
And when I looked at CRDTs, like I
could never grasp what they were.

312
00:15:30,078 --> 00:15:33,878
Nobody really, I, back then, at least
it was just not a good, it was too much

313
00:15:33,898 --> 00:15:35,938
math, too much, very, very intimidating.

314
00:15:36,068 --> 00:15:37,048
And they're really not that hard.

315
00:15:37,048 --> 00:15:40,148
Like it does not need to be explained
that way, but something clicked at

316
00:15:40,148 --> 00:15:42,018
some point when I finally implemented.

317
00:15:42,433 --> 00:15:46,393
A basic last right winds map that
the thing that really unlocked.

318
00:15:46,393 --> 00:15:47,903
It was hybrid logical clocks.

319
00:15:47,953 --> 00:15:51,413
I can't remember where, like, when
I found that, but when I started

320
00:15:51,413 --> 00:15:54,843
looking into that and reading that
paper, uh, there's a simplicity about

321
00:15:54,853 --> 00:15:56,853
them that I found really compelling.

322
00:15:56,853 --> 00:15:59,903
And it really matched match my
own technical kind of approach

323
00:15:59,903 --> 00:16:01,223
for things, which is like.

324
00:16:01,383 --> 00:16:05,543
make things as simple as possible and,
and, and the least surprising as possible.

325
00:16:05,613 --> 00:16:06,903
And you get a lot of benefits from that.

326
00:16:06,933 --> 00:16:11,213
Now there are, there are drawbacks
to HLCs, but the benefit of them,

327
00:16:11,223 --> 00:16:14,383
especially for this use case
seemed like a really good match.

328
00:16:14,383 --> 00:16:17,043
And so I went off and, you know,
it was one of those things where

329
00:16:17,043 --> 00:16:18,843
like everything kind of came
together in a couple of weeks.

330
00:16:19,303 --> 00:16:22,113
And I started seeing some really
compelling success with that.

331
00:16:22,123 --> 00:16:25,483
And also the ability to unlock
things like undo and Yeah.

332
00:16:25,523 --> 00:16:29,753
Redo, because once you start using the
system that like mutates everything

333
00:16:29,763 --> 00:16:33,013
through these like messages, you can
suddenly start tracking those messages

334
00:16:33,323 --> 00:16:35,003
and you can invert the messages.

335
00:16:35,293 --> 00:16:39,053
So undo literally becomes take this
batch of messages that happened

336
00:16:39,063 --> 00:16:43,093
in the last action and invert them
and then apply those messages.

337
00:16:43,383 --> 00:16:47,643
And suddenly, Actual turned out to
have a really robust undo and redo

338
00:16:47,643 --> 00:16:49,113
system, which I'm super proud of.

339
00:16:49,183 --> 00:16:51,733
And like, work, like literally
everything that you do in Actual,

340
00:16:51,733 --> 00:16:53,433
you can press command Z to undo.

341
00:16:53,633 --> 00:16:57,363
If you import 2000 transactions and
it runs a bunch of rules and mutates

342
00:16:57,383 --> 00:17:03,293
them, press command Z and in like 500
milliseconds, or not 500 100 milliseconds.

343
00:17:03,633 --> 00:17:05,493
Um, everything will go
back to where it was.

344
00:17:05,723 --> 00:17:10,063
So I started seeing signs of
this architecture, which was

345
00:17:10,073 --> 00:17:12,363
like really exciting, and then
I just kind of went from there.

346
00:17:12,993 --> 00:17:18,073
So just as a side questions for, for those
of us in the audience who might not be

347
00:17:18,073 --> 00:17:22,603
familiar with hypological clocks, could
you give a quick explainer, uh, what the

348
00:17:22,603 --> 00:17:24,763
concepts are and what they're used for?

349
00:17:24,978 --> 00:17:25,298
Sure.

350
00:17:25,308 --> 00:17:26,298
So I'll try to be fast.

351
00:17:26,338 --> 00:17:30,478
I think we could probably talk
about this, uh, area of research

352
00:17:30,488 --> 00:17:31,678
probably for the rest of the time.

353
00:17:32,038 --> 00:17:35,848
Hybrid logical clocks is a way to
solve the coordination problem.

354
00:17:35,858 --> 00:17:39,478
So the problem with distributed systems,
which a local-first app is a distributed

355
00:17:39,488 --> 00:17:44,018
system, because you have copies of the
app and copies of the data across multiple

356
00:17:44,018 --> 00:17:48,038
devices, is you need to know when, if you
have something like a last write wins set.

357
00:17:48,038 --> 00:17:50,438
If two people write that
offline, then come back online

358
00:17:50,518 --> 00:17:51,888
to sync up, which one wins?

359
00:17:52,228 --> 00:17:56,318
So you, you, you have to have a clock
for every single mutation in the system.

360
00:17:56,468 --> 00:17:57,448
There's a vector clocks.

361
00:17:57,448 --> 00:18:00,758
There's a more advanced clocks that are
built on top of things like vector clocks.

362
00:18:00,798 --> 00:18:03,788
There's a lot of different approaches for,
to, to solve this kind of a problem, to

363
00:18:03,788 --> 00:18:06,478
say, which one came after the other one.

364
00:18:06,538 --> 00:18:09,948
To take a pretty simplistic approach
here, but it's still super robust.

365
00:18:09,968 --> 00:18:14,058
And the very short summary of it is
it actually, the neat thing about it

366
00:18:14,058 --> 00:18:15,858
is that it serializes into a string.

367
00:18:15,858 --> 00:18:19,903
Um, And so the comparison of if this
message came after or before is you just

368
00:18:19,903 --> 00:18:24,023
compare the string, like it says less than
or greater than the string is always the

369
00:18:24,023 --> 00:18:26,953
same length and so it's lexically ordered.

370
00:18:27,023 --> 00:18:31,113
So you can just say, if I wanted to
inspect my database and get the messages

371
00:18:31,143 --> 00:18:34,913
in order, I would say, select star for
messages and then like order by the.

372
00:18:35,143 --> 00:18:38,733
CRDT, HLC, and then everything
comes back ordered, right?

373
00:18:39,023 --> 00:18:42,363
Whereas like vector clocks and all these
other complicated ones are like complex

374
00:18:42,373 --> 00:18:46,423
object data structures that have to keep
track of the ID or the counts or whatever

375
00:18:46,623 --> 00:18:48,513
of every single device in the whole world.

376
00:18:48,523 --> 00:18:52,363
And you have to like name, name those
devices and, and, and do a lot of things.

377
00:18:52,453 --> 00:18:57,353
HLCs are a thing that Take the
current time, like the clock of the

378
00:18:57,353 --> 00:18:59,083
system, which sounds terribly scary.

379
00:18:59,093 --> 00:19:01,643
If you know anything about this
thing, like involving the clock

380
00:19:01,643 --> 00:19:04,673
of the current local computer, it
sounds awful, but they do it in a

381
00:19:04,673 --> 00:19:06,063
way that is really, really novel.

382
00:19:06,403 --> 00:19:10,053
So the first part of the string
is like the, the UTC, like that

383
00:19:10,053 --> 00:19:13,163
timestamp in the format, that's
like that Z at the end, right?

384
00:19:13,193 --> 00:19:14,583
That like, I forget what it is.

385
00:19:14,703 --> 00:19:18,263
And so it's that, it's that big
timestamp and then it's dash.

386
00:19:18,283 --> 00:19:21,293
And then there's like
a, a padding of bits.

387
00:19:21,743 --> 00:19:23,273
And then there's another like dash.

388
00:19:23,548 --> 00:19:27,288
And then there's the device ID,
I think, at, at, at the very end.

389
00:19:27,628 --> 00:19:31,458
And so the way that you get
deterministic order is that,

390
00:19:31,508 --> 00:19:35,448
that little bucket of bits in the
middle, that's the key there, right?

391
00:19:35,848 --> 00:19:38,518
So that bucket of bits
represents an integer.

392
00:19:38,588 --> 00:19:41,858
When you receive a message,
normally you use your local time.

393
00:19:41,898 --> 00:19:43,178
Well, that's not exactly true.

394
00:19:43,179 --> 00:19:45,388
You actually use your local time.

395
00:19:45,608 --> 00:19:50,558
Or the last highest time that you've
ever seen from the whole system.

396
00:19:50,598 --> 00:19:53,808
So if you are receiving a bunch
of messages, you're reading the

397
00:19:53,818 --> 00:19:54,958
times off of those messages.

398
00:19:54,958 --> 00:19:58,268
And you say, if that time is greater
than my time, so that laptop's

399
00:19:58,288 --> 00:20:01,808
clock is like one minute faster,
then I'm going to have a timestamp.

400
00:20:02,108 --> 00:20:02,878
One minute faster.

401
00:20:02,878 --> 00:20:06,608
I need to, I need to fast forward
my time to match that clock's time.

402
00:20:06,828 --> 00:20:09,308
Cause that was like that person's
clock is later than mine.

403
00:20:09,658 --> 00:20:12,868
But when you start doing that kind of
a stuff, you can't generate another

404
00:20:12,868 --> 00:20:14,598
message with the same exact time, right?

405
00:20:14,628 --> 00:20:16,928
You need to differentiate those
two messages locally somehow.

406
00:20:16,928 --> 00:20:18,088
And so you increment that.

407
00:20:18,248 --> 00:20:22,758
That number that's stored in
those bits in the middle by one.

408
00:20:23,018 --> 00:20:26,248
And so if things are generating with
the same timestamp, well, suddenly that

409
00:20:26,248 --> 00:20:28,068
timestamp is not ordering things anymore.

410
00:20:28,288 --> 00:20:31,278
Now it's the little set of bits in
the middle that are ordering things.

411
00:20:31,318 --> 00:20:34,278
And you can serialize them as like
a hex value, I think, or something

412
00:20:34,278 --> 00:20:37,168
like something that's still like a
string that can be lexically ordered.

413
00:20:37,268 --> 00:20:40,558
And so you're bumping that up, but
the minute that your, your system

414
00:20:40,808 --> 00:20:42,318
moves forward, like a second.

415
00:20:42,518 --> 00:20:46,008
Or, or some, some amount of time, you
can reset that back to zero because

416
00:20:46,018 --> 00:20:50,068
the, the, the minute that your system
meets up with the time that you had

417
00:20:50,148 --> 00:20:53,368
seen from the other system, and then
you can start using your local system

418
00:20:53,368 --> 00:20:57,288
again, then you can start using, then
you reset the counter to zero and you

419
00:20:57,288 --> 00:20:58,788
don't need to use that anymore, right?

420
00:20:58,808 --> 00:21:02,538
So it's, it's really tricky to, this
is really interesting technique where.

421
00:21:02,843 --> 00:21:06,753
You can sort of leverage your
local time and other everybody in

422
00:21:06,753 --> 00:21:09,973
the system can coordinate on this
and you get this really simplistic

423
00:21:09,973 --> 00:21:11,323
approach throughout this whole thing.

424
00:21:11,423 --> 00:21:15,833
Now, the big downside of this is that
you can't have something that's so far

425
00:21:15,833 --> 00:21:19,943
ahead in time that you, your timestamp
locally is just meaningless now and that

426
00:21:19,943 --> 00:21:21,083
you're always incrementing the bits.

427
00:21:21,113 --> 00:21:24,753
You're going to hit a ceiling where
you can't, that integer is too large.

428
00:21:24,978 --> 00:21:28,428
And you can't store it in like the,
you know, whatever the 24 bits it is.

429
00:21:28,598 --> 00:21:29,328
So then you're screwed.

430
00:21:29,368 --> 00:21:30,868
Then that thing is all busted.

431
00:21:31,128 --> 00:21:33,218
So there's this whole like
thing where there's approach.

432
00:21:33,218 --> 00:21:37,458
You can say like all of the
devices in this system need to be

433
00:21:37,458 --> 00:21:41,018
synchronized with at least like
five minutes or like an hour.

434
00:21:41,258 --> 00:21:45,398
And if you try to generate or
send a message or get a message.

435
00:21:45,638 --> 00:21:48,298
That is outside of that
timeframe, you just reject it.

436
00:21:48,398 --> 00:21:53,168
So it's a little bit of a like
brute force simplistic approach.

437
00:21:53,428 --> 00:21:56,848
Uh, but for something like Actual,
it worked out really, really well.

438
00:21:56,958 --> 00:21:59,928
It would not work in a complex
distributed system where there's

439
00:21:59,928 --> 00:22:01,678
like many, many, many clients.

440
00:22:01,908 --> 00:22:05,173
And you know, they're, they're,
it's not as robust for sure.

441
00:22:05,323 --> 00:22:08,763
That's as simple as I can explain it,
hopefully as, as a little bit longer

442
00:22:08,763 --> 00:22:11,143
than I was hoping, but that's, that's,
that's the best way that I can help.

443
00:22:11,333 --> 00:22:13,113
No, this was super insightful.

444
00:22:13,143 --> 00:22:17,753
And I think going deep into those
kinds of topics, I think that's, that's

445
00:22:17,784 --> 00:22:19,903
what the audience is interested in.

446
00:22:19,934 --> 00:22:23,113
So thanks so much for, for
like taking that little detour.

447
00:22:23,153 --> 00:22:25,323
I understand that now much better.

448
00:22:25,413 --> 00:22:31,494
So you've taken this concept and then
went from your local SQLite database

449
00:22:31,889 --> 00:22:36,299
And how did you take Hyperlogical
Clocks with your SQLite database

450
00:22:36,509 --> 00:22:38,349
and now made things collaborative?

451
00:22:38,599 --> 00:22:42,899
So once you have Hyperlogical Clocks,
it becomes pretty easy to sync things

452
00:22:42,899 --> 00:22:47,399
around because I essentially, honestly,
I don't use a super complicated.

453
00:22:47,408 --> 00:22:48,899
I know, is it Martin Kleppmann?

454
00:22:48,899 --> 00:22:52,149
I think works on like a lot
of really, really robust data

455
00:22:52,149 --> 00:22:53,679
structures that work really well.

456
00:22:53,729 --> 00:22:56,269
When you're in this distributed
world, you have to make sure that

457
00:22:56,279 --> 00:22:57,989
things don't end up in a bad state.

458
00:22:57,998 --> 00:22:59,199
So if you want like a.

459
00:22:59,489 --> 00:23:00,319
Tree data structure.

460
00:23:00,319 --> 00:23:03,869
And you want to say, Hey, this, like,
there should never be any orphaned

461
00:23:03,879 --> 00:23:05,639
nodes in this tree tree data structure.

462
00:23:05,639 --> 00:23:08,489
Well, it's really easy to get
that state in a naive thing.

463
00:23:08,489 --> 00:23:12,618
If you say like a last right, when set
that it's like parent child, and then

464
00:23:12,619 --> 00:23:17,219
like one person updates a node and the
other person had deleted that node.

465
00:23:17,298 --> 00:23:19,019
And so the node gets like.

466
00:23:19,279 --> 00:23:22,669
removed from the parents, but
then the message comes in later

467
00:23:22,809 --> 00:23:24,529
that this person edited that node.

468
00:23:24,649 --> 00:23:27,619
So the node gets created again,
but it's an orphaned node.

469
00:23:27,829 --> 00:23:29,218
Like that's a weird place to be.

470
00:23:29,269 --> 00:23:33,018
So like there's work in the CRDT
world, which is fantastic and makes

471
00:23:33,059 --> 00:23:34,649
those kinds of things super robust.

472
00:23:35,179 --> 00:23:36,539
I did not use any of that kind of stuff.

473
00:23:36,589 --> 00:23:39,459
So like those kinds of things, I just kind
of accepted and was like, well, I just

474
00:23:39,459 --> 00:23:41,549
code defensively against like bad data.

475
00:23:41,699 --> 00:23:45,589
And generally speaking in my app,
I didn't have a ton of places where

476
00:23:45,599 --> 00:23:47,289
things needed to be super robust.

477
00:23:47,289 --> 00:23:50,969
It was pretty easy to defend against
bad data, but essentially  like I said,

478
00:23:51,008 --> 00:23:53,463
there's like an update, a crate and a.

479
00:23:53,763 --> 00:23:58,023
Delete function, they all actually
intercept to a single like lower level

480
00:23:58,033 --> 00:24:01,963
update function because instance and
updates are exactly the same thing that

481
00:24:02,093 --> 00:24:04,533
take a, take an object of things to set.

482
00:24:04,663 --> 00:24:06,673
And that object has to contain an ID.

483
00:24:06,923 --> 00:24:09,193
It turns it into CRDT messages.

484
00:24:09,203 --> 00:24:10,853
So every single field set.

485
00:24:10,863 --> 00:24:13,953
So if you said, set the transaction
amount in the transaction date to

486
00:24:13,983 --> 00:24:18,223
X and Y, those would become two
different messages with the same.

487
00:24:18,808 --> 00:24:20,358
object ID, right?

488
00:24:20,368 --> 00:24:22,288
Like they're, they're
targeting the same object.

489
00:24:22,498 --> 00:24:26,498
One is set the field amount to X and
one is set, set the field date to Y.

490
00:24:26,738 --> 00:24:30,658
There's two messages get created and then
this gets sent off to a syncing server.

491
00:24:30,948 --> 00:24:32,588
The syncing server is
really, really stupid.

492
00:24:32,728 --> 00:24:36,328
It just holds the messages and
when it's, when a client asks for

493
00:24:36,328 --> 00:24:38,258
messages, it gives us messages back.

494
00:24:38,498 --> 00:24:41,658
And so it can ask for messages
since a certain point in time,

495
00:24:41,788 --> 00:24:46,658
which again, in our, uh, HLC clock
world, that is our point in time.

496
00:24:46,898 --> 00:24:50,348
So it's literally just a Postgres query
that says select star for messages

497
00:24:50,548 --> 00:24:55,248
where HLC is greater than X and X
is the HLC that you gave it, right?

498
00:24:55,328 --> 00:24:58,118
So, so nice that you can just do
that, like greater than comparison.

499
00:24:58,248 --> 00:25:01,428
And then it, so it gets the messages back
and then it says, Hey, I'm, I'm, I'm,

500
00:25:01,438 --> 00:25:03,518
I'm all synced up and that's essentially.

501
00:25:03,843 --> 00:25:04,353
How it does.

502
00:25:04,353 --> 00:25:06,853
There is like a Merkle tree here in there.

503
00:25:06,853 --> 00:25:08,233
And I'm happy to talk about that.

504
00:25:08,363 --> 00:25:08,623
Yeah.

505
00:25:08,623 --> 00:25:09,513
Let's get into it.

506
00:25:09,603 --> 00:25:11,613
Uh, how Merkle tree is fitting in here.

507
00:25:11,913 --> 00:25:12,283
Sure.

508
00:25:12,313 --> 00:25:15,073
So yeah, the, so there's
a big problem here, right?

509
00:25:15,123 --> 00:25:17,993
Like, how do you know that
you're actually in sync?

510
00:25:18,113 --> 00:25:19,833
How do I know what messages to ask for?

511
00:25:19,923 --> 00:25:24,433
So when I, when I open up Actual and I
hit sync, do I ask for all the messages

512
00:25:24,433 --> 00:25:25,883
in the entire history of the world?

513
00:25:26,023 --> 00:25:28,243
Or like I could ask for.

514
00:25:28,983 --> 00:25:32,573
The messages since I've last opened
the app, like that seems nice, or like

515
00:25:32,613 --> 00:25:34,643
the last time I have synced, right?

516
00:25:34,723 --> 00:25:36,963
And then I would get those
messages and apply them.

517
00:25:37,083 --> 00:25:38,943
But there's like several questions there.

518
00:25:39,053 --> 00:25:43,143
One, what if another client had created
a message and just hadn't synced it

519
00:25:43,143 --> 00:25:47,313
yet, and created the message before
you last synced, and then you closed

520
00:25:47,323 --> 00:25:48,543
the app and like didn't open it.

521
00:25:48,723 --> 00:25:52,663
And then the other app, Came up, came
up and synced and then it like sent that

522
00:25:52,663 --> 00:25:56,453
message into the system and then you
open, you know, the, the, the original

523
00:25:56,453 --> 00:25:59,703
app, you would miss that message because
I've, I've seen since, since I've

524
00:25:59,703 --> 00:26:03,073
last synced, there couldn't possibly
any, couldn't possibly be any more

525
00:26:03,073 --> 00:26:05,063
messages, but that's not true at all.

526
00:26:05,073 --> 00:26:08,983
In this distributed world, you have
to assume everything bad, everything

527
00:26:08,993 --> 00:26:10,513
out of order is going to happen.

528
00:26:10,853 --> 00:26:12,313
You cannot code like that.

529
00:26:12,423 --> 00:26:13,753
The other problem.

530
00:26:14,163 --> 00:26:18,003
Is when I have synced up, like, let's say
I asked for all, all of the messages in

531
00:26:18,003 --> 00:26:24,593
the world and I apply them locally, how
do I know that I'm just actually valid?

532
00:26:24,723 --> 00:26:26,413
Like there could be a bug in my system.

533
00:26:26,533 --> 00:26:29,293
And so like, how do I know that
I have, like, when you, when you

534
00:26:29,293 --> 00:26:32,163
get all of those messages back,
how do I know which ones to apply?

535
00:26:32,403 --> 00:26:35,443
So locally, there's like kind of a
ledger of things that you've applied.

536
00:26:35,473 --> 00:26:38,023
And so you, you, you go through
every single message and you

537
00:26:38,023 --> 00:26:39,353
say, have I applied this message?

538
00:26:39,393 --> 00:26:40,223
I've already applied it.

539
00:26:40,543 --> 00:26:41,183
Don't apply it.

540
00:26:41,513 --> 00:26:46,693
If I have not applied it and it is a valid
message, like if it's a last right, when

541
00:26:46,713 --> 00:26:52,413
set, it'll say, if this has been written
by a message, like later than this one,

542
00:26:52,613 --> 00:26:54,383
then I can just describe this as well.

543
00:26:54,643 --> 00:26:57,543
So there's logic about how
to apply the messages there.

544
00:26:57,943 --> 00:27:01,303
That could be buggy or just like
a network request is so weird.

545
00:27:01,303 --> 00:27:03,463
And there's a state that I
just, I didn't anticipate.

546
00:27:03,553 --> 00:27:05,923
You need look kind of like a, a, another.

547
00:27:06,248 --> 00:27:08,688
piece of data structure, and
that's, that's, that's the Merkle

548
00:27:08,688 --> 00:27:10,958
tree to sort of audit things.

549
00:27:11,168 --> 00:27:13,718
It's, it's, it's a hash of
everything in a system, right?

550
00:27:13,718 --> 00:27:17,998
So if you hash all of the objects, all
of the CRDT messages, you can think of

551
00:27:18,008 --> 00:27:21,318
all the CRDT messages as like leaf nodes.

552
00:27:21,498 --> 00:27:26,018
And then you have like them grouped into
buckets and every node in the tree, all

553
00:27:26,018 --> 00:27:27,828
the way up to the root is a new hash.

554
00:27:28,048 --> 00:27:31,668
And so the hash at the root is a hash
of everything in the entire system.

555
00:27:31,698 --> 00:27:35,758
So you can quickly compare if like,
have I seen all of these CRDT messages?

556
00:27:35,788 --> 00:27:37,658
I just compared the two root hashes.

557
00:27:37,768 --> 00:27:40,418
And then if I, if those hashes are
exactly the same, then I know I've

558
00:27:40,598 --> 00:27:42,348
like, these have been processed.

559
00:27:42,668 --> 00:27:48,528
Where it gets weird is that like,
I use, A base three system, I think

560
00:27:48,608 --> 00:27:52,078
of like, basically every node in
the tree is a zero one or two.

561
00:27:52,588 --> 00:27:57,508
And basically you can construct
a timestamp of basically

562
00:27:57,528 --> 00:27:59,658
how I bucket the messages.

563
00:27:59,718 --> 00:28:01,348
Like I talked about buckets, right?

564
00:28:01,358 --> 00:28:03,478
You have to have like,
what are those buckets?

565
00:28:03,478 --> 00:28:05,638
The buckets for me were
basically time windows.

566
00:28:06,208 --> 00:28:09,938
And so I had time windows of
like, down to like, 10 minutes.

567
00:28:10,638 --> 00:28:15,808
So every single like CRDT messages
all applied within a 10 minute window

568
00:28:15,818 --> 00:28:17,758
would be hashed together into one hash.

569
00:28:17,758 --> 00:28:19,598
And that would be one
leaf node in the tree.

570
00:28:20,128 --> 00:28:22,108
And those would be all
the separate leaf buckets.

571
00:28:22,778 --> 00:28:26,528
So the bucket above it represented
a new, a larger window.

572
00:28:26,613 --> 00:28:27,483
Of time, right?

573
00:28:27,523 --> 00:28:31,323
And it was a, because it was base three,
you could reconstruct this timestamp.

574
00:28:31,513 --> 00:28:34,903
The thing that I wanted to guard
against was these are a lot of messages.

575
00:28:34,903 --> 00:28:36,773
There could be tens of
thousands of messages.

576
00:28:37,033 --> 00:28:41,513
You have to come up with a, a system
that is detailed enough so that you're

577
00:28:41,513 --> 00:28:46,623
not requesting too much, too, too, like
too many messages, but it needs to be.

578
00:28:46,843 --> 00:28:49,913
Coarse enough so that the tree
just doesn't get massive because

579
00:28:49,913 --> 00:28:52,843
this tree is sent across the
network every single time you sync.

580
00:28:52,863 --> 00:28:56,203
It's stored in your database and
updated, like updated as a blob

581
00:28:56,283 --> 00:28:57,593
every single time that you sync.

582
00:28:57,853 --> 00:28:59,593
So it cannot be a huge tree.

583
00:28:59,663 --> 00:29:03,553
And so this base three system encoded
these windows in a way that allowed

584
00:29:03,553 --> 00:29:07,493
me to calculate things down to the
five minutes windows and not be huge.

585
00:29:07,493 --> 00:29:09,963
Like I think it could ever
only get like 10 leaves deep.

586
00:29:10,243 --> 00:29:12,053
And so that solves the depth problem.

587
00:29:12,063 --> 00:29:13,073
It doesn't solve the.

588
00:29:13,238 --> 00:29:14,648
breadth problem of the tree.

589
00:29:14,648 --> 00:29:17,108
So the Merkle tree could still
get really wide if you're using

590
00:29:17,108 --> 00:29:18,138
the app over years and years.

591
00:29:18,188 --> 00:29:21,418
So if you think of this huge tree, we're
ever seeing in windows of time, there's

592
00:29:21,418 --> 00:29:25,098
only ever two paths down 10 nodes deep.

593
00:29:25,478 --> 00:29:28,098
And before that, it just
prunes them all away.

594
00:29:28,098 --> 00:29:28,968
It just deletes them.

595
00:29:29,218 --> 00:29:33,448
What that means is that if I miss a
message, like I think it was about a

596
00:29:33,448 --> 00:29:38,078
nine month mark, that if a message comes
through past nine months previous to that,

597
00:29:38,718 --> 00:29:41,018
I don't know which things to download.

598
00:29:41,108 --> 00:29:44,888
I check that I've received all of the
messages by comparing the hashes, right?

599
00:29:45,008 --> 00:29:47,898
If the hash of the root is wrong,
then I go down and I compare.

600
00:29:48,118 --> 00:29:50,538
That's how I get the window
to retrieve messages by.

601
00:29:50,848 --> 00:29:54,428
So I compare down and I get, I get
the, the node that is different.

602
00:29:54,678 --> 00:29:57,658
And then from that different node,
I can construct a window and says

603
00:29:57,978 --> 00:30:01,008
something came through like, like
a message from like, like three

604
00:30:01,008 --> 00:30:02,698
weeks ago, there's something here.

605
00:30:02,863 --> 00:30:03,493
That's a difference.

606
00:30:03,543 --> 00:30:06,863
I need to, I need to retrieve all of
the messages from three weeks ago, and

607
00:30:06,863 --> 00:30:10,343
I need to reapply them all because like
something changed their Merkle tree hat.

608
00:30:10,343 --> 00:30:14,463
Like the, the server sends the
Merkle tree, it's Merkle tree to me.

609
00:30:14,463 --> 00:30:18,083
And so I can compare the service Merkle
tree to mine and say, Hey, the service

610
00:30:18,103 --> 00:30:20,203
Merkle tree like changed around this time.

611
00:30:20,203 --> 00:30:21,443
So I need to get those messages.

612
00:30:22,063 --> 00:30:25,153
There's a certain point in time, which if
a message, if the Merkle tree like gets.

613
00:30:25,498 --> 00:30:29,588
Change like a long time ago, like
a really old message comes to the

614
00:30:29,588 --> 00:30:31,158
system and just screws everything up.

615
00:30:31,538 --> 00:30:34,278
I actually won't know that because
I've already pruned that tree away.

616
00:30:34,538 --> 00:30:37,818
So you'll either have to redownload
all of the messages, which is

617
00:30:37,818 --> 00:30:40,548
probably not practical, or you just
reject that client and you like

618
00:30:40,548 --> 00:30:41,748
detach it from the whole system.

619
00:30:42,248 --> 00:30:44,678
So there's edge cases there that
you have to sort of think about,

620
00:30:44,908 --> 00:30:47,758
but the really nice thing that the
Merkle tree gives me is that window.

621
00:30:48,123 --> 00:30:52,663
And a validation that I have actually
processed, like if the roots hashes

622
00:30:52,683 --> 00:30:55,813
are the same on the server and the
client, then I know I'm up to date.

623
00:30:55,943 --> 00:31:00,643
So thank you so much for giving
this, uh, quite in depth overview

624
00:31:00,683 --> 00:31:05,083
of how Merkle tree works and more
specifically how you applied them

625
00:31:05,103 --> 00:31:07,613
on the, the Actual syncing scenario.

626
00:31:07,673 --> 00:31:09,143
That was very insightful.

627
00:31:09,153 --> 00:31:13,728
I've now seen Merkle trees applied on
a, on a few different technological

628
00:31:13,798 --> 00:31:19,118
scenarios and the way how you've now
used them with the time buckets, I

629
00:31:19,128 --> 00:31:23,128
think is super elegant where it can,
in the happy path, save a lot of

630
00:31:23,128 --> 00:31:25,648
work, uh, for, for the syncing engine.

631
00:31:25,648 --> 00:31:29,068
And once you've understood the entire
system, actually quite simple to think

632
00:31:29,068 --> 00:31:33,878
about, and I'm sure also when you, maybe
something went wrong at some point and

633
00:31:33,878 --> 00:31:38,378
you at least still had sort of like an
intuitive mental model, how you can debug

634
00:31:38,428 --> 00:31:39,978
this, et cetera, and how you can test it.

635
00:31:40,138 --> 00:31:41,728
So I'm sure you didn't.

636
00:31:41,963 --> 00:31:48,223
just build a syncing system just for one
or two Electron apps, but probably for

637
00:31:48,253 --> 00:31:51,193
a vision to go beyond the Electron app.

638
00:31:51,533 --> 00:31:54,133
So how did Actual evolve

639
00:31:54,163 --> 00:31:54,773
from here?

640
00:31:54,833 --> 00:31:55,173
Yeah.

641
00:31:55,173 --> 00:32:00,323
So once I validated that I was going
to build a syncing engine and that

642
00:32:00,353 --> 00:32:02,243
it worked, I built a mobile app.

643
00:32:02,363 --> 00:32:06,373
Because obviously that was part of the
reason for wanting to do syncing is to

644
00:32:06,373 --> 00:32:07,723
be able to support like a mobile app.

645
00:32:07,783 --> 00:32:11,763
And I sort of naively was like, I'm
not going to build a shoddy mobile app.

646
00:32:11,763 --> 00:32:13,883
I'm going to, I'm going to do
this and I'm going to do it right.

647
00:32:13,893 --> 00:32:15,873
And so I, I, I did use React Native.

648
00:32:15,873 --> 00:32:20,068
I didn't, I wasn't so naive that I was
going to build a separate Android and iOS

649
00:32:20,068 --> 00:32:24,118
at myself, but I did attempt to use React
Native and I built it with React Native.

650
00:32:24,128 --> 00:32:27,998
And then I leveraged a project that
was at the time was having a decent

651
00:32:27,998 --> 00:32:31,588
amount of maintenance and investment
from, I forget which company started

652
00:32:31,588 --> 00:32:33,218
it, but it's a project called Node.

653
00:32:33,228 --> 00:32:34,638
js mobile.

654
00:32:35,028 --> 00:32:38,998
And so they basically took Node and
they compiled it for iOS and Android.

655
00:32:38,998 --> 00:32:42,598
And they basically built this thing
that would load your JavaScript bundle.

656
00:32:42,648 --> 00:32:45,308
And you could write JavaScript
based off of Node APIs.

657
00:32:45,488 --> 00:32:46,018
And it was great.

658
00:32:46,113 --> 00:32:50,093
Cause like HTTP, like all of the
standard node APIs that you would

659
00:32:50,093 --> 00:32:53,563
expect worked there and it fired off
as like a separate process, like it

660
00:32:53,573 --> 00:32:55,623
did it in, in the, in the proper way.

661
00:32:55,703 --> 00:32:57,913
And so  I got a prototype working
and it worked really well.

662
00:32:57,913 --> 00:32:59,893
And so I was able to use
the exact same backend.

663
00:33:00,133 --> 00:33:02,303
And then I built a new
front-end in react native.

664
00:33:02,313 --> 00:33:03,483
That was like mobile specific.

665
00:33:03,493 --> 00:33:06,083
Cause I was like, I really want to
think this through in like a mobile way.

666
00:33:06,083 --> 00:33:10,103
And I'm not going to like shrink
down this transactions table and from

667
00:33:10,103 --> 00:33:13,393
desktop and like, Try to shove in
this complicated table on the mobile.

668
00:33:13,423 --> 00:33:15,313
I'm going to have a proper
mobile app and do it right.

669
00:33:15,363 --> 00:33:19,723
But yeah, having to, to design and
develop two separate UIs for two different

670
00:33:19,723 --> 00:33:21,443
use cases was just, was just awful.

671
00:33:21,443 --> 00:33:24,413
I mean, just time time wise, it was
just a bad decision, but I did, I

672
00:33:24,443 --> 00:33:27,823
did get the mobile app working and I
released it like, and it was a used

673
00:33:27,853 --> 00:33:31,703
thing that most people use, I had
like, I don't know, 900 installs on,

674
00:33:31,713 --> 00:33:35,313
on iOS and, uh, some, a couple of
hundred on Android, I think as well,

675
00:33:35,383 --> 00:33:37,573
people use it and it, it had the same.

676
00:33:37,573 --> 00:33:41,018
So like, React Native
powered the UI part of it.

677
00:33:41,128 --> 00:33:44,638
Um, and then in the backend, the syncing
engine and all the exact same code ran.

678
00:33:44,638 --> 00:33:47,598
And so the syncing, the syncing stuff
would, would run and it would send

679
00:33:47,598 --> 00:33:50,168
messages off to the server and then
it would be synced back to the desktop

680
00:33:50,168 --> 00:33:51,208
and, and it worked pretty well.

681
00:33:51,418 --> 00:33:53,288
That must've been a magical moment.

682
00:33:53,338 --> 00:33:56,998
Once you had your data from the desktop
app show up in the mobile app, you make

683
00:33:56,998 --> 00:34:00,818
changes on the mobile app and things
are appearing on your desktop app.

684
00:34:00,968 --> 00:34:02,138
All that work paying off.

685
00:34:02,413 --> 00:34:08,093
Yeah, yeah, I think I have a, I might have
a tweet, I think that showed them side by

686
00:34:08,093 --> 00:34:11,023
side and it was like, look, you can change
one thing here and it shows up over there.

687
00:34:11,023 --> 00:34:13,763
And it's always a cool,
like a cool demo able thing.

688
00:34:13,913 --> 00:34:20,363
Yeah, I think if as like local-first apps
are luckily becoming more and more normal,

689
00:34:20,383 --> 00:34:22,263
I think we will take it for granted.

690
00:34:22,623 --> 00:34:27,223
But I feel quite nostalgic about like
the, the days where this is not normal.

691
00:34:27,473 --> 00:34:29,393
And like the, the magic is really strong.

692
00:34:29,393 --> 00:34:33,493
And in my opinion, it's still
strong when you see like the, the

693
00:34:33,493 --> 00:34:37,243
real time collaboration of apps, et
cetera, and those things just work.

694
00:34:37,763 --> 00:34:41,438
So you've mentioned that you've
been using what was called Node.

695
00:34:41,438 --> 00:34:42,383
js mobile.

696
00:34:42,443 --> 00:34:45,293
And so that allowed you to
bring most of your code there.

697
00:34:45,483 --> 00:34:50,323
Did even like the, the SQLite bindings
and the SQLite reactivity system, did all

698
00:34:50,323 --> 00:34:52,693
of that just work also on React Native?

699
00:34:52,883 --> 00:34:55,673
It didn't just work, but it
wasn't too hard to get it to work.

700
00:34:55,673 --> 00:34:57,323
It was sort of just like Electron.

701
00:34:57,323 --> 00:34:59,343
I had to figure out how to load in.

702
00:34:59,523 --> 00:35:02,773
I think there was already an
Electron like C library that

703
00:35:02,773 --> 00:35:04,783
allowed me to easily access SQLite.

704
00:35:05,063 --> 00:35:08,463
But for React Native, I actually
built, I think I built my own SQLite.

705
00:35:08,648 --> 00:35:11,998
Bindings for react native.

706
00:35:12,108 --> 00:35:14,408
I'm actually trying to remember,
maybe I, maybe I did it.

707
00:35:14,418 --> 00:35:17,378
Maybe it did just work because
the, no, I think it was weird.

708
00:35:17,538 --> 00:35:18,128
I think the Node.

709
00:35:18,128 --> 00:35:22,148
js mobile allowed you to
compile C dependencies, but

710
00:35:22,148 --> 00:35:23,508
like it didn't fully work.

711
00:35:23,518 --> 00:35:25,628
And so I had to kind
of get things working.

712
00:35:25,628 --> 00:35:28,848
So it didn't just work, but it wasn't
like super hard to get working.

713
00:35:28,928 --> 00:35:31,318
You basically had to compile SQLite for.

714
00:35:31,383 --> 00:35:32,373
IOS and Android, right?

715
00:35:32,373 --> 00:35:35,903
And so you have to like hook in their
build process and get that loaded.

716
00:35:35,903 --> 00:35:36,303
And Node.

717
00:35:36,303 --> 00:35:41,413
js's mobile support for like C
dependencies from Node wasn't straight

718
00:35:41,433 --> 00:35:44,623
out of the box, but honestly it
worked, it worked well enough to where

719
00:35:44,623 --> 00:35:45,943
it wasn't that hard to get working.

720
00:35:45,943 --> 00:35:48,153
So I was, I was pretty impressed
by that, but I did, I did have

721
00:35:48,153 --> 00:35:51,653
to kind of wire up the, the, the
core things that I still needed.

722
00:35:51,813 --> 00:35:52,143
Got it.

723
00:35:52,173 --> 00:35:57,353
But I suppose that's mostly one of work
and the overall goal of like reusing

724
00:35:57,363 --> 00:36:01,233
the same code from your Actual desktop
app in the mobile app to the most

725
00:36:01,233 --> 00:36:03,783
degree aside from the UI that paid off.

726
00:36:03,953 --> 00:36:07,493
Yes, I think that paid off like
that specific investment worked.

727
00:36:07,813 --> 00:36:12,903
The real downside to all of this stuff
about actually like actually having an

728
00:36:12,913 --> 00:36:17,583
app and especially for Mobile is that
the ecosystem just changes and your app

729
00:36:17,583 --> 00:36:19,763
is in this like proprietary app store.

730
00:36:19,763 --> 00:36:23,743
And like the proprietary app store
is like forced you to, to update it.

731
00:36:23,753 --> 00:36:27,993
Like I think Android at one point forced
me to like, they basically deprecated an

732
00:36:27,993 --> 00:36:32,543
API and I could not release a new version
of the app until I stopped using that

733
00:36:32,543 --> 00:36:34,303
old API and started using like a new one.

734
00:36:34,593 --> 00:36:37,413
The new one requires some really
weird thing that like triggered

735
00:36:37,413 --> 00:36:39,583
a fault, like a crash on Node.

736
00:36:39,583 --> 00:36:40,293
js mobile.

737
00:36:40,403 --> 00:36:42,123
So like, that's a super risky.

738
00:36:42,403 --> 00:36:46,243
Place to be in and so it, it paid
off, but like, it was a continuous

739
00:36:46,413 --> 00:36:52,023
investment risk that didn't require
any maintenance really, except when

740
00:36:52,023 --> 00:36:54,093
I was forced to buy the ecosystem.

741
00:36:54,153 --> 00:36:55,653
And so that's the real
win of the web, right?

742
00:36:55,663 --> 00:36:57,903
Like, you're not like, the
web definitely is weird.

743
00:36:58,438 --> 00:37:00,928
sucks in a lot of ways and like
they change things and they

744
00:37:00,928 --> 00:37:02,508
force you into certain things.

745
00:37:02,808 --> 00:37:04,768
But generally speaking,
backwards compatibility is a

746
00:37:04,768 --> 00:37:06,248
huge, huge value on the web.

747
00:37:06,248 --> 00:37:08,578
And it's just not the case
in the mobile ecosystem.

748
00:37:08,578 --> 00:37:11,388
And they have leverage on you
because you have to go through

749
00:37:11,388 --> 00:37:15,238
their app stores, which just, I
just hate, like, I love mobile apps.

750
00:37:15,238 --> 00:37:18,418
I love having like a real
app, but like that Node.

751
00:37:18,598 --> 00:37:21,238
js mobile was like a risk for sure.

752
00:37:21,268 --> 00:37:25,618
Because There's like certificates and how
things are assigned that kept changing.

753
00:37:25,618 --> 00:37:29,168
And like was, I luckily
never got totally stuck.

754
00:37:29,198 --> 00:37:31,718
Andre actually, I think was the
only other person using this.

755
00:37:31,988 --> 00:37:34,438
And so luckily he had
like a PR that fixed it.

756
00:37:34,458 --> 00:37:37,478
Cause he, he was stuck in the same way,
but he actually knows how to change stuff.

757
00:37:37,568 --> 00:37:41,418
And so he had a PR merge like
three days earlier that luckily

758
00:37:41,418 --> 00:37:42,318
I was able to update Node.

759
00:37:42,318 --> 00:37:45,208
js mobile and it finally
worked, but it's scary, right.

760
00:37:45,218 --> 00:37:45,668
To be.

761
00:37:45,953 --> 00:37:49,233
Like I could have just been totally
stuck as far as I know, there's not

762
00:37:49,253 --> 00:37:53,913
a great way to have that set up today
without a similar level of risk to,

763
00:37:53,933 --> 00:37:57,003
to, because you're, you would have to
build a pretty bespoke sort of custom

764
00:37:57,243 --> 00:38:02,633
native app on, on mobile to, to do this
whole, uh, truly local-first type thing.

765
00:38:02,853 --> 00:38:03,163
Got it.

766
00:38:03,193 --> 00:38:03,583
Yeah.

767
00:38:03,633 --> 00:38:08,233
I think, well, as you mentioned, uh,
well, most platforms are evolving over

768
00:38:08,233 --> 00:38:12,793
time, some more aggressively and yeah,
with little tolerance for developers

769
00:38:12,793 --> 00:38:16,893
who don't update the apps, I think the
web is more graceful in that regard.

770
00:38:16,923 --> 00:38:20,023
On mobile, the rock is sometimes
being pulled underneath you.

771
00:38:20,303 --> 00:38:24,943
But on the, seeing the glass half
full, you also get hopefully improved

772
00:38:24,953 --> 00:38:27,523
APIs and the ecosystem is improving.

773
00:38:27,603 --> 00:38:32,873
I've been recently getting a bit closer to
the React native ecosystem and Expo seems

774
00:38:32,873 --> 00:38:35,183
to make a lot of that quite a lot nicer.

775
00:38:35,203 --> 00:38:36,963
So there's an impressive amount of.

776
00:38:37,468 --> 00:38:40,278
Bindings to native APIs that
you, that you might need.

777
00:38:40,638 --> 00:38:44,698
So, and also given that JavaScript
is also evolving in terms of

778
00:38:44,708 --> 00:38:46,308
standards, supported standards.

779
00:38:46,818 --> 00:38:50,168
Uh, I think there's now also the
lines are getting a bit blurrier

780
00:38:50,248 --> 00:38:51,798
of what needs to run a Node.

781
00:38:51,798 --> 00:38:55,088
js or what is just supported
by like other JavaScript

782
00:38:55,138 --> 00:38:56,818
execution environments or mobile.

783
00:38:57,118 --> 00:38:59,878
You have JavaScript
core on iOS on Android.

784
00:38:59,878 --> 00:39:03,918
I don't know what it's called, but
you also have Hermes, the tool by the

785
00:39:03,918 --> 00:39:08,248
JavaScript runtime by Facebook, I think,
which is specifically designed for mobile.

786
00:39:08,558 --> 00:39:12,538
So I think you get more options,
but there's still like sharp edges.

787
00:39:13,188 --> 00:39:17,178
So did you eventually give up
on those mobile apps or did

788
00:39:17,178 --> 00:39:19,128
you, did this lead you to web?

789
00:39:19,418 --> 00:39:21,808
How, how did the journey
continue from here?

790
00:39:22,048 --> 00:39:22,398
Sure.

791
00:39:22,428 --> 00:39:26,288
So I'd never gave up on the mobile
apps until I fully open source Actual

792
00:39:26,308 --> 00:39:29,678
when I kind of gave up on Actual and
entirely as, as a whole business.

793
00:39:29,678 --> 00:39:33,308
And that was when I told the
community, Hey, if you want to,

794
00:39:33,388 --> 00:39:36,428
like, this is the source code, I
included the source code of it.

795
00:39:36,428 --> 00:39:40,498
And so if you want to figure out how to
like build this and purchase a developer

796
00:39:40,498 --> 00:39:41,893
account and get this working, like it's.

797
00:39:42,053 --> 00:39:42,833
Totally up to you.

798
00:39:42,983 --> 00:39:46,133
But in terms of the Actual apps
that were on people's phones at the

799
00:39:46,133 --> 00:39:48,563
time, yeah, there was never going
to be another update of those.

800
00:39:48,783 --> 00:39:50,143
I'm still working on shutting down Actual.

801
00:39:50,143 --> 00:39:53,093
And so those apps still do exist in the
App Store as far as I know, but they

802
00:39:53,093 --> 00:39:54,873
will be removed when I shut down Actual.

803
00:39:54,913 --> 00:39:59,513
So there was another phase where
I had not given up yet, but I did

804
00:39:59,543 --> 00:40:03,868
sort of admit defeat in terms of
Where the investment should go.

805
00:40:03,958 --> 00:40:06,278
And I just was like, I
need to be on the web.

806
00:40:06,298 --> 00:40:08,708
Like at that, there was a
point when Actual just like,

807
00:40:08,708 --> 00:40:09,598
didn't even work on the web.

808
00:40:09,598 --> 00:40:10,568
It just was not a thing.

809
00:40:10,718 --> 00:40:13,508
And so I think I might have
had a demo on the web, but it

810
00:40:13,508 --> 00:40:14,738
just like, didn't support it.

811
00:40:14,738 --> 00:40:18,208
Loaded the loaded in the
entire SQLite file locally.

812
00:40:18,238 --> 00:40:20,058
And then it just like,
didn't persist anything.

813
00:40:20,058 --> 00:40:20,228
Right.

814
00:40:20,228 --> 00:40:24,318
It just like the app ran, but it didn't
actually, it kind of like removed a

815
00:40:24,318 --> 00:40:27,458
lot of the functionality and it was
just like a quick demo, but like the.

816
00:40:27,868 --> 00:40:31,528
Man forcing people to, to, to
download an app and running into

817
00:40:31,528 --> 00:40:32,918
problems with their local device.

818
00:40:32,928 --> 00:40:36,378
Like it just, the, the web is such
a powerful distribution mechanism.

819
00:40:36,378 --> 00:40:40,388
And I think that's, you know, everybody
knows that and it's really hard to, to

820
00:40:40,388 --> 00:40:44,088
fight against to fight against that and
to force people to, to download apps.

821
00:40:44,088 --> 00:40:45,898
I honestly still love apps.

822
00:40:45,898 --> 00:40:48,378
Like once it gets set up,
I think it's really nice.

823
00:40:48,408 --> 00:40:51,038
Like if it's an app that I use almost
every day, or even just a couple,

824
00:40:51,068 --> 00:40:52,368
like a couple times a week to have it.

825
00:40:52,579 --> 00:40:54,938
In my, in my doc and I can like close it.

826
00:40:54,938 --> 00:40:55,568
I can quit it.

827
00:40:55,958 --> 00:40:57,558
It doesn't have to be in my mess of tabs.

828
00:40:57,838 --> 00:41:01,988
I have a billion tabs and I, so
like managing my, an app that I use

829
00:41:01,988 --> 00:41:05,098
frequently in the browser to me, I,
I don't really like that, but at the

830
00:41:05,108 --> 00:41:09,368
other, at the same time, There are
things that I use all the time on the

831
00:41:09,368 --> 00:41:11,368
web that are only in my tabs, actually.

832
00:41:11,558 --> 00:41:14,918
And it's very kind of nice to
like be able to close the tab and

833
00:41:14,918 --> 00:41:17,488
then like quickly open up a tab
and go to the app really fast.

834
00:41:17,488 --> 00:41:20,938
Whereas like the app startup for,
you know, Mac OS tends to be like

835
00:41:20,938 --> 00:41:22,068
at least two or three seconds.

836
00:41:22,178 --> 00:41:23,928
So I don't know, it's, it's weird.

837
00:41:23,938 --> 00:41:26,913
I kind of say that I like local
apps and yet I live in a browser

838
00:41:26,913 --> 00:41:28,338
like 80 percent of my time.

839
00:41:28,338 --> 00:41:29,968
So there's something there.

840
00:41:30,338 --> 00:41:32,748
And as much as web technology
kind of sucks, and it's so

841
00:41:32,748 --> 00:41:36,528
confusing and I don't like it,
the web is a really powerful draw.

842
00:41:36,818 --> 00:41:39,738
And so I eventually was just like, you
know what, I need to figure this out.

843
00:41:39,948 --> 00:41:43,258
So I never like stopped investing
in the Electron and, and Node.

844
00:41:43,258 --> 00:41:45,528
js or the um, mobile Node.

845
00:41:45,528 --> 00:41:46,728
js mobile stuff.

846
00:41:46,808 --> 00:41:48,138
I would still update.

847
00:41:48,298 --> 00:41:50,538
Do a release of those
every couple of weeks.

848
00:41:50,928 --> 00:41:54,458
But the first, I remember the first time
that I got Actual working on, um, on the

849
00:41:54,458 --> 00:41:58,338
web and I used, you know, that's where
absurd-sql came from, where I was able

850
00:41:58,338 --> 00:42:03,658
to, to compile things out at a compiled
SQL light to web assembly and use

851
00:42:03,698 --> 00:42:07,718
techniques such that like you can open
the app in multiple tabs and actually

852
00:42:07,728 --> 00:42:09,258
change the data, actually query the data.

853
00:42:09,258 --> 00:42:12,768
And like, when you change data
on one tab, that when you.

854
00:42:12,868 --> 00:42:15,708
Query that data on another
tab, it actually shows up.

855
00:42:15,748 --> 00:42:17,518
I got all that working, which is
really, really great, which is

856
00:42:17,518 --> 00:42:18,808
something that we can talk about more.

857
00:42:19,038 --> 00:42:22,618
I remember like the first time that
I released that and people were

858
00:42:22,618 --> 00:42:24,238
just like instantly able to use it.

859
00:42:24,528 --> 00:42:28,018
And then when I was able to fix a bug
and I was just like with like, basically,

860
00:42:28,538 --> 00:42:30,878
basically in, in, in our sync command.

861
00:42:31,298 --> 00:42:35,218
Was able to like, get that bug in
people's hands or that, that, that

862
00:42:35,218 --> 00:42:39,828
bug fix, um, out there was just like
insanely addicting that, that, I mean,

863
00:42:39,828 --> 00:42:43,698
that's the power of the web, because also
actually, it's just a local app, right?

864
00:42:44,118 --> 00:42:47,268
That's the other thing that I
realized when I built the web app, it

865
00:42:47,268 --> 00:42:49,408
literally is a set of static files.

866
00:42:49,818 --> 00:42:50,648
There's no.

867
00:42:50,858 --> 00:42:54,698
Like there's a syncing service needed,
but like in terms of the entirety of

868
00:42:54,698 --> 00:42:58,278
the app itself, I don't need to do
any database mutations when I deploy.

869
00:42:58,278 --> 00:43:02,198
There's no, there's no large CI pipeline
that does all of these complicated

870
00:43:02,208 --> 00:43:05,978
things to deploy, to restart services, to
update services and do all of this stuff.

871
00:43:06,248 --> 00:43:10,538
It is literally an HTML file and
like five JavaScript bundles.

872
00:43:11,098 --> 00:43:18,018
I can just rsync those over to my web
server that host static files and users

873
00:43:18,108 --> 00:43:21,558
like load those new static files and then
it queries their local data and all of

874
00:43:21,558 --> 00:43:25,998
their local stuff is now reading that
like fresh stuff, but like, just the

875
00:43:26,198 --> 00:43:29,503
ability to just like sync those files
over and just, you know, Deploy like

876
00:43:29,693 --> 00:43:34,123
a hundred times a day just unlocks an
iterative development speed that just

877
00:43:34,153 --> 00:43:36,763
cannot be matched by mobile development.

878
00:43:36,833 --> 00:43:37,153
Yeah.

879
00:43:37,213 --> 00:43:40,753
Waiting for the iOS app store to
finally approve your app to be

880
00:43:40,763 --> 00:43:43,123
released is not a fun state to be in.

881
00:43:43,203 --> 00:43:47,673
And having the ability to just in
a matter of seconds, release a new

882
00:43:47,683 --> 00:43:49,303
version of the, of the web app.

883
00:43:49,598 --> 00:43:51,088
It's just so liberating.

884
00:43:51,398 --> 00:43:55,858
So you've now went from this transition
of initially building an Electron

885
00:43:55,868 --> 00:43:57,678
app, which was just an Electron app.

886
00:43:57,718 --> 00:44:02,028
And then you went to another
platform, mobile, Android, and iOS,

887
00:44:02,418 --> 00:44:04,268
where you could still bring Node.

888
00:44:04,278 --> 00:44:05,528
js with you.

889
00:44:05,778 --> 00:44:10,898
And so the, the platforms were still
like similar enough and they, you could

890
00:44:10,898 --> 00:44:15,788
leverage the native aspects of those
platforms still to the extent that

891
00:44:15,798 --> 00:44:20,218
you've so far needed, um, uh, But the
web is quite different in that regard.

892
00:44:20,468 --> 00:44:23,688
There is no native C
bindings you can leverage.

893
00:44:24,088 --> 00:44:29,618
And I'm not sure how far Wasm was along at
that point, but there would have been one

894
00:44:29,618 --> 00:44:34,948
path where we just say, okay, I need to
completely rewrite Actual, maybe even give

895
00:44:34,948 --> 00:44:40,558
up on SQLite and just like embrace all the
standard things that people do on the web

896
00:44:40,968 --> 00:44:45,678
or somehow bring the architecture and the
technological choices that you had so far.

897
00:44:45,983 --> 00:44:47,123
Bring them to the web.

898
00:44:47,153 --> 00:44:50,603
And that means like a
pretty intense pioneer path.

899
00:44:50,983 --> 00:44:52,633
And I think you've chosen the letter.

900
00:44:52,833 --> 00:44:54,033
So tell me more about that.

901
00:44:54,263 --> 00:44:54,963
Absolutely.

902
00:44:55,003 --> 00:44:58,853
Yeah, it was really a fun, a fun
experiment because the things

903
00:44:58,853 --> 00:45:00,943
that I was using were not that.

904
00:45:01,048 --> 00:45:04,498
Like novel SQLite was really like
the biggest one that just did not

905
00:45:04,498 --> 00:45:06,148
work on the web, everything else.

906
00:45:06,148 --> 00:45:08,228
You know, like if you need like
a background process, you can

907
00:45:08,228 --> 00:45:09,638
just fire up a, a web worker.

908
00:45:09,638 --> 00:45:11,058
It's, it's not too bad.

909
00:45:11,268 --> 00:45:12,908
I'm trying to think of other things I did.

910
00:45:12,968 --> 00:45:17,838
So I was using the, the old node async
hooks, which is now the async local

911
00:45:17,838 --> 00:45:22,688
storage for some really, really neat
stuff for, for the undo and redo mechanism

912
00:45:22,708 --> 00:45:28,158
for Actual, uh, it actually tracks the
messages that get generated for an entire.

913
00:45:28,333 --> 00:45:29,413
Like API requests.

914
00:45:29,523 --> 00:45:30,853
So I have these like handlers.

915
00:45:31,153 --> 00:45:35,773
And so it sets in local and async
local storage, like a buffer of

916
00:45:35,773 --> 00:45:38,073
messages that is fresh each time.

917
00:45:38,273 --> 00:45:42,743
And so when you send an action to do,
while it's executing that entire, like

918
00:45:43,303 --> 00:45:47,923
transaction create method, it tracks
all of the messages that are done

919
00:45:47,933 --> 00:45:49,443
during, like created during that time.

920
00:45:49,813 --> 00:45:53,373
And then it, and then when that
action is done, it reads that from

921
00:45:53,373 --> 00:45:56,983
that buffer and then it packages
them up as like an, like an undo.

922
00:45:57,273 --> 00:45:58,293
Packet, right?

923
00:45:58,313 --> 00:46:00,673
That, and that then gets
stored onto another queue.

924
00:46:00,943 --> 00:46:04,703
And so if you want to undo that,
that's how it knows like which

925
00:46:04,763 --> 00:46:08,683
messages and through which points
of time it needs to like, it needs

926
00:46:08,683 --> 00:46:10,343
to undo up to a certain time, right?

927
00:46:10,423 --> 00:46:12,543
And so the async local source is
really, really great for that.

928
00:46:12,543 --> 00:46:15,153
Cause I didn't have to thread
through all of this stuff.

929
00:46:15,483 --> 00:46:17,793
The web just doesn't support
those kinds of things at all.

930
00:46:17,893 --> 00:46:20,273
So there are things like
that, that I had to undo.

931
00:46:20,528 --> 00:46:23,968
Luckily, that actually improved things
on mobile as well, because Node.

932
00:46:23,968 --> 00:46:27,208
js mobile was actually, I was
finding some, some bugs with some

933
00:46:27,208 --> 00:46:29,588
of those more advanced Node APIs.

934
00:46:30,088 --> 00:46:33,008
So there are things that I had to
simplify and like kind of undo,

935
00:46:33,288 --> 00:46:36,168
but for the most part, SQLite
was like the main hard problem.

936
00:46:37,018 --> 00:46:41,288
And so yes, this is where I, I
experimented and I played and I

937
00:46:41,408 --> 00:46:44,478
was like, it's I mean, WebAssembly
was pretty mature at this point.

938
00:46:44,478 --> 00:46:46,438
I think this was like 2020 or so.

939
00:46:46,468 --> 00:46:49,278
Um, I could be wrong on that, but it
was, you know, like WebAssembly was

940
00:46:49,298 --> 00:46:50,898
like a pretty good at that point.

941
00:46:50,908 --> 00:46:53,958
I had no problems like trying
to bet on WebAssembly then.

942
00:46:54,068 --> 00:46:55,883
It was not that hard to compile.

943
00:46:56,173 --> 00:46:58,503
Uh, SQLite's WebAssembly
at that time, the SQL.

944
00:46:58,503 --> 00:47:01,773
js was, I think, the big library that
already came with the pre compiled SQLite.

945
00:47:01,793 --> 00:47:04,363
So I got the app running with SQL.

946
00:47:04,364 --> 00:47:06,993
js and without a ton
of work, to be honest.

947
00:47:07,223 --> 00:47:10,143
Then it was like, crap, what
happens when you change the data?

948
00:47:10,183 --> 00:47:15,653
Because SQLite literally just slurps in
the whole database in local memory, right?

949
00:47:15,683 --> 00:47:16,473
So you can change the data.

950
00:47:16,473 --> 00:47:17,743
You can be working with your data.

951
00:47:17,853 --> 00:47:19,563
I mean, it all works totally fine.

952
00:47:20,083 --> 00:47:22,583
But then you refresh the tab
and then all that data is lost.

953
00:47:22,593 --> 00:47:24,313
So obviously that's not,
that's not going to work.

954
00:47:24,353 --> 00:47:28,303
So I came up with all these ideas,
like, well, what if I like stored the

955
00:47:28,313 --> 00:47:31,263
messages persistently in the background?

956
00:47:31,283 --> 00:47:33,723
And then like, when you
refresh the tab, it.

957
00:47:34,243 --> 00:47:36,893
You know, reapply this
messages after loading it up.

958
00:47:37,073 --> 00:47:40,163
But that means that every single time
you open up a new tab, it's loading

959
00:47:40,163 --> 00:47:41,943
in the entire database into memory.

960
00:47:41,963 --> 00:47:45,713
And like, for me, at least that
was like a hard requirement.

961
00:47:46,003 --> 00:47:48,613
Do not load the whole
database into memory.

962
00:47:48,823 --> 00:47:52,763
Even worse than that, do not write
the whole database back into memory.

963
00:47:52,793 --> 00:47:55,563
Cause that was, that was the thing that
some people were doing at this time.

964
00:47:55,793 --> 00:47:57,543
They were like, Oh, just
solve this persistent thing.

965
00:47:57,713 --> 00:47:57,913
Okay.

966
00:47:57,933 --> 00:48:00,853
You change one number from four to
five, and you're going to rewrite

967
00:48:00,853 --> 00:48:02,873
this entire six megabyte database.

968
00:48:03,273 --> 00:48:06,163
Back into memory, it is so inefficient.

969
00:48:06,173 --> 00:48:09,523
And I think that's one of the, my gripes
with the web is that like, we've, we've

970
00:48:09,523 --> 00:48:16,323
lost the plot of software development
where it's, where our bar for, for quality

971
00:48:16,383 --> 00:48:22,098
and, and good engineering practices
is so low and it's, Might be a spicy

972
00:48:22,098 --> 00:48:27,108
take, but like, we just accept the
fact that things have to be this bad.

973
00:48:27,228 --> 00:48:31,898
And like, it's so like acceptable to be
like, well, there's nothing I can do.

974
00:48:31,898 --> 00:48:33,068
It's out of my control.

975
00:48:33,478 --> 00:48:33,808
Right.

976
00:48:33,828 --> 00:48:34,928
So I'm just going to write, it's right.

977
00:48:34,928 --> 00:48:35,898
Six megabytes of memory.

978
00:48:35,898 --> 00:48:39,048
Every single time I change
something that is like so bad for

979
00:48:39,068 --> 00:48:41,128
like your computer's hard drive.

980
00:48:41,129 --> 00:48:42,008
It's so bad for your.

981
00:48:42,353 --> 00:48:45,793
Memory consumption, your, your power
is so bad in all sorts of ways, right?

982
00:48:45,793 --> 00:48:46,813
It's so wasteful.

983
00:48:46,843 --> 00:48:51,013
Man, it just makes the app entirely
slow and prone to all kinds of problems.

984
00:48:51,013 --> 00:48:55,253
Like it's, I do have gripes with
just, I feel like our bar as

985
00:48:55,253 --> 00:48:56,403
an industry needs to be higher.

986
00:48:56,403 --> 00:48:59,973
And I, I honestly think that web
development is to blame for some of that.

987
00:49:00,013 --> 00:49:03,383
That's my spicy take because it makes
it easy for us to throw up our hands.

988
00:49:03,963 --> 00:49:07,133
And say, I can't go past
that layer of abstraction.

989
00:49:07,323 --> 00:49:09,733
Whereas in the native
world, you have a problem.

990
00:49:09,753 --> 00:49:13,393
You can dig into those, the C binaries.

991
00:49:13,433 --> 00:49:17,063
it might be hard, but you have the
power to go in and change things.

992
00:49:17,063 --> 00:49:19,293
Whereas on the web, it's
like, it just works that way.

993
00:49:19,293 --> 00:49:19,793
It sucks.

994
00:49:20,153 --> 00:49:20,973
Like too bad.

995
00:49:21,053 --> 00:49:22,683
I think we need aim, aim higher.

996
00:49:22,773 --> 00:49:25,103
And so that's, that's, that
was my approach, right?

997
00:49:25,133 --> 00:49:26,583
I was like, I'm not, I'm
not going to accept this.

998
00:49:26,583 --> 00:49:27,503
This is unacceptable.

999
00:49:27,543 --> 00:49:29,813
Is there a way I, um,
I can get this to work?

1000
00:49:30,123 --> 00:49:30,883
And maybe there wasn't.

1001
00:49:30,883 --> 00:49:32,743
And then I would have
to just sort of give up.

1002
00:49:32,893 --> 00:49:34,953
But I discovered a kind of
novel thing that ended up

1003
00:49:34,953 --> 00:49:36,073
working actually pretty well.

1004
00:49:36,133 --> 00:49:39,708
And that's how absurd-sql came
about was I, I figured out a way.

1005
00:49:39,808 --> 00:49:43,798
The real trick here was, so don't
read everything from memory, right?

1006
00:49:44,078 --> 00:49:45,798
So how do I not read
everything from memory?

1007
00:49:45,798 --> 00:49:49,658
Well, I have to store it in an existing
database that exists on the web.

1008
00:49:49,688 --> 00:49:52,618
There's no question at the time
that that was IndexedDB, right?

1009
00:49:52,628 --> 00:49:55,508
That was like the only
mature database abstraction.

1010
00:49:55,838 --> 00:49:59,348
Okay, so I can store all this
in IndexedDB, this whole blob.

1011
00:49:59,513 --> 00:50:02,433
But I'm still having to read
this whole thing into memory.

1012
00:50:02,443 --> 00:50:03,673
How do I stop that?

1013
00:50:04,163 --> 00:50:06,833
So I figured out, I looked
into SQLite's internal APIs.

1014
00:50:06,833 --> 00:50:09,543
At this point, I was pretty familiar
with SQLite's internal APIs.

1015
00:50:09,543 --> 00:50:11,123
Honestly, it's a really fun read.

1016
00:50:11,163 --> 00:50:14,283
They're well documented,
really straightforward C code.

1017
00:50:14,303 --> 00:50:15,693
It's really, really fun.

1018
00:50:15,793 --> 00:50:17,673
So basically, SQLite works in blocks.

1019
00:50:17,733 --> 00:50:19,773
So you have like, I
forget what size it is.

1020
00:50:20,318 --> 00:50:21,368
Is it 5k blocks?

1021
00:50:21,558 --> 00:50:24,678
Like you can actually change, change the
block size that SQLite works, works by.

1022
00:50:24,738 --> 00:50:27,538
But like, I think the default is 5k.

1023
00:50:27,538 --> 00:50:30,848
I think like 4k, or 8k,
something like that.

1024
00:50:30,848 --> 00:50:32,758
But like, yeah, that,
that ballpark, I think.

1025
00:50:32,938 --> 00:50:33,168
Yeah.

1026
00:50:33,168 --> 00:50:36,488
So that's, that's the amount that it
reads from your hard disk per chunk.

1027
00:50:36,508 --> 00:50:38,788
Like if it needs to run,
read one little bit.

1028
00:50:39,148 --> 00:50:41,178
It reads at least that amount, right?

1029
00:50:41,188 --> 00:50:43,998
So it like reads a whole block in
and then it does what it needs to.

1030
00:50:44,198 --> 00:50:47,168
And then it writes that block back
in if it, if it needs to write stuff.

1031
00:50:47,278 --> 00:50:51,368
So it does, that's how it like, doesn't
read everything in from all the memory.

1032
00:50:51,418 --> 00:50:54,928
Um, and so I thought, I was like,
okay, well, what if, what if in

1033
00:50:55,268 --> 00:50:56,378
indexedDB we store these blocks?

1034
00:50:56,708 --> 00:51:00,448
Like that is the, like, this needs to
be just basically like a file system.

1035
00:51:00,458 --> 00:51:02,798
Like I need to make this
treat as a file system.

1036
00:51:02,798 --> 00:51:03,748
And so I.

1037
00:51:04,058 --> 00:51:07,248
Started playing with this
and I, I, conceptually, I was

1038
00:51:07,248 --> 00:51:08,908
like, this, this could work.

1039
00:51:08,908 --> 00:51:11,768
Like, I don't see any reason why
this couldn't work because once

1040
00:51:12,038 --> 00:51:14,658
I have that, once you can read
different blocks and you don't have

1041
00:51:14,658 --> 00:51:16,018
to read the whole thing into memory.

1042
00:51:16,258 --> 00:51:18,848
Well, SQLite, it was very
straightforward in SQLite's code

1043
00:51:18,858 --> 00:51:20,178
about how it writes stuff down.

1044
00:51:20,228 --> 00:51:22,578
I was like, well, I can also, I
can also just write the blocks.

1045
00:51:23,198 --> 00:51:23,578
Right?

1046
00:51:23,928 --> 00:51:27,238
Every, I feel like every, every
three or four years I have this,

1047
00:51:27,238 --> 00:51:31,838
like, I see a gap and like, it's,
it's intriguing to me a lot.

1048
00:51:31,878 --> 00:51:34,538
And like, I, I like prettier
was kind of this thing too.

1049
00:51:34,548 --> 00:51:37,078
Like I see this thing that's like, man,
there's this problem that everything

1050
00:51:37,078 --> 00:51:40,318
is having, but like, I see this thing
that works really well over here.

1051
00:51:40,318 --> 00:51:41,338
Like, can we just.

1052
00:51:41,893 --> 00:51:43,673
Take that from over there
and like apply it here.

1053
00:51:43,673 --> 00:51:44,733
Like, like, does that work?

1054
00:51:44,853 --> 00:51:47,603
And you know, a third, a third
of the time, it ends up being an

1055
00:51:47,603 --> 00:51:48,653
interesting thing that does work.

1056
00:51:48,713 --> 00:51:53,533
Essentially all of that to say,
I discovered how to store these

1057
00:51:53,533 --> 00:51:58,553
blocks in, into IndexedDB to read
and write them from, from IndexedDB.

1058
00:51:58,803 --> 00:52:02,313
And I was able to leverage
IndexedDB's transactional

1059
00:52:02,333 --> 00:52:05,063
semantics so that locking worked.

1060
00:52:05,133 --> 00:52:09,018
And that's the real critical thing here
is that like SQLite, heavily relies

1061
00:52:09,048 --> 00:52:13,458
on file system locking API, such that
says, I'm going to lock this block.

1062
00:52:13,478 --> 00:52:14,928
I'm going to lock this piece of data here.

1063
00:52:15,308 --> 00:52:16,818
Do not let anything else write to that.

1064
00:52:16,818 --> 00:52:18,028
That is a stable database.

1065
00:52:18,048 --> 00:52:19,798
Like that is how it does not get corrupt.

1066
00:52:19,888 --> 00:52:22,608
If it, if you break those
semantics, it will get corrupt.

1067
00:52:22,978 --> 00:52:25,368
And so, um, I was able to leverage.

1068
00:52:25,878 --> 00:52:27,538
IndexedDB is like locking semantics.

1069
00:52:27,538 --> 00:52:30,658
And I was like, I can map these
onto these such that like, when I'm

1070
00:52:30,658 --> 00:52:32,868
writing this thing, nothing else
should be able to read from it.

1071
00:52:32,938 --> 00:52:33,678
And it worked.

1072
00:52:33,758 --> 00:52:36,458
So you can like load in
the data in different tabs.

1073
00:52:36,458 --> 00:52:39,988
You can write to that in like one tab
and like see that data in the other tab.

1074
00:52:40,068 --> 00:52:41,108
And it worked out really well.

1075
00:52:41,108 --> 00:52:43,028
And I was like, this would be
a really interesting thing to

1076
00:52:43,028 --> 00:52:43,938
open source and talk about.

1077
00:52:43,958 --> 00:52:44,738
And so.

1078
00:52:44,738 --> 00:52:48,388
It became absurd-sql and it became
like a, a pretty influential thing.

1079
00:52:48,388 --> 00:52:52,578
I think, honestly, this kind of project,
I invest like a month or two of my life

1080
00:52:52,578 --> 00:52:54,128
into, and then like, I have a day job.

1081
00:52:54,178 --> 00:52:55,928
And so I just did not have a lot of time.

1082
00:52:55,948 --> 00:52:59,268
Like I got it working for Actual and it
deployed Actual and it worked on the web.

1083
00:52:59,268 --> 00:53:03,238
And that was amazing, but I just didn't
have the resources or the time to really

1084
00:53:03,238 --> 00:53:07,028
like build it out or like fix really
obscure bugs that people were coming up, I

1085
00:53:07,028 --> 00:53:10,203
sort of like kind of left it as a project
that wasn't, that wasn't Maintained

1086
00:53:10,203 --> 00:53:13,143
super well, and sometimes I feel guilty
about that, but I think there's other

1087
00:53:13,173 --> 00:53:14,623
community members that have taken it on.

1088
00:53:14,623 --> 00:53:17,273
It's, it's more of like an
influential prototype, I think,

1089
00:53:17,553 --> 00:53:19,783
more than like a mantained thing.

1090
00:53:19,928 --> 00:53:24,378
Yeah, I think for me, this is
actually kind of like a, a definition

1091
00:53:24,398 --> 00:53:26,838
of a kind of project category.

1092
00:53:26,868 --> 00:53:30,658
Like not every project needs
to like prettier lived on and

1093
00:53:30,658 --> 00:53:32,148
now it's like super common.

1094
00:53:32,178 --> 00:53:36,778
And like, I think that's one very
successful way how a project can evolve.

1095
00:53:37,078 --> 00:53:41,518
But I think with absurd-sql, you've built
something it's even more than a proof of

1096
00:53:41,518 --> 00:53:44,158
concept because you used it in production.

1097
00:53:44,198 --> 00:53:46,343
I, I use it for a while in production.

1098
00:53:46,353 --> 00:53:50,523
Other people use it for a while in
production, but, uh, like you said, like

1099
00:53:50,533 --> 00:53:52,983
it became a very influential project.

1100
00:53:53,003 --> 00:53:57,813
And I think it showed to the world,
like, Hey, we don't need to settle for

1101
00:53:57,833 --> 00:54:02,893
saving six megabytes or 50 megabytes
for every little change as you do.

1102
00:54:02,903 --> 00:54:07,113
Want to do persistence in the web before
people would just like ride, like.

1103
00:54:07,498 --> 00:54:12,218
80 megabyte JSON files, like all the
time to, to index to be, and then we're

1104
00:54:12,448 --> 00:54:16,158
then wonder why their, the app is slow
and their, their CPU is going crazy.

1105
00:54:16,438 --> 00:54:20,158
So you aiming for higher, I
think that has a massive impact.

1106
00:54:20,188 --> 00:54:24,118
And I think that's now those
ideas are still like leveraged

1107
00:54:24,128 --> 00:54:25,648
now in other projects.

1108
00:54:25,658 --> 00:54:26,903
Whether I think.

1109
00:54:26,903 --> 00:54:32,433
The wa-sqlite, um, project that is,
I think also using some, some similar

1110
00:54:32,433 --> 00:54:37,993
approaches and by now we also have a
different storage mechanism and the web

1111
00:54:37,993 --> 00:54:42,513
that is more and more common, namely
OPFS, original private file system.

1112
00:54:43,353 --> 00:54:47,483
Which also already gives you a file
system representation where you can

1113
00:54:47,493 --> 00:54:49,493
lock individual files, et cetera.

1114
00:54:49,494 --> 00:54:53,223
I think the details are still being
figured out right now, but that's, for

1115
00:54:53,223 --> 00:54:57,743
example, what I'm using for Overtone and
in production, that's already quite nice.

1116
00:54:57,783 --> 00:55:03,233
But I think you've really, you, you went
super early on that and very daring from

1117
00:55:03,243 --> 00:55:07,123
first principles and that's so admirable
and that, that like very inspiring.

1118
00:55:07,233 --> 00:55:07,893
Thank you very much.

1119
00:55:07,893 --> 00:55:08,963
I appreciate that.

1120
00:55:08,973 --> 00:55:10,773
Sometimes I, I don't know.

1121
00:55:10,793 --> 00:55:11,973
I, I don't really track.

1122
00:55:12,403 --> 00:55:16,843
The, like some of the fallout sometimes,
like I, I just had to invest in my job

1123
00:55:16,843 --> 00:55:18,333
a lot for the next like six months.

1124
00:55:18,373 --> 00:55:20,173
And so sometimes I'm, I'm not sure.

1125
00:55:20,173 --> 00:55:22,813
I don't, I don't know how much influence
it has, but it's, it's good to hear

1126
00:55:22,813 --> 00:55:25,483
feedback that work is impactful.

1127
00:55:25,583 --> 00:55:29,123
I, I would love to hear about your
experience with, with it, with OPFS.

1128
00:55:29,133 --> 00:55:31,623
And, and, uh, are you using
the file system native access

1129
00:55:31,733 --> 00:55:33,363
stuff as well and in your app?

1130
00:55:33,603 --> 00:55:34,233
How's that going?

1131
00:55:34,473 --> 00:55:35,473
The latter, not yet.

1132
00:55:35,503 --> 00:55:39,233
I'm also planning to, since like
for Overtone, which is a music

1133
00:55:39,233 --> 00:55:43,353
player, I do want to also support
you bringing your own music that

1134
00:55:43,353 --> 00:55:47,043
you might have like in a download
folder on a music folder somewhere.

1135
00:55:47,103 --> 00:55:50,683
Right now I don't use that
particular browser APIs yet.

1136
00:55:51,113 --> 00:55:54,413
But I'm using OPFS for many purposes.

1137
00:55:54,603 --> 00:55:59,353
I'm using OPFS for persistence
of a SQLite database, where you

1138
00:55:59,353 --> 00:56:01,403
would have used IndexedDB before.

1139
00:56:01,403 --> 00:56:05,213
And I think some people still
use IndexedDB for targeting

1140
00:56:05,263 --> 00:56:06,513
older browsers as well.

1141
00:56:06,573 --> 00:56:11,133
But I'm also using OPFS for like what
you would use a file system for as

1142
00:56:11,133 --> 00:56:13,083
well, which is like storing files.

1143
00:56:13,113 --> 00:56:17,998
So for example, Before displaying
images in the app, I actually don't

1144
00:56:18,008 --> 00:56:22,368
use like just an image tag and
then point to an external URL since

1145
00:56:22,618 --> 00:56:24,538
those URLs might go away, et cetera.

1146
00:56:24,538 --> 00:56:30,088
So I actually download those images, store
them in OPFS and then pre process them.

1147
00:56:30,313 --> 00:56:34,093
Um, on a worker and sent them
over on a, on an off screen

1148
00:56:34,123 --> 00:56:35,243
canvas to the main thread.

1149
00:56:35,703 --> 00:56:40,123
So I'm like using some, some more
like native development practices here

1150
00:56:40,123 --> 00:56:41,573
and trying to bring them to the web.

1151
00:56:41,683 --> 00:56:45,953
Very, very much sharing the same
opinion, like your spicy take from,

1152
00:56:46,133 --> 00:56:49,683
for me, not so spicy take that
we should aim higher in the web.

1153
00:56:49,703 --> 00:56:51,923
And I think we can learn quite a lot from.

1154
00:56:52,253 --> 00:56:54,423
More native development backgrounds.

1155
00:56:54,813 --> 00:57:00,153
And this is, I think this is how we
get, uh, after all pretty fast web apps.

1156
00:57:00,403 --> 00:57:04,793
Figma is another notable example
there where how you get actually a

1157
00:57:04,833 --> 00:57:10,583
really high performance app that feels
nice is by aiming higher and, uh,

1158
00:57:10,593 --> 00:57:14,693
bringing some of those methodologies
from other environments to the web.

1159
00:57:14,953 --> 00:57:15,993
So I think.

1160
00:57:16,168 --> 00:57:19,748
There's still a couple of
interesting aspects in absurd-sql

1161
00:57:19,768 --> 00:57:21,868
that we haven't yet gotten into.

1162
00:57:21,968 --> 00:57:26,528
So you've mentioned that you're
using index to be, uh, with, uh, the

1163
00:57:26,528 --> 00:57:30,428
block, um, the, the block storage
and the index to be transactions,

1164
00:57:30,868 --> 00:57:33,318
but to actually write that you could.

1165
00:57:33,578 --> 00:57:37,048
I guess either do, could you do
that on the main thread since you've

1166
00:57:37,048 --> 00:57:38,718
chosen to do that on a worker?

1167
00:57:38,938 --> 00:57:42,458
So maybe you can talk a little bit
about the, the threading model and

1168
00:57:42,468 --> 00:57:49,338
also how you even went beyond IndexedDB
transactions and using atomics and

1169
00:57:49,338 --> 00:57:54,628
shared array buffers to still slay some
dragons that needed to be slayed here.

1170
00:57:54,798 --> 00:57:59,348
Yeah, there is one little trick that I
discovered and I think I was actually

1171
00:57:59,348 --> 00:58:01,668
a live stream and I think I have a
recording of it, which was kind of fun.

1172
00:58:01,668 --> 00:58:04,758
It's like a fun little blew
my mind at, um, at the time.

1173
00:58:04,848 --> 00:58:07,328
And I think this is a trick
that was independently

1174
00:58:07,328 --> 00:58:08,608
discovered by several people.

1175
00:58:08,618 --> 00:58:12,678
I think there's a, uh, there's another
library that I think from is from some,

1176
00:58:12,698 --> 00:58:15,998
some Google folks about how, like,
like loading in third party things

1177
00:58:15,999 --> 00:58:19,618
on, you know, In like an iframe or
something to kind of like sandbox things

1178
00:58:19,668 --> 00:58:21,478
that that uses this same technique.

1179
00:58:21,498 --> 00:58:23,998
But essentially here's,
here's the problem.

1180
00:58:24,288 --> 00:58:28,548
When you compile SQLite through
WebAssembly, you can intercept

1181
00:58:28,568 --> 00:58:30,238
the API calls that it makes.

1182
00:58:30,248 --> 00:58:33,778
So you can intercept the like read
The read and write command, like

1183
00:58:33,778 --> 00:58:37,148
there's a C API is reading right
for reading and writing files.

1184
00:58:37,338 --> 00:58:41,028
And so you can say, okay, I'm going to
implement the read command for like,

1185
00:58:41,328 --> 00:58:45,078
through WebAssembly, the WebAssembly
compiled version of this SQLite.

1186
00:58:45,118 --> 00:58:47,218
I'm going to implement the
read command and like read some

1187
00:58:47,218 --> 00:58:48,388
data for it and give it back.

1188
00:58:48,658 --> 00:58:52,348
So I can just read from index, from index
db and everything is going to work right.

1189
00:58:52,478 --> 00:58:55,208
The, the problem is, is that
the read command from C is

1190
00:58:55,238 --> 00:58:56,678
completely synchronous, right?

1191
00:58:56,708 --> 00:59:00,478
So the WebAssembly compiled binary
expects that to be synchronous.

1192
00:59:00,528 --> 00:59:03,768
Like you can, it'll call out to
JavaScript and you can implement the read

1193
00:59:03,768 --> 00:59:06,338
command, but you cannot await in there.

1194
00:59:06,348 --> 00:59:09,918
Like you literally have to, in that event
loop, synchronously return some data.

1195
00:59:10,328 --> 00:59:13,418
And so, uh, when I hit that, I was
like, I thought I was dead in the water.

1196
00:59:13,418 --> 00:59:14,318
I was like this, this.

1197
00:59:14,708 --> 00:59:15,428
I just can't do this.

1198
00:59:15,718 --> 00:59:18,098
Maybe, maybe it wasn't that bad
actually, because I think I did

1199
00:59:18,148 --> 00:59:22,428
see, so one option is you can in
WebAssembly, I think it has a way to

1200
00:59:22,538 --> 00:59:24,518
convert synchronous APIs to async.

1201
00:59:24,988 --> 00:59:27,188
I think it's called like
asynchronify or something like that.

1202
00:59:27,228 --> 00:59:28,978
I was terrified of it to be honest.

1203
00:59:29,358 --> 00:59:30,708
And so I thought it just
wasn't going to work.

1204
00:59:30,778 --> 00:59:32,048
I was terrified about the performance.

1205
00:59:32,158 --> 00:59:36,388
I've had experiences in some
codebases that overuse of asynchronous

1206
00:59:36,458 --> 00:59:41,168
APIs really killed performance
because waiting one promise tick.

1207
00:59:41,533 --> 00:59:46,363
In every single layer of the
abstraction is very not noticeable

1208
00:59:46,363 --> 00:59:49,963
at a small scale, but it very
becomes noticeable at a large scale.

1209
00:59:50,113 --> 00:59:54,033
And the really pernicious thing about it
is that it's death by not a thousand cuts.

1210
00:59:54,173 --> 00:59:55,333
It's death by a million cuts.

1211
00:59:55,653 --> 00:59:57,803
Like, it's, you can't
go in there and remove.

1212
00:59:58,013 --> 00:59:59,683
20 by 20 awaits, right?

1213
00:59:59,823 --> 01:00:00,683
And like, it'll be fine.

1214
01:00:00,763 --> 01:00:03,693
The thing that I don't like about
async code is that as you abstract

1215
01:00:03,693 --> 01:00:06,393
things away, so if you take it from
two abstractions to 10 abstract, like

1216
01:00:06,403 --> 01:00:10,523
you split a two async functions into
10 async functions, well, every one

1217
01:00:10,523 --> 01:00:12,163
of them has to await on each other.

1218
01:00:12,443 --> 01:00:14,513
And so it's literally making it slower.

1219
01:00:14,698 --> 01:00:16,618
And that's not  the
case , with synchronous stuff.

1220
01:00:16,618 --> 01:00:17,598
You can make it two functions.

1221
01:00:17,598 --> 01:00:19,358
You can make it 500 functions.

1222
01:00:19,358 --> 01:00:21,608
You can abstract things
the way that you want to.

1223
01:00:21,808 --> 01:00:25,008
The shape of your abstraction
literally impacts performance when

1224
01:00:25,008 --> 01:00:26,568
you're in, in, um, async world.

1225
01:00:26,668 --> 01:00:31,258
So I have experience with code bases that
like, that did things in a really weird

1226
01:00:31,298 --> 01:00:33,508
way, like awaited literally everything.

1227
01:00:33,648 --> 01:00:36,488
And it was, it was causing
all sorts of perf problems.

1228
01:00:36,778 --> 01:00:40,438
And so that, For that reason, from my
experience, I was terrified of it because

1229
01:00:40,438 --> 01:00:44,778
I thought it made every single function
in C in the WebAssembly binary async.

1230
01:00:44,928 --> 01:00:48,528
It turns out that I was probably wrong
and I probably should have done my

1231
01:00:48,528 --> 01:00:52,278
own investigation in like performance
profiling because I got some feedback.

1232
01:00:52,713 --> 01:00:56,973
Later on, like months after I released
absurd-sql, I think from some people

1233
01:00:56,973 --> 01:01:00,463
that I respected pretty well that
were saying, uh, the async stuff is

1234
01:01:00,463 --> 01:01:03,883
actually fine, like it actually has
a very negligible performance impact.

1235
01:01:04,143 --> 01:01:06,823
So there's probably some tricks
that they, they, they do that.

1236
01:01:07,003 --> 01:01:07,723
So it's probably fine.

1237
01:01:07,723 --> 01:01:11,673
And I think that WA SQLite
project that you mentioned, I

1238
01:01:11,673 --> 01:01:13,993
think it actually uses this mode.

1239
01:01:14,273 --> 01:01:17,693
And the good thing about that is
that it doesn't need atomics, which

1240
01:01:17,713 --> 01:01:19,603
means it doesn't require HTTPS.

1241
01:01:19,953 --> 01:01:21,083
So I'm probably.

1242
01:01:21,243 --> 01:01:24,163
Let me jump back a little bit to
explain why atomics are even needed.

1243
01:01:24,333 --> 01:01:26,493
So let's go back to that
synchronous method, right?

1244
01:01:26,683 --> 01:01:30,333
So I need to call into IndexedDB
and get and do an async API.

1245
01:01:30,713 --> 01:01:31,723
How am I going to do that?

1246
01:01:31,753 --> 01:01:37,263
Well, there's a little trick that
you can do by making an async call.

1247
01:01:37,393 --> 01:01:42,403
And I believe that it's Has
to be on a different thread.

1248
01:01:42,493 --> 01:01:44,443
So there's like two
background threads, right?

1249
01:01:44,483 --> 01:01:46,123
There's like the normal backend thread.

1250
01:01:46,133 --> 01:01:48,083
And then there's another separate thread.

1251
01:01:48,303 --> 01:01:51,773
That's like the thread that reads and
writes from index CB like asynchronously.

1252
01:01:51,773 --> 01:01:52,073
Right.

1253
01:01:52,423 --> 01:01:55,963
Between those two threads, you
share a shared array buffer, which

1254
01:01:55,963 --> 01:01:59,548
is this really low level, really
interesting thing  on the web, which

1255
01:01:59,548 --> 01:02:01,138
is shared memory across threads.

1256
01:02:01,538 --> 01:02:05,478
And on top of that, you, uh, there's
APIs you can use called atomics, which

1257
01:02:05,488 --> 01:02:08,478
allow you to interact with this shared
array buffer, and you can actually

1258
01:02:08,528 --> 01:02:12,348
coordinate across the threads and do
some really, really powerful things.

1259
01:02:12,358 --> 01:02:15,468
So one of those things that you can
do is you can write to that shared

1260
01:02:15,498 --> 01:02:16,938
array buffer in a certain way.

1261
01:02:17,078 --> 01:02:19,888
And then in another thread,
you can call atomics.Wait.

1262
01:02:20,428 --> 01:02:24,118
And what atomics.wait does is it literally
synchronously blocks that thread from

1263
01:02:24,118 --> 01:02:26,198
running like it, you call that thread.

1264
01:02:26,218 --> 01:02:29,288
And if it does not wake up,
then it won't wake up ever.

1265
01:02:29,358 --> 01:02:33,288
Like you call atomic weight and you
tell it, wait for this bit to be

1266
01:02:33,288 --> 01:02:34,778
flipped in the shared array buffer.

1267
01:02:34,998 --> 01:02:37,228
And then the other thread can
flip that bit when it's not.

1268
01:02:37,293 --> 01:02:40,123
done, and then the other
thing can continue executing.

1269
01:02:40,423 --> 01:02:44,423
Using that technique, we can read from an
asynchronous thing in the second thread,

1270
01:02:44,523 --> 01:02:48,883
do whatever we need to, and store that
data in some sort of buffer somewhere.

1271
01:02:49,133 --> 01:02:52,173
And then while the first thread is
blocked on that atomic set, wait.

1272
01:02:52,453 --> 01:02:55,133
And then once the, once that
bit is flipped and it continues

1273
01:02:55,133 --> 01:02:58,143
executing, then it can read from that
buffer and actually get the data.

1274
01:02:58,423 --> 01:03:02,008
And so using that technique, Um, I
use that technique in every single API

1275
01:03:02,018 --> 01:03:06,138
in, in Upstairs SQL to turn an async
function into a synchronous function.

1276
01:03:06,428 --> 01:03:07,898
And that's how it can
interface with IndexedDB.

1277
01:03:09,168 --> 01:03:12,678
The downside though, is that to use
atomic set weight, it's one of those

1278
01:03:13,368 --> 01:03:18,628
newer APIs that Chrome and other browsers
force HTTPS a secure context on you.

1279
01:03:18,708 --> 01:03:22,318
And so your app has to be running under
HTTPS, which I've always said, like.

1280
01:03:22,603 --> 01:03:23,103
Who cares?

1281
01:03:23,113 --> 01:03:24,933
Like, of course you're going to
be running under that anyway.

1282
01:03:25,073 --> 01:03:27,473
It's been enough of a,
kind of a pain point.

1283
01:03:27,483 --> 01:03:30,063
Like people are using like weird
reverse proxies or doing their

1284
01:03:30,063 --> 01:03:32,573
own things where suppose, I guess
some people just don't really care

1285
01:03:32,583 --> 01:03:33,853
and they just want to run HTTP.

1286
01:03:33,973 --> 01:03:37,443
And so Actual, like the open source
version of Actual can't run under HTTP.

1287
01:03:37,463 --> 01:03:40,193
And that's like, you always
have to be setting up to me.

1288
01:03:40,193 --> 01:03:42,883
It's, I don't know, I still am a
little bit like, I don't really care.

1289
01:03:42,923 --> 01:03:45,843
Like you can just like set up
HTTPS, but it's enough of a source

1290
01:03:45,843 --> 01:03:48,873
of a pain that I can see benefits
and not, not having to require it.

1291
01:03:49,178 --> 01:03:49,578
Right.

1292
01:03:49,588 --> 01:03:53,828
Yeah, I think this was also related
to the, uh, Spectre exploit at

1293
01:03:53,828 --> 01:03:55,368
some point, uh, vulnerability.

1294
01:03:55,788 --> 01:03:59,988
And I think it's not just that you need
to run on HTTPS by now, but I think you

1295
01:03:59,998 --> 01:04:02,998
also need to have a few HTTP headers set.

1296
01:04:03,058 --> 01:04:07,298
I think like cross origin, uh,
open policy and embedders policy.

1297
01:04:07,358 --> 01:04:10,758
And I think there was also like
a limitation that Safari didn't

1298
01:04:10,758 --> 01:04:12,698
support it well for, for some time.

1299
01:04:12,718 --> 01:04:17,728
So yeah, there it's, Still, browsers
are still growing up, but I think at

1300
01:04:17,728 --> 01:04:21,628
some point this can be assumed that, uh,
this will just work, but on the other

1301
01:04:21,638 --> 01:04:26,108
side, I think those tricks might also,
you mentioned that asyncify approach.

1302
01:04:26,328 --> 01:04:28,428
So that is also another option.

1303
01:04:28,428 --> 01:04:31,778
And I think there's even a new
approach stabilizing right now.

1304
01:04:32,468 --> 01:04:36,178
Where WebAssembly natively
can integrate with Promises.

1305
01:04:36,588 --> 01:04:41,918
So I think that's a, that's a new, um,
development around WASM and browsers.

1306
01:04:42,298 --> 01:04:47,168
So I think there's, you, you certainly
use quite the bleeding edge there.

1307
01:04:47,478 --> 01:04:51,098
And so, yeah, right now it's
getting more stabilized.

1308
01:04:51,548 --> 01:04:55,308
But I do think there is some truth
to what you've mentioned in regards

1309
01:04:55,338 --> 01:04:59,648
to avoiding asynchronous code when
possible, since it's not just like

1310
01:04:59,648 --> 01:05:02,388
a potential performance overhead.

1311
01:05:02,558 --> 01:05:06,808
So, and I think some people go even
as far as saying that going from

1312
01:05:06,848 --> 01:05:09,418
callbacks to async await and promises.

1313
01:05:09,738 --> 01:05:12,008
Was one of the biggest
mistakes in JavaScript.

1314
01:05:12,048 --> 01:05:14,208
I think that can also be
considered a hot take.

1315
01:05:14,478 --> 01:05:17,848
Let's see where we'll end up
in a couple of years on that.

1316
01:05:17,938 --> 01:05:22,208
But, uh, aside from the performance,
I think another common downside

1317
01:05:22,238 --> 01:05:25,938
of asynchronous code is that it
basically introduces distributed

1318
01:05:25,938 --> 01:05:28,288
systems problems into, into your code.

1319
01:05:28,618 --> 01:05:32,528
In this case, where we basically
just wrap an API, I think it's okay.

1320
01:05:32,528 --> 01:05:37,853
Um, But, uh, that's a, that's another
notable difference of like how, what

1321
01:05:37,883 --> 01:05:42,563
we've been exploring with LiveStore
and, and Riffle is by really making

1322
01:05:42,563 --> 01:05:46,573
the, like in a browser context, still
from the main thread, allowing for

1323
01:05:46,573 --> 01:05:52,083
synchronous SQL queries, which return
very fast and therefore like you can just

1324
01:05:52,243 --> 01:05:54,143
write your normal JavaScript as you do.

1325
01:05:54,478 --> 01:05:56,808
comes at a risk of potentially
blocking the main thread.

1326
01:05:56,808 --> 01:06:01,058
That's a, that's a different
challenge, but if you, if you have

1327
01:06:01,138 --> 01:06:04,168
performance under control, it gives
you a much simpler programming model.

1328
01:06:04,448 --> 01:06:05,618
So that's another weak one.

1329
01:06:05,688 --> 01:06:09,568
Yeah, that's, I'm all here
for synchronous SQL queries.

1330
01:06:09,618 --> 01:06:11,238
I think it's crazy.

1331
01:06:11,238 --> 01:06:14,488
I think there's been no libraries
before that integrated with SQLite 3 and

1332
01:06:14,488 --> 01:06:16,418
like made all of the APIs, um, async.

1333
01:06:16,458 --> 01:06:17,138
It was crazy.

1334
01:06:17,138 --> 01:06:20,348
Like it's such a in
memory super close local.

1335
01:06:20,613 --> 01:06:24,153
thing and , you really want at the
low level to, to provide the at

1336
01:06:24,153 --> 01:06:25,233
least option to be synchronous.

1337
01:06:25,473 --> 01:06:28,793
And it, so that I just remembered
actually, it's not just

1338
01:06:28,793 --> 01:06:30,313
wrapping an async function.

1339
01:06:30,513 --> 01:06:32,933
So like it's, it's an internal
implementation detail.

1340
01:06:33,203 --> 01:06:37,903
If you go the asynchronous by route,
which turns C functions into asynchronous

1341
01:06:37,903 --> 01:06:40,993
functions, therefore you can interact
with like asynchronous stuff.

1342
01:06:41,428 --> 01:06:46,618
Believe that that, that forces you
when you call a SQLite by like method,

1343
01:06:47,118 --> 01:06:48,798
that method is an asynchronous method.

1344
01:06:49,198 --> 01:06:51,878
You cannot synchronously execute a SQL.

1345
01:06:51,898 --> 01:06:56,898
That was a big reason why I also really
did not want to use WI SQL because I

1346
01:06:56,898 --> 01:06:58,308
wanted my functions to be synchronous.

1347
01:06:58,598 --> 01:07:01,778
My app depends on many of them being
synchronous and just my workflows.

1348
01:07:01,778 --> 01:07:04,428
And it just, it just greatly
simplifies the entire workflow.

1349
01:07:04,658 --> 01:07:06,548
There's no reason for
me to make this async.

1350
01:07:06,788 --> 01:07:08,278
This is a single client.

1351
01:07:08,798 --> 01:07:11,788
App, there's one request
coming through at a time.

1352
01:07:11,798 --> 01:07:13,768
I can control it entirely, right?

1353
01:07:13,768 --> 01:07:16,738
This is not a web server handling
thousands of requests at a time

1354
01:07:17,378 --> 01:07:21,158
to take on the complexities of
asynchronous code with the performance

1355
01:07:21,208 --> 01:07:23,438
hits was just ridiculous to me.

1356
01:07:23,518 --> 01:07:28,633
And so I, This technique I still think
has has merit, actually, the more I think

1357
01:07:28,643 --> 01:07:33,893
about it, and it's something that I owe
this community like a great deal of, of

1358
01:07:34,193 --> 01:07:36,483
like blog posts and and and research.

1359
01:07:36,483 --> 01:07:39,163
I need to sit down and really
like go through what the latest

1360
01:07:39,163 --> 01:07:40,603
and greatest is and really.

1361
01:07:40,928 --> 01:07:44,188
Vetted and see, see what people
are landing on, because if it's

1362
01:07:44,248 --> 01:07:47,008
still not possible to do that,
I think that's a huge downside.

1363
01:07:47,258 --> 01:07:47,808
I do.

1364
01:07:47,818 --> 01:07:51,058
I did just see that the, the
file system access APIs, I

1365
01:07:51,058 --> 01:07:52,738
believe they finally converted.

1366
01:07:53,018 --> 01:07:56,918
Originally there were some of them
in the, in a worker thread that

1367
01:07:56,958 --> 01:08:00,438
were supposed to be synchronous, but
in the spec, we're actually async.

1368
01:08:00,798 --> 01:08:03,268
And that made me really, really not happy.

1369
01:08:03,298 --> 01:08:05,158
And I filed like a GitHub ticket.

1370
01:08:05,368 --> 01:08:07,108
I think they finally, if I.

1371
01:08:07,163 --> 01:08:08,453
Just checked earlier this morning.

1372
01:08:08,453 --> 01:08:11,393
They finally have on the MDN
page says that they are now

1373
01:08:11,393 --> 01:08:12,263
synchronous, which is great.

1374
01:08:12,323 --> 01:08:14,333
So hopefully we're moving
in the right direction.

1375
01:08:14,653 --> 01:08:17,413
But I think it's, yeah, I think it's
really, I'm all about synchronous 'cause

1376
01:08:17,413 --> 01:08:21,883
it means that you can use them in context
that like are synchronous, like there you

1377
01:08:21,888 --> 01:08:27,313
might be removing a lot of functionality
because once something is async, the thing

1378
01:08:27,313 --> 01:08:28,993
that uses it has to be async as well.

1379
01:08:29,113 --> 01:08:32,088
And a lot of these cases for local-first
apps especially, it just is, there's,

1380
01:08:32,233 --> 01:08:33,763
there's literally no benefit to it.

1381
01:08:33,923 --> 01:08:35,033
, it's a local app.

1382
01:08:35,238 --> 01:08:37,968
There's not a second user that
can come be querying this.

1383
01:08:38,278 --> 01:08:38,628
I don't know.

1384
01:08:38,668 --> 01:08:39,898
I think I get it.

1385
01:08:40,188 --> 01:08:42,578
I think we've, we've over
indexed a little bit on this.

1386
01:08:42,578 --> 01:08:44,988
And I do think that I
forgot about the headers.

1387
01:08:45,128 --> 01:08:48,608
That is because like reverse
proxies can like drop those headers

1388
01:08:48,608 --> 01:08:50,938
and then like a user is like,
why isn't this app even loading?

1389
01:08:50,958 --> 01:08:51,868
So I, I don't know.

1390
01:08:51,878 --> 01:08:52,528
It's a trade off.

1391
01:08:52,558 --> 01:08:55,518
I still probably lean a little
bit towards it's worth it.

1392
01:08:55,568 --> 01:09:00,408
But yeah, I completely agree with, uh,
I think this is the same theme again,

1393
01:09:00,428 --> 01:09:05,848
where in web development, we've kind
of gotten so used to some practices.

1394
01:09:06,048 --> 01:09:09,708
And I think it's one is, uh,
being efficient and performance

1395
01:09:09,708 --> 01:09:13,318
minded, but another is just
like the programming models, how

1396
01:09:13,318 --> 01:09:15,398
they've kind of eroded over time.

1397
01:09:15,808 --> 01:09:18,508
we need to deal with distributed
systems problems where they're

1398
01:09:18,508 --> 01:09:20,128
just completely accidental.

1399
01:09:20,508 --> 01:09:25,208
And , we're just so used to, to like
so many things being asynchronous,

1400
01:09:25,278 --> 01:09:27,068
which doesn't need to be asynchronous.

1401
01:09:27,398 --> 01:09:30,508
We kind of went from callback
hell to async hell in a way.

1402
01:09:30,568 --> 01:09:35,108
And in React we use, useEffect for
so many things where we shouldn't.

1403
01:09:35,438 --> 01:09:38,128
And  is just so wild
that like most of our.

1404
01:09:38,358 --> 01:09:43,088
Data interactions are asynchronous,
like in a way it's almost like if

1405
01:09:43,138 --> 01:09:48,252
react would not just give you a value
right away, but would give you like a

1406
01:09:48,282 --> 01:09:52,912
tuple of like either it's loading or
a value is like we're already halfway

1407
01:09:52,922 --> 01:09:55,542
there just in, in such a bad direction.

1408
01:09:55,872 --> 01:10:00,292
So I, uh, I think this is kind of a
subtle difference for people to understand

1409
01:10:00,312 --> 01:10:04,792
how much synchronous code execution
can simplify your app development.

1410
01:10:05,322 --> 01:10:08,712
But I think I'm, I'm preaching
to, to the choir here.

1411
01:10:09,842 --> 01:10:10,612
Yeah, sure.

1412
01:10:10,822 --> 01:10:11,242
Totally.

1413
01:10:11,242 --> 01:10:12,702
I mean, it helps with debugging.

1414
01:10:12,712 --> 01:10:15,432
Like if you're stepping over code,
whenever you're hitting like async

1415
01:10:15,492 --> 01:10:19,642
code, Chrome tries to do this thing
where it like will step over the await,

1416
01:10:19,642 --> 01:10:21,142
but it only works like half the time.

1417
01:10:21,152 --> 01:10:23,322
Like it's yeah, it's super annoying.

1418
01:10:23,452 --> 01:10:27,352
I do get this needed probably most of
the time, but it makes me sad when I just

1419
01:10:27,382 --> 01:10:29,602
see it like applied without any thought.

1420
01:10:29,897 --> 01:10:35,367
So after fulfilling your goal with
bringing Actual to the web through

1421
01:10:35,367 --> 01:10:39,847
absurd-sql, which I think is like
just to look back, like how much

1422
01:10:39,867 --> 01:10:42,817
pioneering work you've really
done to make this app happen.

1423
01:10:42,827 --> 01:10:45,077
Like you started this journey before.

1424
01:10:45,422 --> 01:10:47,442
The term local-first was there.

1425
01:10:47,472 --> 01:10:51,782
You've built one of the first credible
local-first apps and really invented

1426
01:10:51,912 --> 01:10:56,322
so many things along the way, like all
by yourself with the help from, from

1427
01:10:56,342 --> 01:11:00,212
some of your, your friends, but you,
like you figured out like how to use

1428
01:11:00,252 --> 01:11:05,672
SQLite in like even in a web context in
a reactive way, but then also made it

1429
01:11:05,702 --> 01:11:09,512
collaborative through your sync engine and
ultimately brought all of this to the web.

1430
01:11:09,912 --> 01:11:11,712
That is an impressive journey.

1431
01:11:11,807 --> 01:11:15,487
And I think you've been on the journey,
not just building all of this full

1432
01:11:15,487 --> 01:11:20,247
time, but you actually had like some,
some, some Actual full time job next

1433
01:11:20,247 --> 01:11:24,867
to that, which I think at some point
was just too much, which led you to,

1434
01:11:24,897 --> 01:11:29,307
to at some point, uh, hand over the
project to the broader community.

1435
01:11:29,607 --> 01:11:32,097
Can you share a little bit
more about that transition?

1436
01:11:32,357 --> 01:11:32,677
Sure.

1437
01:11:32,697 --> 01:11:33,297
Absolutely.

1438
01:11:33,297 --> 01:11:37,787
So I, there was about three or four
years when I was doing consulting work.

1439
01:11:37,837 --> 01:11:43,497
And that was during that time was around
when I started it in 2017, around 2019,

1440
01:11:43,517 --> 01:11:46,997
I think was the year that I full time
just didn't do any consulting work.

1441
01:11:46,997 --> 01:11:48,747
And I was like, I'm just going
to give this a try to see if I

1442
01:11:48,747 --> 01:11:49,817
can build this out as a business.

1443
01:11:50,082 --> 01:11:51,562
And it got moderately successful.

1444
01:11:51,562 --> 01:11:55,942
I think I got up to around 800
users, I think at, um, at the height.

1445
01:11:55,982 --> 01:11:59,562
And I was only charging 4 a month, which
again, I had no idea what I was doing.

1446
01:11:59,582 --> 01:12:03,652
Um, so too low, but like, you
know, like if you do the math,

1447
01:12:03,662 --> 01:12:05,112
that's like, that's not terrible.

1448
01:12:05,162 --> 01:12:06,842
A lot of people aren't
even able to get to that.

1449
01:12:07,292 --> 01:12:10,552
Like that much, like, but it wasn't even
close to becoming like a full time thing.

1450
01:12:10,572 --> 01:12:13,162
I honestly look, looking back and
all the experience that I have now

1451
01:12:13,162 --> 01:12:14,912
too, I, I, I could have built it out.

1452
01:12:14,912 --> 01:12:17,962
And especially if like Mint shut
down now, like there's so many

1453
01:12:17,972 --> 01:12:20,112
things that I've missed that I, I
probably could have built it out.

1454
01:12:20,112 --> 01:12:22,442
But, um, building a business
is really hard though.

1455
01:12:22,502 --> 01:12:27,242
And it's, I think I realized that I
also had a hard time finding people

1456
01:12:27,272 --> 01:12:29,382
to kind of like work with me on it.

1457
01:12:29,422 --> 01:12:33,892
Like I kept trying to, people were like
sort of interested in helping and which

1458
01:12:33,892 --> 01:12:37,325
made this kind of weird dynamic where
There is like four times when I tried to

1459
01:12:37,325 --> 01:12:41,995
allow contributors, uh, like without pay,
they just wanted to help, but without,

1460
01:12:42,155 --> 01:12:43,395
without paying them, it's just weird.

1461
01:12:43,425 --> 01:12:45,695
Like, they would do something for
a week and then be gone for two

1462
01:12:45,695 --> 01:12:47,225
weeks and not even like speak to me.

1463
01:12:47,225 --> 01:12:48,355
Like, I can't operate like that.

1464
01:12:48,475 --> 01:12:49,695
I'm operating a business here.

1465
01:12:49,785 --> 01:12:51,325
So I try to like, I try to.

1466
01:12:51,645 --> 01:12:56,275
Think about how I could hire a developer,
but I was so busy with everything else.

1467
01:12:56,275 --> 01:12:58,975
It just felt, I had no
idea how to filter through.

1468
01:12:58,975 --> 01:13:01,415
I don't know, should I use like,
you know, one of those like Upwork

1469
01:13:01,425 --> 01:13:05,195
or other things I had no, this just
seemed like so full of spam and, and

1470
01:13:05,215 --> 01:13:08,585
people who had no idea what they were
doing that it just was overwhelming.

1471
01:13:08,585 --> 01:13:10,055
I never figured out how to hire people.

1472
01:13:10,065 --> 01:13:11,435
And that was, that's my biggest regret.

1473
01:13:11,435 --> 01:13:14,015
Probably it's not figuring out how
to involve people in a way that

1474
01:13:14,015 --> 01:13:15,825
would help share some of the burden.

1475
01:13:15,825 --> 01:13:19,140
And so that, it, Would help it a little
bit, be a little bit more sustainable.

1476
01:13:19,310 --> 01:13:22,280
And I was just, I was ready to work with
people again, work at a company that

1477
01:13:22,280 --> 01:13:25,930
was like, I could grow from grow, how
to like, learn how to lead people and

1478
01:13:25,940 --> 01:13:29,250
learn how to, you know, lead teams and
do like product requirements and kind

1479
01:13:29,250 --> 01:13:30,810
of focus more on just what I'm good at.

1480
01:13:31,150 --> 01:13:35,260
And so ultimately, yeah, I just
wasn't, I wasn't serving Actual well,

1481
01:13:35,260 --> 01:13:37,060
I wasn't serving so that I did that.

1482
01:13:37,060 --> 01:13:38,580
Well, I did do that for a whole year.

1483
01:13:38,710 --> 01:13:42,840
which was, which was good, but
it, it, I wasn't growing enough

1484
01:13:42,850 --> 01:13:43,930
for it to be like a real thing.

1485
01:13:43,930 --> 01:13:48,010
And so I got a little burned out on
trying to make it like a full time thing.

1486
01:13:48,110 --> 01:13:51,120
Um, and so I was talking to a
friend who referred me to Stripe.

1487
01:13:51,120 --> 01:13:52,330
And so I got hired at Stripe.

1488
01:13:52,470 --> 01:13:55,320
I did try to work on it kind of
like in the mornings for like an

1489
01:13:55,330 --> 01:13:57,070
hour or two and then work at Stripe.

1490
01:13:57,080 --> 01:14:00,620
But obviously like that, just when I
got more and more involved in Stripe

1491
01:14:00,620 --> 01:14:04,040
and doing like really interesting things
there that kind of absorbed my time.

1492
01:14:04,040 --> 01:14:07,930
And so two years ago, I just was like,
I'm not serving Actual well, I'm not.

1493
01:14:08,030 --> 01:14:08,970
Spending enough time there.

1494
01:14:08,970 --> 01:14:09,870
I'm not serving stripe.

1495
01:14:09,890 --> 01:14:13,780
Well, cause I'm not spending enough time
or like, it was too tiring to like do

1496
01:14:13,780 --> 01:14:15,790
both and it wasn't serving like my family.

1497
01:14:15,820 --> 01:14:17,300
I wasn't serving my
own personal interests.

1498
01:14:17,300 --> 01:14:20,310
Well, either it's just like too
way, like way too overinvested.

1499
01:14:20,780 --> 01:14:23,370
So I decided to, to
not work on it anymore.

1500
01:14:23,370 --> 01:14:25,940
And so the choices was either sell
it or open source it, or just.

1501
01:14:26,160 --> 01:14:26,730
Shut it down.

1502
01:14:26,810 --> 01:14:28,440
I investigated selling it a little bit.

1503
01:14:28,440 --> 01:14:31,820
And this is like, again, some of the
downside of, of doing such a novel app is

1504
01:14:31,820 --> 01:14:33,450
that, um, I talked to a couple of people.

1505
01:14:33,450 --> 01:14:36,360
It's just so clear that nobody
had not much interest in it.

1506
01:14:36,370 --> 01:14:39,920
One, one, it just wasn't making enough
money, but two, like even starting

1507
01:14:39,920 --> 01:14:43,690
to explain how it worked, that
just, it's not a, you know, People

1508
01:14:43,690 --> 01:14:44,690
aren't interested in buying that.

1509
01:14:44,690 --> 01:14:47,840
Like they're interested in buying
like the super run of the mill apps

1510
01:14:47,850 --> 01:14:51,800
that they, that they can take and
grow from a thousand to 10, 000 users.

1511
01:14:51,930 --> 01:14:53,400
So this app was not that app at all.

1512
01:14:53,430 --> 01:14:56,770
So selling it was not in the cards
and with the money it was making, it

1513
01:14:56,770 --> 01:14:57,780
wasn't going to sell for that much.

1514
01:14:57,780 --> 01:15:00,210
So I decided to open source it and
it was a decent amount of work.

1515
01:15:00,220 --> 01:15:02,210
It was decent amount of work
for me for like the year after

1516
01:15:02,210 --> 01:15:03,410
that too, to help transition.

1517
01:15:03,840 --> 01:15:04,350
Doing it.

1518
01:15:04,350 --> 01:15:06,450
And sometimes I was like,
should I have just shut it down?

1519
01:15:06,620 --> 01:15:09,160
But I'm really glad I did because
now it's running entirely on its own.

1520
01:15:09,170 --> 01:15:12,010
Like I, I'm, I'm not even
on, on the discord anymore,

1521
01:15:12,010 --> 01:15:12,970
which I probably should be.

1522
01:15:13,040 --> 01:15:16,190
Honestly, I was starting to get
involved in it again in January.

1523
01:15:16,190 --> 01:15:18,870
And then like a work thing
happened and totally absorbed

1524
01:15:18,870 --> 01:15:20,060
my time for the last two months.

1525
01:15:20,280 --> 01:15:23,410
So I will, I want to start interacting
with the community again, but there

1526
01:15:23,450 --> 01:15:26,000
there's, they seem to be, they
seem to be running it really well.

1527
01:15:26,080 --> 01:15:27,950
And so it's, it's been great.

1528
01:15:27,960 --> 01:15:28,420
They've.

1529
01:15:28,540 --> 01:15:30,880
They've been taking it and
like fixing a lot of bugs.

1530
01:15:30,880 --> 01:15:35,190
And like it, there's definitely like a
power of, of, of open source at work.

1531
01:15:35,190 --> 01:15:36,470
It's, it's messier.

1532
01:15:36,470 --> 01:15:39,390
I honestly think that there's things,
parts of the app that don't look as good.

1533
01:15:39,470 --> 01:15:44,010
They're, they're not as clean, not as,
not as polished, not as thoughtful, but.

1534
01:15:44,355 --> 01:15:46,915
There's a lot of added functionality,
a lot of stuff that is good and a

1535
01:15:46,915 --> 01:15:48,435
lot of stuff that is improved too.

1536
01:15:48,535 --> 01:15:51,215
Thank you so much for, for
sharing this entire story.

1537
01:15:51,275 --> 01:15:55,725
I mean, I have massive respect to
you of how you've like navigated

1538
01:15:55,765 --> 01:15:59,505
this entire journey and takes a lot
of, I think there's not just on a

1539
01:15:59,515 --> 01:16:01,475
technological level, there's like doubt.

1540
01:16:01,495 --> 01:16:03,145
It's just like, is this possible?

1541
01:16:03,155 --> 01:16:04,145
Does this make sense?

1542
01:16:04,175 --> 01:16:07,515
But you're like building a
product, you're building a company.

1543
01:16:07,755 --> 01:16:11,755
So there's a lot of uncertainty if you're,
if you're not in a full time job where

1544
01:16:11,755 --> 01:16:15,305
someone else Takes care of the things
and you have your responsibilities.

1545
01:16:15,325 --> 01:16:17,235
Here's like, everything is uncertain.

1546
01:16:17,655 --> 01:16:21,645
And so navigating that while
also like having a family and

1547
01:16:21,665 --> 01:16:24,785
other responsibilities, that's,
that's a lot of commitment.

1548
01:16:25,085 --> 01:16:29,455
So I have a lot of respect for
like, Not just the decisions you've

1549
01:16:29,455 --> 01:16:32,768
made, but like also , how thoughtful
you went about those transitions.

1550
01:16:32,778 --> 01:16:37,138
Like how much you, like you said, like
you owe the community, like an update.

1551
01:16:37,138 --> 01:16:39,938
I don't think you owe the community
anything you've given the community

1552
01:16:39,938 --> 01:16:44,718
a lot, but I think all of your
contributions are really well received.

1553
01:16:44,728 --> 01:16:47,408
And, and I think highly
valued by a lot of people.

1554
01:16:47,818 --> 01:16:52,848
And so I think even if Actual now did
not work out as like that, uh, That

1555
01:16:52,858 --> 01:16:55,408
billion dollar startup by itself.

1556
01:16:55,458 --> 01:16:59,488
Uh, I think it will actually influence
a lot of those and who knows, maybe

1557
01:16:59,498 --> 01:17:04,018
you take another stab at the same thing
at another thing in a similar way.

1558
01:17:04,348 --> 01:17:08,668
And I can't wait to hear sort of what
you'll be innovating on at that point.

1559
01:17:08,738 --> 01:17:11,948
You've mentioned that every four
years of like, you're, you're staring

1560
01:17:11,958 --> 01:17:14,798
at a problem where you feel like,
ah, there has to be a better way.

1561
01:17:15,088 --> 01:17:19,558
So can't wait to see which sort of absurd
things you might build in the future.

1562
01:17:20,128 --> 01:17:24,418
So you mentioned that right now
you're at Stripe and I think at Stripe

1563
01:17:24,428 --> 01:17:29,978
you're working on design systems
and like more UI related things.

1564
01:17:30,028 --> 01:17:33,408
So which sort of things are
you, are you working at Stripe?

1565
01:17:33,568 --> 01:17:34,018
Yes.

1566
01:17:34,028 --> 01:17:36,298
So I work on our design system.

1567
01:17:36,328 --> 01:17:37,108
It's called SAIL.

1568
01:17:37,378 --> 01:17:41,558
And so SAIL recently, it used to just be
a single team called the design system

1569
01:17:41,588 --> 01:17:45,068
team, but now there's been changed into
like a larger org, which is really cool.

1570
01:17:45,228 --> 01:17:49,538
Exciting in some ways, it kind of, you
know, messed up our team a little bit.

1571
01:17:49,548 --> 01:17:52,068
Some, some people got moved
around, which was, we had to kind

1572
01:17:52,068 --> 01:17:53,728
of work through that, but we did.

1573
01:17:53,738 --> 01:17:56,178
And like, there's a wholesale
org now, which is great.

1574
01:17:56,278 --> 01:17:58,318
So it's like a, a bigger investment.

1575
01:17:58,388 --> 01:18:00,098
SAIL itself is becoming this thing.

1576
01:18:00,098 --> 01:18:02,458
That's actually more than
just a UI design system.

1577
01:18:02,458 --> 01:18:04,508
We're also starting to integrate other.

1578
01:18:04,518 --> 01:18:08,018
Concerns that you always hit when you
start doing front-end work, which is like,

1579
01:18:08,018 --> 01:18:09,398
how do you internationalize something?

1580
01:18:09,408 --> 01:18:11,408
How do you, how do you do observability?

1581
01:18:11,478 --> 01:18:15,828
How do you do like GraphQL queries or how
do you just fetch data like in general?

1582
01:18:15,828 --> 01:18:18,468
And so SAIL is becoming like
a bigger platform overall.

1583
01:18:18,548 --> 01:18:21,868
I am focusing more on the, on the
UI design system part, but I'm

1584
01:18:21,868 --> 01:18:24,648
also collaborating a lot with the
others as well to sort of integrate

1585
01:18:24,648 --> 01:18:26,078
this into a cohesive platform.

1586
01:18:26,248 --> 01:18:27,578
Internally, we have a, a.

1587
01:18:27,668 --> 01:18:30,678
A whole system for like
variants and tokens.

1588
01:18:30,688 --> 01:18:33,378
And we have a, like a
view and a, and a CSS API.

1589
01:18:33,468 --> 01:18:36,098
And it's something that I don't
think we've talked a ton about.

1590
01:18:36,098 --> 01:18:38,568
I think we really should talk
about it more, um, openly.

1591
01:18:38,878 --> 01:18:40,918
And cause it's, it's, it's pretty neat.

1592
01:18:40,978 --> 01:18:42,008
We try to.

1593
01:18:42,328 --> 01:18:44,258
Use third party libraries when we can.

1594
01:18:44,258 --> 01:18:46,138
We actually leverage a lot of React Aria.

1595
01:18:46,178 --> 01:18:48,298
So a lot of our components
use, use React Aria.

1596
01:18:48,388 --> 01:18:48,738
Yeah.

1597
01:18:48,918 --> 01:18:51,908
We actually use the, also the like
lower level hooks API, which is like in

1598
01:18:51,908 --> 01:18:55,578
some ways really cumbersome to use, but
like, that's kind of intentional, like,

1599
01:18:55,578 --> 01:18:59,148
because it gives you like direct access
to the entire way that the things work.

1600
01:18:59,478 --> 01:19:02,528
They now have a higher level API, like
the React Aria components API, which

1601
01:19:02,528 --> 01:19:03,848
is all like really, really great.

1602
01:19:03,948 --> 01:19:07,168
But we do things with React Aria
that I don't think anybody else does.

1603
01:19:07,168 --> 01:19:10,578
I think that like, there's like
classes that we import and use.

1604
01:19:11,053 --> 01:19:13,433
That, like, we've talked to the
ReactARIA team and they're like,

1605
01:19:13,563 --> 01:19:15,973
nobody else has imported that class
and, like, used that directly.

1606
01:19:16,243 --> 01:19:19,623
There's, like, a list, like, a list
collection class that is, that it uses.

1607
01:19:19,673 --> 01:19:21,933
And we import that and create
our own collection system.

1608
01:19:22,253 --> 01:19:25,543
And, like, cause, like, Stripe
has pretty specific needs, right?

1609
01:19:25,543 --> 01:19:28,343
And so what I love about ReactARIA
is that it's taken this, like,

1610
01:19:28,393 --> 01:19:31,703
unabashed approach to being cumbersome.

1611
01:19:31,704 --> 01:19:33,113
And it's, like, kind of intentional.

1612
01:19:33,123 --> 01:19:36,333
Like, if you look at the docs for some
of the hooks, like, it's a lot of code

1613
01:19:36,343 --> 01:19:43,003
to get a, Menu working, but because it
is that openly exposed, you have like

1614
01:19:43,133 --> 01:19:45,033
total control over how everything works.

1615
01:19:45,163 --> 01:19:47,863
So it's really allowed us to go
in and really, really wire things

1616
01:19:47,863 --> 01:19:48,923
up the way that we want to.

1617
01:19:49,133 --> 01:19:49,963
So yeah, it's, it's great.

1618
01:19:49,963 --> 01:19:51,123
It's a fantastic library.

1619
01:19:51,123 --> 01:19:52,073
It's super, super good.

1620
01:19:52,393 --> 01:19:55,278
Um, lots of good accessibility
that, that you get from that.

1621
01:19:55,428 --> 01:19:57,548
But that's our component system, right?

1622
01:19:57,808 --> 01:20:01,078
We still have a lower level,
like view and CSS APIs.

1623
01:20:01,078 --> 01:20:04,748
And so how you do CSS, how you do tokens,
how you, how you do variants, how do

1624
01:20:04,748 --> 01:20:09,648
you make sure that you, when you, uh,
create a new component, that everything

1625
01:20:09,648 --> 01:20:15,103
is wired through such that like, The, um,
like if you pass styles to it, but you

1626
01:20:15,113 --> 01:20:19,513
also internally want to style the, the
same div, like, like the same top level

1627
01:20:19,513 --> 01:20:23,543
div that you take the styles from your
props and you apply styles to that div.

1628
01:20:23,763 --> 01:20:26,373
But like in that component,
you also want to apply styles.

1629
01:20:27,218 --> 01:20:27,678
Do that div.

1630
01:20:27,708 --> 01:20:29,468
How do you, how do you
combine those styles?

1631
01:20:29,658 --> 01:20:33,878
I guess you could like spread on the
styles that you get from the props, right?

1632
01:20:33,948 --> 01:20:35,928
And then spread on your
own styles as well.

1633
01:20:35,928 --> 01:20:38,458
But then you get into like
precedence problems, like which

1634
01:20:38,468 --> 01:20:39,838
order do you spread those on?

1635
01:20:40,178 --> 01:20:44,418
So we have a whole way of like creating
a component, a whole pattern for

1636
01:20:44,708 --> 01:20:46,238
taking the props that you get and.

1637
01:20:46,508 --> 01:20:50,418
And spreading, and like, spreading
those props onto some internal element.

1638
01:20:50,838 --> 01:20:54,638
And we have a very strict precedence
order for how styles and how

1639
01:20:54,658 --> 01:20:58,528
variants and, and how other things
like that get merged together, um,

1640
01:20:58,528 --> 01:20:59,948
in a way that's a very intuitive.

1641
01:21:00,218 --> 01:21:01,778
Um, and it's all very, very consistent.

1642
01:21:01,788 --> 01:21:03,838
So we have like a, an
API called create view.

1643
01:21:04,068 --> 01:21:07,228
So that's how you create a new component
that interacts with our whole system.

1644
01:21:07,438 --> 01:21:11,058
When you call create view, you get back
an exact same React component, just like

1645
01:21:11,058 --> 01:21:12,528
you would call it forward ref, right?

1646
01:21:12,538 --> 01:21:16,528
Like it's, it's taking a component, but
it's giving it additional capabilities.

1647
01:21:16,538 --> 01:21:18,668
And the, the, the two things, like
there's a couple of things that

1648
01:21:18,668 --> 01:21:20,518
it gets, it gets like a CSS prop.

1649
01:21:20,878 --> 01:21:25,728
So we, we pass CSS by, by saying
button CSS equals, and then an object.

1650
01:21:26,028 --> 01:21:29,268
And in that object, we have a
whole like little system that

1651
01:21:29,268 --> 01:21:30,408
is wired up with our tokens.

1652
01:21:30,409 --> 01:21:33,818
And so you can say margin
is, you know, small.

1653
01:21:34,073 --> 01:21:35,573
And small gets resolved to a token.

1654
01:21:35,583 --> 01:21:39,213
We also do like things where you
can, um, say like small is eight

1655
01:21:39,243 --> 01:21:43,313
pixels and we have an ESLint rule
that automatically knows that for

1656
01:21:43,313 --> 01:21:48,093
the current theme and for the current
system wired up, small is eight pixels.

1657
01:21:48,303 --> 01:21:52,503
So you've hard coded a token here and it
automatically actually fixes that for you.

1658
01:21:52,503 --> 01:21:55,273
So it's changes eight pixels to small.

1659
01:21:55,373 --> 01:21:58,373
So we, we try to let developers build.

1660
01:21:58,568 --> 01:21:59,598
Like not get in their way.

1661
01:21:59,598 --> 01:22:02,598
Basically, we really want them to
sort of, we have the same, like fall

1662
01:22:02,598 --> 01:22:05,948
into the pit of success where you
should go off and build a product

1663
01:22:05,978 --> 01:22:07,128
the way that you want to build it.

1664
01:22:07,408 --> 01:22:11,978
And sale should meet you where you
are there and like, let you build

1665
01:22:11,978 --> 01:22:15,498
components that end up getting wired
up the way that we want them wired up.

1666
01:22:15,808 --> 01:22:18,608
But just because that's the
The way that feels natural.

1667
01:22:18,618 --> 01:22:22,278
Like you shouldn't have to feel like
you have to like go against our system

1668
01:22:22,278 --> 01:22:26,278
and like begrudgingly use it to like
be conformed to Stripe's design system.

1669
01:22:26,278 --> 01:22:26,538
Right.

1670
01:22:26,718 --> 01:22:28,298
We want you to be like happy using it.

1671
01:22:28,498 --> 01:22:32,048
And so a lot of our work goes into
like making the APIs feel good.

1672
01:22:32,198 --> 01:22:32,428
Yeah.

1673
01:22:32,428 --> 01:22:34,558
There, there's a lot of stuff here
that I think we should talk about more.

1674
01:22:34,558 --> 01:22:37,808
Like we have our own variant system
and our whole, like how tokens are

1675
01:22:37,808 --> 01:22:39,188
wired up is really, really interesting.

1676
01:22:39,208 --> 01:22:41,028
Cause one thing about Stripe.

1677
01:22:41,858 --> 01:22:43,878
It's a very, very broad company.

1678
01:22:43,878 --> 01:22:45,218
We have a lot of different products.

1679
01:22:45,218 --> 01:22:46,078
We have the dashboard.

1680
01:22:46,078 --> 01:22:46,958
We have connect.

1681
01:22:47,128 --> 01:22:50,498
We have like people taking
pieces of functionality and

1682
01:22:50,658 --> 01:22:52,078
embedding it in their own website.

1683
01:22:52,098 --> 01:22:54,978
And then they're theming that
content, but it's like exactly the

1684
01:22:54,978 --> 01:22:57,448
same little widget that you would
take from the dashboard, right?

1685
01:22:57,458 --> 01:22:59,588
It's like a payments list that
you would take from the dashboard

1686
01:22:59,588 --> 01:23:01,428
and embed into your own page.

1687
01:23:01,628 --> 01:23:03,318
And then we also have like custom hosted.

1688
01:23:04,053 --> 01:23:06,593
Invoices and a bunch of other
little like third party things.

1689
01:23:06,593 --> 01:23:09,283
Then like checkout and like elements
as a whole, another thing as well.

1690
01:23:09,503 --> 01:23:12,713
We're trying to bring all of that
together into us and to use the same

1691
01:23:12,713 --> 01:23:17,723
design system that can be themed and like
leverage and customized for each surface.

1692
01:23:17,863 --> 01:23:19,823
And so it's really hard problems.

1693
01:23:20,078 --> 01:23:22,708
Like honestly, and it's, it's, it's
a lot of work, but it's really fun.

1694
01:23:22,868 --> 01:23:24,468
That, that sounds fascinating.

1695
01:23:24,518 --> 01:23:29,448
Uh, I feel like I want to learn a lot
more about how to do design systems.

1696
01:23:29,498 --> 01:23:33,408
I should also educate myself more of
like, should I use a design system

1697
01:23:33,418 --> 01:23:35,028
even for a smaller scale products?

1698
01:23:35,038 --> 01:23:37,958
For example, would you now
looking back, would you've used

1699
01:23:37,958 --> 01:23:39,528
a design system for Actual?

1700
01:23:39,808 --> 01:23:43,578
Is this like, rather like, does it
solve an organizational problem or does

1701
01:23:43,578 --> 01:23:47,638
it really help even on a smaller scale
when fewer engineers are involved?

1702
01:23:48,003 --> 01:23:52,473
But I most certainly love the design
principle of like the pit of success

1703
01:23:52,533 --> 01:23:54,353
that can't go wrong with that.

1704
01:23:54,633 --> 01:23:55,403
Yeah, it's great.

1705
01:23:55,413 --> 01:23:58,503
It's, it's been a good, like a
good way to frame things for sure.

1706
01:23:58,703 --> 01:24:03,253
So you're building with React,
uh, Stripe it seems, and you've

1707
01:24:03,263 --> 01:24:05,533
also used React for Actual.

1708
01:24:05,593 --> 01:24:09,633
React has changed quite a bit over
the years, so I'm curious to hear

1709
01:24:09,633 --> 01:24:13,983
whether you have any opinions on
where React has gone over the last

1710
01:24:13,983 --> 01:24:15,213
few years and where it's going.

1711
01:24:15,518 --> 01:24:18,688
I don't have super strong opinions
only because I feel like I

1712
01:24:18,688 --> 01:24:20,318
can't back them up right now.

1713
01:24:20,338 --> 01:24:23,148
I haven't given it the time to
sit down and really like write

1714
01:24:23,148 --> 01:24:27,108
out like a thorough argument for
why I should feel a certain way.

1715
01:24:27,138 --> 01:24:29,638
I think it's been hard for just
in general, like everybody.

1716
01:24:29,698 --> 01:24:32,918
I respect the team in a
lot of ways because I think

1717
01:24:32,918 --> 01:24:34,048
they have a high bar, right?

1718
01:24:34,078 --> 01:24:35,148
Unlike many other teams.

1719
01:24:35,528 --> 01:24:38,178
Developers, generally speaking,
I feel like we don't have a high

1720
01:24:38,178 --> 01:24:40,938
enough bar and I feel like the
React team has a high bar and like,

1721
01:24:41,098 --> 01:24:43,468
ultimately, I do respect them for that.

1722
01:24:43,528 --> 01:24:47,458
I do think there's, there's been
times when it's just like very heavy

1723
01:24:47,458 --> 01:24:50,888
investment and very complicated
things when it feels like there's

1724
01:24:50,928 --> 01:24:52,838
lower hanging fruit, which are like.

1725
01:24:53,433 --> 01:24:54,823
Man, this really sucks to have to do this.

1726
01:24:54,833 --> 01:24:58,233
Like every single time sucks to have
to use forward ref every single time.

1727
01:24:58,233 --> 01:24:59,563
Why can't just ref be a freaking prop?

1728
01:24:59,823 --> 01:25:02,443
And like, I know that there's like
backwards compatibility problems and

1729
01:25:02,443 --> 01:25:05,953
like all sorts of reasons, but it, when
like two years are spent on this really

1730
01:25:05,953 --> 01:25:09,213
complicated thing, and like, we're still
hitting these problems on a day to day

1731
01:25:09,213 --> 01:25:13,097
development thing, sometimes there's a
little dissonance there that can be a

1732
01:25:13,097 --> 01:25:16,703
little bit like, ah, you know, we also
don't pay for react, it's almost like.

1733
01:25:17,113 --> 01:25:18,543
I don't know, like we're
getting it for free.

1734
01:25:18,593 --> 01:25:20,563
It's up to them to ultimately decide.

1735
01:25:20,573 --> 01:25:22,583
We're not, they're not
forcing us to use React.

1736
01:25:22,953 --> 01:25:24,823
There's a little bit
of a lock in for sure.

1737
01:25:24,823 --> 01:25:27,793
Like I don't know how Stripe would
possibly move away from React, but

1738
01:25:27,803 --> 01:25:30,813
ultimately like the members of the
team, I respect really, really well.

1739
01:25:30,813 --> 01:25:33,103
And I'm not going to say
anything like super bad about it.

1740
01:25:33,208 --> 01:25:35,518
I think react server components
is really interesting.

1741
01:25:35,658 --> 01:25:38,428
Again, it's like, sure, like the
local-first stuff might be able to

1742
01:25:38,438 --> 01:25:39,678
be cleaned up a little bit with it.

1743
01:25:39,678 --> 01:25:42,588
Like, there's kind of some interesting
things there where like, maybe you

1744
01:25:42,588 --> 01:25:44,658
could like run components on the server.

1745
01:25:44,658 --> 01:25:47,518
Like, even if it's just like a
backend web worker process, but

1746
01:25:47,518 --> 01:25:49,738
the wins are much less for sure.

1747
01:25:49,788 --> 01:25:50,158
Like.

1748
01:25:50,683 --> 01:25:51,603
Everything's already local.

1749
01:25:51,603 --> 01:25:53,373
I don't care if it goes
through a WebSocket message.

1750
01:25:53,373 --> 01:25:55,303
Like I can, like, you
can embed SQL queries.

1751
01:25:55,393 --> 01:25:57,093
Like we're embedding SQL queries already.

1752
01:25:57,093 --> 01:25:58,723
We don't need React
Server components for it.

1753
01:25:58,753 --> 01:26:01,673
So it's less convincing to
me if you're already doing it

1754
01:26:01,683 --> 01:26:02,903
the way that we're doing it.

1755
01:26:02,943 --> 01:26:04,993
But for Stripe, like React
Server components could be

1756
01:26:04,993 --> 01:26:06,253
potentially pretty compelling.

1757
01:26:06,343 --> 01:26:10,398
But it's still, again, overall,
like, Man, I just want support

1758
01:26:10,398 --> 01:26:11,888
for exit animations, right?

1759
01:26:11,888 --> 01:26:15,358
Like I want the ability to not have
to wrap something in animate presence

1760
01:26:15,408 --> 01:26:19,678
to just freaking get something to
like maintain it in a dom while it

1761
01:26:19,798 --> 01:26:21,378
animates out and then unmount it.

1762
01:26:21,388 --> 01:26:23,048
Like React still doesn't
allow you to do that.

1763
01:26:23,068 --> 01:26:24,958
Like exit animations are
a huge pain in the butt.

1764
01:26:25,018 --> 01:26:25,528
Yeah,

1765
01:26:25,728 --> 01:26:26,788
I, I fully agree.

1766
01:26:26,788 --> 01:26:30,748
And I think the React server components,
what you've mentioned, I think the,

1767
01:26:30,758 --> 01:26:34,608
the way from my current perspective,
how it would most meaningfully help

1768
01:26:34,668 --> 01:26:36,498
in a local-first context as well.

1769
01:26:36,818 --> 01:26:41,578
is on the initial upload experience,
since that is sometimes a bit of

1770
01:26:41,578 --> 01:26:45,668
like the cost that you're paying that
you're, you're kind of with local-first,

1771
01:26:45,688 --> 01:26:47,108
you say like no more spinners.

1772
01:26:47,278 --> 01:26:51,888
Well, sort of, since you have like one
front loaded, typically larger spinner.

1773
01:26:52,188 --> 01:26:55,738
And I think this can be addressed also
with react server components, where

1774
01:26:55,738 --> 01:26:57,618
you get sort of an hybrid approach.

1775
01:26:57,948 --> 01:27:03,208
Where you get much more quickly reactive
initial version of the app that then

1776
01:27:03,258 --> 01:27:05,118
upgrades itself to be local-first.

1777
01:27:05,138 --> 01:27:09,828
So that, that's my take on react
server components, but the other pain

1778
01:27:09,828 --> 01:27:14,088
point that you've pointed at with
like exit animations, et cetera, and

1779
01:27:14,088 --> 01:27:18,188
where this is kind of where we are
now paying the cost for virtual DOM,

1780
01:27:18,198 --> 01:27:20,458
which constantly updates everything.

1781
01:27:20,508 --> 01:27:24,598
I've had actually some, some
interesting results now with just

1782
01:27:24,598 --> 01:27:26,028
using web components for that.

1783
01:27:26,458 --> 01:27:30,298
Where it's a bit funny since like, in
some regards, it feels like going a bit

1784
01:27:30,308 --> 01:27:36,738
back in history where your DOM is more
static in a way, but this way you actually

1785
01:27:36,738 --> 01:27:41,358
also can think a little bit differently
about like, for example, animations.

1786
01:27:41,438 --> 01:27:47,718
So, and this is where I feel also on
the general theme of learning more from

1787
01:27:47,748 --> 01:27:52,708
other native programming environments,
other native platforms, where for

1788
01:27:52,708 --> 01:27:54,858
example, on iOS, when you have like.

1789
01:27:55,088 --> 01:27:58,178
Things like a collection view or
a table view controller where you

1790
01:27:58,178 --> 01:28:02,948
have those cells and they're like,
they don't constantly cycle out.

1791
01:28:03,008 --> 01:28:07,258
They cycle out if you're like, if they
leave your view, but based on like

1792
01:28:07,268 --> 01:28:10,298
entering the view, leaving the view,
this is how we can do animations.

1793
01:28:10,708 --> 01:28:15,373
And with an approach like web
components, you can actually do that by.

1794
01:28:15,523 --> 01:28:20,763
Using the native aspects of the web, like
native to the platform web, much more.

1795
01:28:20,763 --> 01:28:23,943
And I feel we're now fighting
a bit of an uphill battle to

1796
01:28:23,973 --> 01:28:25,593
get those benefits from React.

1797
01:28:25,753 --> 01:28:26,133
Agreed.

1798
01:28:26,173 --> 01:28:26,563
Yeah.

1799
01:28:26,653 --> 01:28:27,443
It's interesting.

1800
01:28:27,493 --> 01:28:29,173
There's a decent escape
hatch there, right?

1801
01:28:29,173 --> 01:28:33,023
Where you can mount a div and then in
an effect, like you can do whatever you

1802
01:28:33,023 --> 01:28:35,683
want there, which is, which is nice,
especially for a company like Stripe,

1803
01:28:35,693 --> 01:28:37,733
because we can sort of like opt out when.

1804
01:28:38,113 --> 01:28:42,143
Needed, but something does like, there are
certain things that are like systematic.

1805
01:28:42,143 --> 01:28:44,823
And, you know, as something, somebody
who works, works on a design system,

1806
01:28:44,823 --> 01:28:47,903
seeing like animations are not something
that you can just like opt out for.

1807
01:28:48,193 --> 01:28:48,523
Right.

1808
01:28:48,563 --> 01:28:51,583
That's something that you apply on
like almost every single, I don't

1809
01:28:51,583 --> 01:28:55,473
know, like a 10th of the elements
on the page, which is a lot.

1810
01:28:55,968 --> 01:28:56,948
have animations.

1811
01:28:56,958 --> 01:28:58,788
You can't just like opt
out when you want to.

1812
01:28:58,928 --> 01:29:00,078
So it's, it's hard.

1813
01:29:00,118 --> 01:29:00,358
Yeah.

1814
01:29:00,368 --> 01:29:02,458
I would love to see
some improvements there.

1815
01:29:02,508 --> 01:29:05,778
Just recently on my blog, I have quick
and dirty type thing, but like I don't

1816
01:29:05,778 --> 01:29:08,368
use re I use react, I use, um, Remix.

1817
01:29:08,368 --> 01:29:10,298
And so I use reactor on the backend,
but I don't use it on the front.

1818
01:29:10,298 --> 01:29:11,918
It just feels so fast and nice.

1819
01:29:11,928 --> 01:29:12,518
It's just a.

1820
01:29:12,748 --> 01:29:16,618
Just don't do it just content, but like I
am starting to add more interactive parts.

1821
01:29:16,638 --> 01:29:19,748
And so I'm sort of playing around with
like, well, okay, what do I do here?

1822
01:29:20,198 --> 01:29:22,248
Do I need to load in the full react?

1823
01:29:22,248 --> 01:29:25,088
Maybe this is where like partial
hydration could be interesting.

1824
01:29:25,148 --> 01:29:26,988
Uh, but like, I just don't, it's my blog.

1825
01:29:26,998 --> 01:29:30,428
I don't need partial hydration on
my frigging blog, but I do want

1826
01:29:30,438 --> 01:29:33,488
to be able to like, um, there's
like, I have like live interactive

1827
01:29:33,488 --> 01:29:35,118
demos and I want to be able to.

1828
01:29:35,373 --> 01:29:36,823
Have a view source button.

1829
01:29:36,913 --> 01:29:40,293
And when you click that, it opens up
this, like, it basically like zooms

1830
01:29:40,293 --> 01:29:44,863
into the interactive demo to where it
shows the demo, like, like the demo is

1831
01:29:44,863 --> 01:29:48,483
still running, but it's running in a
dialogue and the demo is run on the left.

1832
01:29:48,483 --> 01:29:51,473
And then on the right is the code
for the demo and then all sorts

1833
01:29:51,473 --> 01:29:53,073
of other controls for the demo.

1834
01:29:53,083 --> 01:29:57,263
And the way that I implemented that
was I literally transport that real

1835
01:29:57,263 --> 01:30:01,473
Dom node, I take the Dom node instance
itself from the, like the whole

1836
01:30:01,473 --> 01:30:04,033
demo Dom node, and I, I move it.

1837
01:30:04,493 --> 01:30:08,183
I create the dialogue and I put
the code on the right, and I moved

1838
01:30:08,183 --> 01:30:10,283
the DOM node into the dialogue.

1839
01:30:10,513 --> 01:30:12,223
And the demo just keeps
on running just fine.

1840
01:30:12,233 --> 01:30:14,293
Like it, it moves into the dialogue.

1841
01:30:14,353 --> 01:30:16,233
And I did that in like 10 lines of code.

1842
01:30:16,503 --> 01:30:20,213
And there is a certain amount of
like, Oh man, it feels so good to be,

1843
01:30:20,263 --> 01:30:21,863
yeah, I just have access to like this.

1844
01:30:22,323 --> 01:30:26,673
Dangerous, dangerous API APIs, but like
I, to do this in react would have felt

1845
01:30:26,683 --> 01:30:29,953
really backwards, like bending backwards
and doing all sorts of weird things.

1846
01:30:30,213 --> 01:30:32,393
And then you can like, and you
close it and it moves it back and

1847
01:30:32,393 --> 01:30:33,463
it just keeps on running just fine.

1848
01:30:33,493 --> 01:30:33,773
Right.

1849
01:30:33,833 --> 01:30:36,743
And I, I don't know, to, to, to think
about it in the react mental model,

1850
01:30:36,743 --> 01:30:37,553
I would have been like, Oh, okay.

1851
01:30:37,553 --> 01:30:40,123
I need to create like a portal,
create a bunch of components and

1852
01:30:40,123 --> 01:30:44,203
like wire them all, I don't like
just saying like dom node, remove

1853
01:30:44,203 --> 01:30:46,183
node, and then like demo dialogue.

1854
01:30:46,363 --> 01:30:46,863
append.

1855
01:30:47,033 --> 01:30:48,873
Uh, I completely agree.

1856
01:30:48,953 --> 01:30:51,023
I feel like we've been now over the last.

1857
01:30:51,608 --> 01:30:53,598
React has been now around for 10 years.

1858
01:30:53,598 --> 01:30:59,288
I feel like over the last 10 years,
we've been really leaning to use the

1859
01:30:59,288 --> 01:31:01,878
React hammer for like every little thing.

1860
01:31:01,928 --> 01:31:06,478
And I think that brought a whole lot
of benefits to us, but I think we

1861
01:31:06,528 --> 01:31:08,268
don't question it really anymore.

1862
01:31:08,278 --> 01:31:11,338
And like, we, I think this is
like the only way to go about it.

1863
01:31:11,768 --> 01:31:14,878
And the web has since then really evolved.

1864
01:31:14,898 --> 01:31:17,838
Like we've gotten a lot more,
the primitives in the web

1865
01:31:18,138 --> 01:31:20,408
itself have gotten a lot better.

1866
01:31:20,798 --> 01:31:25,028
And so I think web components
have also come quite a long way.

1867
01:31:25,028 --> 01:31:29,548
They haven't seen as much investment
in terms of building a nice ecosystem

1868
01:31:29,548 --> 01:31:34,788
around it, but I think it's the closest
we got to a native feel in the web.

1869
01:31:35,223 --> 01:31:40,253
And, uh, I think sometimes you'd be
surprised how simple things can be if you

1870
01:31:40,253 --> 01:31:42,413
embrace those primitives more directly.

1871
01:31:42,613 --> 01:31:45,893
One little anecdote I want to share
there, a friend of mine, Cheng Lu,

1872
01:31:45,903 --> 01:31:50,933
who's I think also worked on the React
team for a while, he did a couple of

1873
01:31:50,943 --> 01:31:55,073
really fascinating demos and I think he
launched some of them on, on Hacker News.

1874
01:31:55,423 --> 01:32:00,213
Where he, for example, built a photo
gallery and a Hacker News clone as well.

1875
01:32:00,708 --> 01:32:05,778
And so when you try those apps, those are
the smoothest animations and the smoothest

1876
01:32:05,778 --> 01:32:07,888
feel you've ever seen on the web.

1877
01:32:07,948 --> 01:32:10,028
And you kind of feel like,
what is going on here?

1878
01:32:10,028 --> 01:32:14,268
Is this like rendered to web
GPU using Wasm or something?

1879
01:32:14,558 --> 01:32:18,158
And turns out this is all
just normal DOM, normal CSS.

1880
01:32:18,208 --> 01:32:21,828
He's, he's built this like
very simple, elegant system.

1881
01:32:21,828 --> 01:32:26,098
I think the photo gallery is like in
a hundred or 200 lines of JavaScript,

1882
01:32:26,118 --> 01:32:27,828
like no dependencies, nothing.

1883
01:32:28,283 --> 01:32:30,143
And this feels like a total native app.

1884
01:32:30,143 --> 01:32:34,123
So this really reminded me of like
how capable the web really is.

1885
01:32:34,413 --> 01:32:38,903
If we use it directly without
layering too many things in between.

1886
01:32:39,023 --> 01:32:40,923
I have high hopes for, for the web.

1887
01:32:41,083 --> 01:32:43,293
If we set up, set ourselves a high bar.

1888
01:32:43,413 --> 01:32:43,813
Same.

1889
01:32:43,833 --> 01:32:44,093
Yeah.

1890
01:32:44,093 --> 01:32:47,833
And I think React needs to figure out
how to like move with the web without

1891
01:32:48,353 --> 01:32:51,343
breaking backwards compatibility,
find the right trade off there.

1892
01:32:51,423 --> 01:32:54,333
Like, I guess even the like web
component support type stuff in that

1893
01:32:54,333 --> 01:32:55,983
has been like super long in the making.

1894
01:32:56,043 --> 01:32:57,083
So I think that's something that.

1895
01:32:57,303 --> 01:32:58,493
Could be improved, but

1896
01:32:58,603 --> 01:33:02,853
hey, James, you've been very generous
with your time and walking us

1897
01:33:02,853 --> 01:33:06,933
through like your entire journey of
the, the various chapters of Actual

1898
01:33:06,933 --> 01:33:11,263
from Electra and the mobile apps,
the web, absurd sequel, et cetera.

1899
01:33:11,313 --> 01:33:13,793
So thank you so much for your time.

1900
01:33:14,203 --> 01:33:17,543
If there's anything you want to share
with the audience here, any sort

1901
01:33:17,543 --> 01:33:18,883
of shout out, now's a good time.

1902
01:33:19,323 --> 01:33:19,663
Uh, cool.

1903
01:33:19,663 --> 01:33:19,913
Yeah.

1904
01:33:19,913 --> 01:33:20,923
Thank you for having me on.

1905
01:33:20,933 --> 01:33:21,883
This was amazing.

1906
01:33:21,953 --> 01:33:25,613
I mean, honestly, shout out to
Martin Kleppmann, PVH, Peter.

1907
01:33:25,673 --> 01:33:28,433
I found them through
their local-first essay.

1908
01:33:28,433 --> 01:33:33,033
And I, I came and give like a, gave like
a workshop at their research studio and

1909
01:33:33,033 --> 01:33:34,413
like talk to them a bunch since then.

1910
01:33:34,413 --> 01:33:36,723
And they've always been very encouraging
throughout the whole process.

1911
01:33:36,773 --> 01:33:38,953
So super fun to talk to
them about all this stuff.

1912
01:33:38,954 --> 01:33:39,523
Awesome.

1913
01:33:39,523 --> 01:33:42,263
Thank you so much for your time
and coming on the show today.

1914
01:33:42,463 --> 01:33:43,223
Thank you so much.

1915
01:33:43,517 --> 01:33:46,057
Thank you for listening to
the localfirst.fm podcast.

1916
01:33:46,297 --> 01:33:50,027
If you've enjoyed this episode and haven't
done so already, please subscribe and

1917
01:33:50,027 --> 01:33:51,787
leave a review wherever you're listening.

1918
01:33:52,157 --> 01:33:53,587
Please also tell your friends about it.

1919
01:33:53,597 --> 01:33:57,167
If you think they could be interested
in local-first, if you have feedback,

1920
01:33:57,207 --> 01:34:00,947
questions or ideas for the podcast,
please get in touch via hello at

1921
01:34:00,947 --> 01:34:06,727
localfirst.fm or use the feedback form on
our website, special thanks to Expo and

1922
01:34:06,727 --> 01:34:08,777
Crab Nebula for supporting this podcast.

1923
01:34:09,177 --> 01:34:09,937
See you next time.