Different kinds of errors need to be handled in different ways in various contexts as the article points out.
I won't comment on the error model proposed in the article as I find that without actually trying out stuff like this it's very hard to get a "feel" for it, but at first glance it looks a bit over-engineered.
I think the gist of what makes a good error handling system is as follows:
Certain errors are contract violations/bugs that should not occur in normal program operation, and should not "taint" the code with explicit error handling. The language should provide a mechanism that allows to abort the current "task" when such an error occurs and trigger some error handler. Exceptions are good for this, but it falls on the programmer to define a "task boundary" appropriately. Defensive try/catch all over the place is not that, and is usually done because...
Certain errors are part of normal program operation and the programmer should be reminded to deal with them. Exceptions are terrible for this but Result<T,E> types are great. Having to use Result<T,E> in situations where the error is actually not normal part of program operation (e.g., you're accessing a key on a map that can only be missing due to a bug) is annoying but is easily solved by providing two separate APIs (like Python's __getitem__() and get() methods on dict).
Often times an expected error has no good way to be dealt with at the point it occurs, so there should be an easy way to just abort the current "task" when that happens. This is your ".expect()" or just a giant pile of ? propagation in Rust but Rust panics don't carry enough information by default like you can with an Exception type. Note that is different from the two API version above. You use the "panicking on error" API only in cases where the error is not expected, whereas you use some .expect() equivalent as a way to abort the current task for an expected error.
Sometimes you also want to collect multiple errors and abort only after every error is collected, like in a typechecker. This is often done with "NullObject" patterns or "ZII" (most of the time handled very poorly) but there are ways to do it right where the error case "poisons" the data up to the task boundary but cannot propagate further than that (turning into a Result<T,E> equivalent at that point).
The most important part is understanding task boundaries. I find most programs (including my own) do a very poor job of this.
The information contained in the error (sometimes called the context) is very tricky to deal with as what constitutes relevant information for a library or for an app using that library can be very different and there's a performance cost involved with its collection.