Appearance
Design of formulas
- simplest mental model
- read [[algebra]]
- all plain attributes (eg, "x") refer to self SO
- dot-prefix (eg, ".x") refers to parent SO
- SO name.attribute name -> reference a non-parent SO (eg, "A.x")
- for attributes with invariant = true -> use default derived formula
- derived formulas use bare self-refs (eg "X - w")
- empty formula for position attributes -> silently use parent.attribute + value
- empty formula for length attributes -> silently use value
Bugs
- 2' resolves to 1' 12"
- value column too narrow, increase by 20px
- values for start in all axes for root MUST ALWAYS be ZERO
- for attributes with formulas, disable the value cell
- stretch a parent -> rotates children (ack!)
- restore on launch also rotates children
- invariant flag ignored during resolve → added
enforce_invariants() - w value shows geometry instead of attribute value → resolve rule: always read
.value - X row shows old user formula after invariant change →
set_invariantmust clear formula first - persisted formula on invariant attribute survives reload →
rebind_formulasclears invariant formulas at top - compound imperial
.w - 1 1/2"didn't parse → addedtry_compound_feet/inchesto Tokenizer - algebra fails 1" + 2" -> 1'
- 3' - 1/4" -> does not work in value cells
- display formulas with spaces between tokens (eg. ".l - front_th + dado")
Examples
For the "x" row:
| Formula | Resolves to |
|---|---|
| (empty) | parent.x_min + x_min |
x * 2 | self.x_min * 2 |
.x * 2 | parent.x_min * 2 |
A.x * 2 | A.x_min * 2 |
.x + A.X | parent.x_min + A.x_max |
Proposal
Mental model: x means "my own x." .x means "parent's x" (dot-prefix, like A.x with name omitted). Only name another SO when you mean something other than your parent. Empty formula = parent.attribute + value — the default relationship every child already has.
Per architecture, aliases go in Constraints.resolve and .write. Tokenizer, compiler, evaluator stay untouched.
Four changes, in order
Alias map in Constraints —
resolve_alias(attribute)mapsx→x_min,y→y_min,w→ computed width, etc. Called insideresolve()beforeget_bound(). Derived aliases (w,h,d) compute from two bounds (x_max - x_min). Reverse propagation: writingw = 300setsx_max = x_min + 300.Bare attribute → parent reference — bare identifier like
x(no dot) currently throws. Change: bare identifier → reference to parent SO with that attribute.x * 2tokenizes as{ type: 'reference', object: <parent_id>, attribute: 'x' }. Needs parent id passed into tokenizer context or resolved later in Constraints.Empty formula default — when formula is empty/null, Constraints silently treats it as
parent.<attribute> + <current_value>. The identity relationship — child at offset from parent.Invariant-derived formulas — when an attribute has
invariant = true, it's not a source of truth — it's derived from the other two in its axis. Constraints generates its formula from the alias map's "For Invariant" column: ifxis invariant,x→X - w(position derived from far edge minus width). Only one attribute per axis can be invariant. The other two are edited directly; the invariant one recomputes.
Tests
- Alias resolution:
x→x_min,X→x_max,w→ width - Bare attribute:
x * 2with parent context →parent.x_min * 2 - Cross-SO reference:
A.x * 2→A.x_min * 2 - WRONG: Empty formula: evaluates to
parent.x_min + value - Reverse propagation through aliases: change result,
wupdatesx_max - Invariant default: marking
xas invariant gives it formulaX - w; changingXorwrecomputesx - Only one invariant per axis: marking
xinvariant meanswandXstay editable