Units of measure
Give Number fields a unit, so values entered in grams, kilograms and tonnes total correctly — stored canonically, displayed in the unit you chose, and never converted across kinds of thing.
Classes, and the units inside them
A class is a dimension — mass, length, count, volume, time, area, or anything your business invents. Inside it sit the units: mm, m, km. One unit in each class is the base, and every other unit says how many base units it is worth (a millimetre against a base of metres is 0.001).
A workspace arrives with a seeded library so nobody starts from an empty page. Seeded rows are editable but flagged, so "did I define this, or did RowFold?" always has an answer.
Stored canonically, shown in your unit
A value is stored in the unit the column declares — not the class's base unit, and not whatever it happened to be typed in. Type 1200 g into a column held in kilograms and 1.2 is what lands in the cell; the column, its total and its export all read 1.2 kg. That is what makes a column people fill in grams, kilograms and tonnes add up to the right number: the conversion happens once, on the way in, so everything downstream is adding like to like.
What you typed is not thrown away. It stays beside the value, seeds the editor when you reopen the cell, and shows on the cell's tooltip — so a row entered as 1200 g still says so.
Changing the column's unit later converts the values with it. Move a column from kilograms to grams and every stored number is multiplied through exactly, once, inside the same save — the confirmation tells you how many changed. This is the one config change in RowFold that rewrites records, and it does so because the alternative is worse: leaving them alone would keep the same digits under a new label, silently restating every number by a factor of a thousand. Moving to a unit in a different class is refused instead, because no exact factor exists.
Conversion inside a class only — never across one
Units convert freely within a class. Across classes, conversion is refused rather than guessed.
This is the whole safety property. Turning a mass into a length depends entirely on what is being measured, and a silent 1:1 fallback is exactly how a figure ends up wrong by three orders of magnitude with nothing raised. If you genuinely need to cross classes — kilograms per metre of a specific product — that factor belongs on the record, not in the unit table.
Decimals, not floats
Conversion factors and converted values are held as decimals end to end, in C# and in the database. Binary floating point is how 0.30000000000000004 ends up in front of somebody on an invoice, and it is not used here.
Each unit also carries the number of decimal places to show when a value is displayed in it — a display setting, not a storage one.
Tips
• Pick the base unit that your smallest realistic value can be expressed in without rounding — it is the unit everything is stored in.
• Give the count class an <em>each</em> unit and use it for quantities. It reads better than a bare number and stops a quantity being silently added to a mass.
• Rename a unit's label freely; its code is what people type, and the class key is never shown.
Good to know
• A class needs its base unit set before conversions mean anything — the base is written immediately after the class, and a class whose base is missing cannot convert.
• Changing a unit's factor changes the meaning of every stored value expressed against it. Treat it as a data migration, not a settings tweak.
• Two units with the same code in one workspace are refused: the code is what people type, so it has to be unambiguous.
Who can do what
• Creating and editing classes and units is Admin-only. Non-admins see an explanation rather than a 404.
• Attaching a unit to a field is part of editing the field, so it follows the same Admin permission as the rest of the schema.