BuildWork

Documentation

The builder, explained

What each palette field does, how the config drawer shapes it, and what a workflow can enforce as an order moves. Everything on this page runs in the playground — no account needed to try it.

Open the playground

The palette

Thirteen field and layout types in four groups, ordered the way a template gets built: the plain values a form collects, the choices that constrain an answer to a set, the fields that reach outside the form, and the containers that arrange it all. Drag one onto the canvas and it lands at its default size on the twelve-column grid.

Values

  • Text field3×1

    One line of free text — a reference, a name, a note that fits on a line.

  • Text area3×2

    Multi-line text for instructions and longer notes.

  • Number field3×1

    A numeric value, with decimal places if you want them.

  • Date field3×1

    A calendar date.

Choices

  • Select field3×1

    One choice from a dropdown you define — or several at once when multiple selection is on.

  • Checkbox2×1

    A single yes-or-no tick.

  • Switch3×1

    The same yes-or-no drawn as a toggle — for states rather than confirmations.

  • Radio3×1

    One choice with every option visible at once.

Linked

  • Material3×1

    Points at a material in your stock. Bind it to a number field and the workflow can deduct that amount when an order advances.

  • Formula3×1

    A number computed from other fields — a number field dropped with its formula already open.

Layout

  • Group6×2

    A repeating set of sub-fields: line items. Every order row repeats the whole set.

  • Card12×3

    A titled container that frames related fields and can show or hide all of them with one rule.

  • Divider12×1

    A horizontal rule to separate sections.

Configuring a field

Click a field and its drawer opens beside the canvas. There is no save button — every change applies as you make it, and Ctrl+Z takes it back.

General

Label and help text on every field, plus the type’s own settings: decimal places on a number, the options list on a select or radio, the deduct-from binding on a material.

Each field gets a key derived from its label — the name formulas and visibility rules use. A key freezes once the template is published or another field references it, so nothing that depends on it can silently break.

Validation

Any field can be required. Beyond that: a maximum length on text, a minimum and maximum on numbers, and a row minimum and maximum on repeating groups.

Logic

Computed values

A number field can calculate itself from other fields, using arithmetic and a small set of rounding and min/max functions. References must point at other number fields, and cycles are rejected as you type.

qty * unit_price

Conditional visibility

Any field — or a whole card — can appear only while a rule holds. A hidden field is stripped from the submitted order, not merely hidden on screen.

coating == "matte" and qty > 0

All functions and operators

Functions and operators

Two small languages appear in the builder. Formulas compute a number. Conditions answer yes or no — they drive field and card visibility and workflow advance guards. Both are plain text with a fixed vocabulary, and anything outside it is rejected as you type.

In formulas

+ - * / % ^
Add, subtract, multiply, divide, remainder and power. Parentheses group as usual.
abs(x)
The value without its sign — abs(-3) is 3.
ceil(x)
Rounds up to the next whole number — ceil(2.1) is 3.
floor(x)
Rounds down to the previous whole number — floor(2.9) is 2.
round(x, digits)
Rounds to the nearest whole number — or to a number of decimal places: round(2.345, 2) is 2.35.
min(a, b, …)
The smallest of any number of values.
max(a, b, …)
The largest of any number of values.

How formulas behave

  • References are field keys and must point at other number fields. Inside a repeating group, a row also sees the template’s top-level number fields.
  • A missing, empty or non-numeric input makes the result empty — never zero — and an empty result carries through formulas that build on it.
  • Division by zero gives an empty result rather than an error.
  • The final value rounds to the field’s decimal places.
  • Unknown references and circular formulas are rejected as you type; a formula caps at 1024 characters.

Totals over repeating groups

A formula can also reduce a repeating group’s rows to a single number. Name the group, then the sub-field column — over a line-items group items with a number sub-field cost, an order total is:

order_total = sum(items, cost)

The total adds up the cost of every row and updates as rows are added and filled in.

sum(group, field)
The total of a number column over the group’s rows.
avg(group, field)
The average of the filled cells in a number column.
min(group, field)
The smallest value in a number column — the same min, aggregating when its first argument is a group.
max(group, field)
The largest value in a number column, the same way.
count(group)
How many rows the group has.
countFilled(group, field)
How many rows have answered that column.
defined(group, field)
1 when every row has answered that column, 0 otherwise.
sum(group.nested, field)
Every aggregation also takes a dotted path into nested groups: sum(items.item, cost) totals the cost column of every item row across all items rows, flattened into one set.

How aggregation behaves

  • Blank or non-numeric cells are skipped, not counted as zero — a total stays partial and grows as rows are filled in. Zero and negative values do count.
  • countFilled and defined ask whether a cell is answered: empty text and an empty selection are not answers, while an unticked checkbox (false) and the number 0 are.
  • Aggregation starts at the direct child groups of the formula’s own scope and reaches deeper with dots: sum(items.item, cost) gathers every item row of every items row into one flat set. A row formula can do the same over the row’s own nested groups — never over the group it sits in. A row that is missing the nested list simply contributes nothing to the total.
  • Over an empty group, sum, count and countFilled give 0; avg, min and max give an empty result; defined gives 1 — with no rows, nothing is left unanswered.

In conditions

== !=
Equal and not equal. Exact match, no type bending — the number 1 is not the text "1".
> >= < <=
Number comparisons. False whenever either side is not a number.
contains(field, value)
On a multi-select, true when that choice is picked; on text, true when the text contains the fragment.
isEmpty(field)
True for empty text or when nothing is selected. An unchecked checkbox and the number 0 are not empty.
isNotEmpty(field)
The opposite of isEmpty.
and or
Chain conditions with and, or with or — one rule uses one of them, never both.

How conditions behave

  • The field key goes on the left, the value on the right: a number, text in double quotes, or true and false. Comparing two fields to each other is not supported.
  • A condition over a missing value is false, so a show-rule hides its field — rules fail towards hiding, never towards showing by accident.
  • The same conditions power field visibility, card visibility and workflow advance guards.

Conditions over repeating groups

A condition can also ask about a repeating group as a whole — how many rows it has, or whether every row has answered a column. Name the group — or a dotted path into its nested groups — then the column. These are the three forms:

defined(items, mat)count(items) > 0countFilled(items, note) >= 2

defined(group, field)
True when that column is answered in every row.
count(group) > 0
How many rows the group has, compared against a plain number. Every comparison operator works: == != > >= < <=.
countFilled(group, field) >= 2
How many rows have answered that column, compared against a plain number.

How group conditions behave

  • defined(group, field) is true when that field is filled in every row — and also when the group has no rows at all. To require rows as well, write count(group) > 0 and defined(group, field). It is the same vacuous truth the formula defined has over an empty group, and it is the one caveat that quietly lets an empty order through a gate. It holds through a dotted path too: a row whose nested list is empty leaves defined true.
  • Answered means the same here as in the aggregation functions: empty text and an empty selection are not answers, while an unchecked checkbox (false) and the number 0 are.
  • A group condition names a top-level repeating group and reaches its nested groups with dots: defined(items.item, mat) asks about every item row of every items row, flattened into one set — count(items.item) > 0 counts them all.
  • If the group is not there at all — hidden by a card rule, or never filled in — every condition about it is false, including count(group) == 0. The same holds at depth: if any row is missing its nested list entirely, the condition is false — a gate never opens on malformed data. Formulas differ here on purpose: an aggregation quietly skips such a row, a condition refuses.
  • All three forms work everywhere a condition does: field visibility, card visibility and workflow advance guards.

Worked example: never deduct on a half-filled order

A line-items group items holds a material reference and a quantity column qty bound to stock deduction. On the stage before the one that deducts, set the advance condition to:

count(items) > 0 and defined(items, qty)

An order with no rows cannot advance, and neither can one whose rows still have a blank quantity — so stock never leaves for an order nobody finished filling in.

This gate is the moment to get it right: once an order has passed the deduction stage, the amounts it deducted are locked. Editing them is refused, because that stock has already left the shelf.

Canvas rules

  • Twelve columns. Drag a field’s edge to set how many it spans, drag its body to move it — fields never overlap.
  • Cards and groups nest up to five containers deep — enough to structure a form, not enough to get lost in one.
  • Repeating groups go at most two levels: line items can hold line items, and no further.
  • Keys are unique within their scope. Two different groups may reuse a sub-field key, but a sub-field never shadows a field above it.

Workflow

Every template carries its own ordered list of stages — there is no fixed status list to fit your process into. The list is the workflow: the first stage is where orders start, the last is where they finish, and orders move one stage at a time, forward or back.

A label shop’s preset, for the shape of it:

  1. New
  2. Printingdeducts stock
  3. Done
  4. Invoicedinvoice no.

What a workflow can enforce

  • An advance condition guards a step: the order cannot move forward until its values satisfy the rule. defects == 0 and approved == true
  • Each stage either counts as active work or not — that is what feeds the shop-floor board, so finished stages can drop off it.
  • Any stage can require an invoice number as an order moves into it; the number is stored on the order.
  • One stage may deduct stock. Advancing into it deducts every bound material amount — all of it or none of it, and short stock blocks the move.
  • Moving back is always allowed, and a deduction never happens twice.

All of it is enforced on the server against the template version the order was created from — the buttons in the interface are a convenience, not the gate.

Publishing and versions

A draft becomes orderable by publishing a version. Versions are immutable: new orders take the newest one, existing orders keep the version they were created with. Editing the template tomorrow never rewrites yesterday’s job.

Build one against your own paperwork

The playground runs the real builder with sample clients and materials. Nothing is saved, and no account is needed.

Open the playground