I spent a bit of today looking over how the Ironsworn character sheet is used in the context of the game, because that informs decisions about how to represent it. As an extreme example, if I decided no code was going to interact with the details, I could say that it should just be freeform text. Since I don't really want to do it like that, I know there are tasks I'd like for the system to actually be able to accomplish with it.
I tried out listing all of the various ways that the character sheet can change in Ironsworn, and I think I have that written up. What I have to figure out next is how to represent this stuff in code. I'm not exactly sure, but I'm leaning towards something like this:
from typing import Protocol from virtual_tabletop import sheet as sheet_ class Transform(Protocol): def apply(self, sheet: sheet_.Sheet) -> sheet_.Sheet: ...
Whether this is represented as a protocol as above, or just as Callable[[Sheet], Sheet] depends on whether I come up with other operations besides simply applying the transformation. The types are somewhat lax compared to what the system should be capable of, because this way is slightly more expressive than the strict idea I had in mind, and slightly more ergonomic than the next strictest idea.
I think what would make sense is to use the callable interface, define classes for handling specific operations paramaterized with arguments, and create a simple fluent interface on Sheet to allow for calls like sheet.apply(transform_1).apply(transform_2), or even possibly sheet(transform_1)(transform_2). (Or maybe indexing, or varargs. The precise details aren't relevant to the rest of the implementation, and finding a good choice means thinking more about how I want to use this.)
Anyway, it's getting late and I'm getting tired.