This is pretty close to type branding (newtype wrapping for the Haskell-inclined), though using template literal types is pretty novel. Normal brands look something like this:
type Brand<BaseType, Brand> = BaseType & { readonly __brand__: Brand };
type FooId = Brand<string, 'FooId'>;
function fooBar(asdf: FooId | 'foobar'): void { }
fooBar will only accept the literal string 'foobar' or a true FooId, but not any arbitrary string. FooId would then come from a function that validates strings as FooIds, or some other part of the app that is an authoritative source for them. Brands extend their BaseType so they can be used anywhere their BaseType is used, but not the inverse
It opens up a huge toolbox of tools that work with Functors and Applicative to make parsing easier, and proves that using those tools will work predictably.
I think you were originally correct and that `Record<string, number>` was not a good example. I would guess that union literals and primitives are handled differently by the compiler in this case, perhaps because a record with keys of every possible string is impossible it may have a special condition to skip some typing rules. In fact, `Record<number, number>` is assignable to `Record<string, number>`.
I believe that would be covariant: `'home' | 'work'` is a subtype of `string`, and `Record<'home' | 'work', number>` is assignable to `Record<string, number>` so the subtype relationship is preserved.
I had read this article more as a "from-first-principles" example, but it's a good note for people who might be unfamiliar with practical Haskell. Using an effects system also promotes the domain DSL from an initial-tagless language (only the interpreter is extensible) to a tagless-final language (both the language and the interpreter are extensible).
It may be worth noting that effectful, polysemy, and the other "new kids" of effects systems are not the only option: record-of-functions, mtl, and type-class effects aren't cutting edge, but they're a little more approachable from an OOP dependency injection pattern.
I don't host it, per-se: I use VimWiki, and occasionally Obsidian for the pretty graph. VimWiki even has a static site generator, but I use Pandoc for that. For search I use FZF and ripgrep.
I can attest to this and took all of my notes on paper in college. However, once I started a real job I realized that this strategy doesn't scale to all situations. In college, I needed to be able to recall all of the information I had ingested: it was low-write, high-read. In the workplace, there's much more information, but I'm unlikely to need most of it: it's high-write, low-read. I need to be able to reference the information, but not necessarily recall it. Taking paper notes became too much of a burden and I moved to a wiki of markdown notes.
I've used Lenses as a beginner/intermediate haskeller and they're very easy to use. It's easy to get bogged down in the theory and specifics but Lenses are perfectly usable on the practical level without ever diving in too deep. That said, now that RecordDotSyntax is a thing, there's less of a use case for surface level lenses.
fooBar will only accept the literal string 'foobar' or a true FooId, but not any arbitrary string. FooId would then come from a function that validates strings as FooIds, or some other part of the app that is an authoritative source for them. Brands extend their BaseType so they can be used anywhere their BaseType is used, but not the inverse