Surface / foldkit-surface
Field references
Surface.application generates a reference tree from the Model Schema, one node
per field. Each FieldRef carries its optic, codec, get, and set, plus the
field name as a literal type, so a selection can infer its own output keys
without a parallel field registry.
App.model.todos // FieldRef<Model, Todo[], 'todos'>
App.model.selectedTodoId // FieldRef<Model, Option<string>, 'selectedTodoId'>
App.model.todosById.at('t1') // OptionalRef<Model, Option<Todo>> (dynamic key)
App.model.todos.index(0) // OptionalRef<Model, Option<Todo>> (dynamic index)A Model field that is itself a Schema.Struct recurses, so each of its fields is
a FieldRef too. Array and record access is .index(i) / .at(key), which
returns an OptionalRef — a ModelRef with an Option value. Those are dynamic
selections, so Projection.pick (which needs a static field name) rejects them;
they are useful inside a Projection.
App.fields is the same tree under a second name, kept only so older code
compiles. It is deprecated; use App.model.
Projection.pick turns references into a writable projection — a Schema.Struct,
get, and set:
const Shared = Projection.pick(App.model.todos, App.model.selectedTodoId)
// { schema, dependencies, get, set }Each picked field is named by its last key, so App.model.post.id is id.
Two fields with the same name at different paths, such as post.id and
viewer.id, would be one field, and are refused when the projection is
built; pick them in separate projections. Projection.compose refuses the
same collision between its parts.
A reference from a different application is rejected by a per-application owner token, so two structurally identical Models cannot be mixed. Duplicate members deduplicate; a conflicting definition throws.
Projection.compose merges disjoint writable projections, keeping each part's own
write behavior. ModelRef.fromOptic builds a reference from an optic you already
have.