What is the "Typst-way" mental model around templates with shared logic and defaults?

This is very close to what was discussed in How can I have global configuration parameters for a module/package?.

It’s not a mental model, it’s the definition of the language, more specifically — Typst is based on pure functions because it can cache the function input & output and later reuse the output if the cache hit occurs. Hence why the fast incremental compilation exists. It’s a mix of procedural and functional programming, so it can be hard to grasp without any practice.

Basically, because all user functions are pure, you have to change their input to get a different output. Which is why you can’t change a default value simply by creating a same-name variable in a file where you import an already baked function and then expect that function to use the new local variable to change its behavior. You either:

  • change function’s named argument directly or through .with() (#let a-thing = a-thing.with(a: 5)),
  • change function’s positional arguments when used (can be also set + consumed via .with()),
  • change a state that the function uses to retrieve a value (need to use context outside or internally).

Same with show rule functions, but instead of using it in-place, you use it on the right side of a show rule.