When I start with "plain" vim the first thing I do is drop in https://trout.me.uk/X11/vimrc to nerf it back to as close to ex-vi as I can get - because my muscle memory comes from classic vis (between the mostly-BSDi job and the mostly-Solaris job I did devbox sysadmin for a software house, so I got to handle Solaris, AIX, IRIX, Tru64, SCO OpenSewer, and a couple others all at the same time ;) my experience of ex-vi versus vim is "when I typo in ex-vi it beeps at me, when I typo in vim it activates yet another feature I didn't know existed and breaks my concentration."

I took a quick look at aurelia after you mentioned it and am clearly going to have to take a deeper look at some point, I'm curious how it's handling all that under the hood, and for cases it handles well it does indeed look really rather nice (though being me I'll need to take it apart before considering using it, automagic reactivity is really cool but only when I can reliably dry-run its path through the framework in my head as I'm writing the code that (ab)uses it).

(so, cheers for mentioning aurelia, all the best conversations involve both/all people involved coming away with extra things/ideas to poke at :)

Anyway. To mobx:

I think the best may to understand mobx before you've actually used it is roughly "it not only provides simple reactivity stuff, when stuff starts getting more complicated you'll find that the more powerful tools you wanted are already there, implemented, and will show you what they're doing in the devtools out of the box."

As an example, computed() is very handy (and the one mobx feature other than just "reacting to changes" that most frameworks' reactivity implementations *do* copy, although I have vague memories of them not always copying it as thoroughly as I'd like) - so this is a bit of a "too simple" example but we're in HN comments so

class SomeData {

@observable rawData = [];

@observable sortBy = 'someField';

@computed get data () {
let { sortBy, rawData } = this;
return rawData.toSorted((a, b) => (a[sortBy] - b[sortBy]));
}
}

(sort function designed for numerics only, dumbass example is dumbass)

and then assuming your display component is tracking its dependencies somehow, a change to the `someData.sortBy` field will automatically expire the cached `data` element and notify the component so it can re-render, at which point `data` gets recalculated and re-cached (and all this will show up as events in the devtools if you've stuck them into your page somewhere).

Since aurelia is basically taking apart your bindings to figure out what to observe, I think it wouldn't be able to track that, and you'd instead have to make sure that when `sortBy` gets changed it fires a trigger to recalculate the `data` field (and then aurelia *would* notice that getting set to a new value).

(aurelia may be smarter than I think, but the only way I can think of for it to *be* the necessary amount of smarter would basically be to implement a subset of mobx's makeAutoObservable and I don't think it does that and I don't think given aurelia's (clearly consciously chosen) aesthetic it *should* do that)

I'm tending towards having viewstate kinda objects as a layer and then model objects behind them, so `rawData` would delegate to the model objects in the above example - then the code that modifies the data I'd be persisting to a backend doesn't have to think/know about how it's going to be displayed, but if you modify it - `addTodo` or an `editTodo` or similar - the change notifications will propagate outwards, expiring things in the viewstate, causing things in the view to notice and re-render themselves as required.

This is especially noticeable to me when e.g. I'm writing a data viewer onto a table of log entries (or something in that vague area, *handwaves vigorously*) and I want to have a 'Refresh' button - that triggers something in the model layer that does a `fetch()` call or whatever, and then sets the new model data to whatever the backend sends back ... and then everything re-renders, keeping all my display choices intact because the viewstate layer didn't get touched.

My experience of learning mobx included a number of "oh for crying out loud, I've implemented half of this feature the hard way in three projects before now" moments - and *that* was what made me fall in love with it.

You might also want to bookmark (and then mostly ignore for the moment) mobx-keystone, which is Even More Complicated under the hood, but provides fairly simple syntax for declaring a tree of reactive state classes that provides (typed if you're using typescript) constructors for you, tree snapshots if you want to be able to save your session state to the backend to resume later, and mutation event logging as JSON-able objects that also includes reverse versions so you can get full undo with a fairly minimal amount of effort (I never, ever want to write undo functionality by hand again, that always required a significant amount of bourbon). Not necessarily to actually *use*, mind you, but seeing what keystone is capable of was useful to my understanding of what mobx itself was capable of.

I ... there's definitely a "would rather be writing code than yak shaving my setup" aspect to my choices, but it's definitely also a question of having as few layers as possible between me and What Actually Happens so things reliably behave how I expect them to behave. Debugging is much more fun for me when I can be confident I'm debugging *my* code rather than there being a decent chance I'm actually debugging how I've misunderstood something else two layers down.

(also, if you're not doing anything particularly strenuous, I'd suggest having a look at bun as an alternative to node, all my 'real world' and/or work stuff is node and I'm not particularly offended by it, but bun is definitely a smoother and more DWIM experience for me, at least so far)