> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loft.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Loft grammar cheat-sheet (v0.4)

One statement per line. `#` starts a comment — comments are kept (they ride the statement below them, or the line they end, and survive reformatting). Blank lines are ignored.
Type declarations may also take the **block form**: end the head with `:` and put one parameter per indented line beneath it (any consistent indent). Same model either way — the one-line form stays legal, and the formatter picks.
Statement order across the file never matters — references resolve across the whole file. Within a line, slot order is fixed: category → type → \[id] → placement (see Line anatomy).
A model may span multiple `.loft` files: every file in a project directory compiles as ONE model with ONE namespace — reference names across files directly, with no import lines, and file layout never changes meaning (folders navigate, never scope).

## Start here — build in this order

Everything below this section is reference. This section is method. Most broken models are correctly-spelled statements written in the wrong order and measured from the wrong thing.

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
# 1. datums first — the framework everything else is measured from
level L1 elev 0' height 10'-0"
grid A vertical   at 0'
grid B vertical   at grid.A + 24'-0"    # the bay width, not grid B's coordinate
grid C vertical   at grid.B + 30'-0"
grid 1 horizontal at 0'
grid 2 horizontal at grid.1 + 20'-0"

# 2. then hosts — walls today (floors and roofs later); frame first, then the line
# traveling (grid.A,grid.1) -> (grid.C,grid.1), heading east; right = south
wall EXT-1 W1 from (grid.A, grid.1) to (grid.C, grid.1) justify left

# 3. then what they host — a typed product, placed along the host off a datum
door 3070 D1 in wall.W1 at 4' past grid.B   # the 3070 doortype states the size once, below

# 4. then the views — what to draw, at what scale, cut where (a query, never a
#    container). A view owns its file: each goes in its own views/ file.
view plan L1-plan of level.L1 scale 1/4" cut 4'-0" name "Level 1 Floor Plan"
```

* **Datums first.** Declare levels and grids before anything measured from them. Order doesn't matter to the parser, but it matters to you: an element you can't anchor is a datum you haven't declared yet
* **Types declare facts once; instances reference a type and add only what it delegates.** A door's size is a fact about the *product* — state it in a `doortype`, and every placement follows. One rule, every category
* **Then hosts, then what they host.** Walls carry doors and windows (floors and roofs will carry their own). An opening names its host and a station along it, so the host must exist as a decision before the opening can be placed — this is the order the model **depends** in, whatever order you type
* **Relative by default; absolute is the last resort.** Prefer, in this order: a declared datum (`(grid.A, grid.1)`) → an expression off one (`grid.A + 14'`) → another element's datum (`wall.W1.end`, `door.D1.jamb.far`) → a bare literal. A literal is the right answer only where the number has no reference — an origin, or a free choice
* **Say the measurement, not the coordinate.** `grid C vertical at grid.B + 30'-0"` states a bay width; `at 54'-0"` states a position and discards why it sits there. Widen the first bay and grid C follows on its own — the coordinate form leaves every later number to be recomputed by hand
* **One travel frame governs everything.** A wall's own from → to direction decides its left from its right — which face `justify` names, which way layers stack, which side `swing` hinges on, and where `at 0'` starts for its openings. Choose the direction, state it in a comment, then write the line
* **When travel and measurement disagree, anchor to a datum.** Closing a perimeter often forces a wall to run opposite the direction you'd naturally measure along it. Rather than counting backwards from that wall's start, place its openings off a grid: `at 4' past grid.2`
* **Check your work with the plan's `dims` button** — it cycles `stated` → `measured` → `off`. **stated** draws only the facts your statements assert: a grid dimensioned from the anchor you chained it off, a carrier's offset, an opening's position. **measured** draws the adjacent-pair chain a paper drawing would show, from resolved positions. If the stated view comes up nearly empty, you dimensioned in coordinates — there are no relationships in the model for it to draw

## Line anatomy — how a declaration reads

Every declaration is **category → type → \[id] → placement**, with a slot dropped where the category doesn't carry it. One of the two leading name slots is always required, so the first name after the keyword is never ambiguous.

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
# element: the type leads and is REQUIRED; the id follows, optional
wall EXT-1 W1 from (grid.A, grid.1) to (grid.C, grid.1) justify left
wall STUD4 from grid.1 to grid.2 along grid.A   # anonymous — id omitted, type still leads

# datum: no type — the REQUIRED id leads (a datum exists to be referenced)
level L1 elev 0' height 10'-0"
grid A vertical   at 0'

# door/window: the type leads here too (and is required); the id follows, optional
door 3070 D1 in wall.W1 at 4' past grid.B   # the 3070 doortype states the size once, below

# opening: the type-less cutter — a hole has no product identity, so its
# dimensions live on the line; id optional, like walls
opening O1 in wall.W1 at 14' width 3' height 3' sill 4'
```

* Read the element form as a noun phrase: `wall EXT-1 W1` = "an EXT-1 wall, called W1" — the type says what it's made of, the id names it, the rest places it
* Declare an element id only when something will reference it — host an opening, anchor a chain (`wall.W1.end`), take a carrier offset. Datums always carry their id
* The v0.1 trailing spelling (`wall W1 from … type EXT-1`) is retired — the parser recognizes it and answers with the corrected line

## Lengths

Always carry units. Bare numbers are **never** lengths.

```loft-fragment theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
12'   8"   12'-6"   12'-6 1/2"   27.5'   4.5"   5/8"   3-5/8"   12ft   8in
```

* Word suffixes (`12ft`, `8in`) are accepted on input; canonical output prints the symbol forms
* Rejected near-misses: `12'6"` (the hyphen is mandatory — write 12'-6") · `1/2'` (no fractional feet — write 6") · `12` (bare numbers are never lengths — write 12')

## Coordinates

`(x, y)`, plan view: positive X is right (east), positive Y is up the page (north).
Each slot is a length literal **or** a position expression over datums:

```loft-fragment theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
(12', 28')     (grid.A, grid.1)     (grid.A + 14', grid.1)
(grid.B - 4', grid.2)     (grid.A + (grid.B - grid.A)/2, grid.1)
```

* A bare token is never a reference and a bare number is never a length: a reference names its category (`grid.A`), a length carries its units.

## Expressions

Anywhere a position or distance goes, a static expression works: `+ - * /`, parentheses, datum references.

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
grid A5  vertical at grid.A + (grid.B - grid.A)/2 label "A.5"  # midpoint between grids A and B
grid D   vertical at grid.C + 12'                              # chained off C
door 3070 D4 in wall.W1 at (grid.2 - grid.1)/2                 # centered: half the 1-to-2 span
```

* **Declared vs projected:** grids and levels are *declared* datums — they get their own statements; elements *project* the rest (`wall.W1.end`, `door.D1.hinge`). Both are **named datums**. An expression like `grid.A - 5'` is a position, not a datum — nothing can reference it later; where a slot demands a named datum, declare one there: `grid A1 vertical at grid.A - 5' label "A.1"`
* A datum reference is a **position**; two same-axis positions subtract into a distance; positions never add, multiply, or divide
* Bare numbers only multiply or divide — a bare number is never a length
* An offset needs an anchor: write `grid.A + (grid.B - grid.A)/2`, not `(grid.B - grid.A)/2`
* Space the minus between names: `(grid.B - grid.A)/2`, never `(grid.B-grid.A)/2` — hyphens are name characters (as in `EXT-1`)
* Cycles between datum positions are errors
* Every reference names its category — `grid.2`, `level.L1`, `wall.W1.end` — so a name is never ambiguous and never guessed

## Project & units

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
project "Sample Project"      # optional
units imperial                # optional; imperial is the v0 default
```

## Levels

A level has three facts: `elev`; **`above`** — the level above it, worked out from the elevations when you don't write it, written (`above level.L3`, or `above none`) when the next level up by elevation isn't the one; and **`height`**, worked out from `above` when you don't write it (the topmost level repeats the story below). Story height is a fact about the building, stated once. A level has `above`; an element has `top`; `top above` is how they meet.

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
level L1 elev 0' height 10'-0"
level L2 elev 10'-0"                # story worked out: L3's elev minus L2's
level L3 elev 21'-0" height 9'-0"   # the top level states its story (else it repeats L2's)
level L1                            # this FILE's level — the default base for its elements
```

* `above <level>` names the level above when the default is wrong — a mezzanine you don't want to stop at; `above none` says nothing is above; `height` beside either overrides the worked-out story. A level has no `top` (write `above`); `stack` is reserved
* A level is declared **once in the unit** (any file, any position — datums beside the grids by convention). A bare `level L2` line is the **file's level statement**: file-scoped content that sets the default base for every element in the file that has one. A default, never a requirement — at most one per file, legal anywhere in it
* With exactly ONE level declared, it is every element's default and no file statement is needed. With two or more, an element needing a base must get one — from the file's statement, or its own `base` — or the error on the element names both fixes
* Elements default to their level's base **and top**: a wall runs from its level to that level's top (its elevation plus its story); `base level.L2 + 7'`, `height 9'`, `top level.L3`, and `top above` — the file's next level up — override per instance (see Walls)

## Grids

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
grid A vertical   at 0'
grid 3 horizontal at 28'-0"
grid B5 vertical  at grid.B + 12'-6" label "B.5"
```

* `vertical` = line of constant X
* `horizontal` = line of constant Y
* An id carries no dot — declare `grid A5 … label "A.5"` and the bubble reads A.5 while every reference says `grid.A5`

## Wall types

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
walltype STUD4 width 4.5" note "3-5/8 stud + gyp ea side"
walltype EXT-1 layers [brick 4", air 1", core: stud 6", gyp 5/8"]
```

* Layers stack left → right of the wall's travel (`flip` mirrors them)
* `core:` marks the structural layer

## Door & window types

**The category requires; the type binds.** Every door/window type must satisfy `width` and `height` — by **fixing** a value (stated once, every placement follows) or **delegating** it (the word `instance`: each placement supplies it).

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
doortype 3070 width 3'-0" height 7'-0"                # fixed: a product fact, stated once
doortype STOREFRONT width instance height 8'-0"       # width delegated to each placement
windowtype W-5040 width 5'-0" height 4'-0"
doortype HM-A:                                        # block form: one parameter per indented line
  width 3'-0"
  height 7'-0"
  u-fire-rating "90"                                  # minutes
  u-hardware instance text required
```

* Rhymes with `walltype`: keyword, type name, parameter pairs. Type names are project-unique per category
* **Block form** (`doortype HM-A:` + indented lines) and one-line form are the same declaration — the block reads better past two or three parameters, and each parameter line is its own error address. Only `doortype`/`windowtype` take a block in v0.4; `walltype` stays one-line
* Supplying a fixed parameter on a placement is an error naming the type and its value — duplicate the type to change it. Omitting a delegated `width`/`height` is an error too: the placement owes what the type left open
* `u-<name>` adds **user parameters** — job-specific data that rides the format (typed from the literal: length, `true`/`false`, or quoted text — a count or a rating is text, since no literal spells a bare number; a delegated one states its type: `u-hardware instance text`). One name has ONE value type within its category — u- vocabulary belongs to the component, so a windowtype may type the same name differently than a doortype
* A delegated u- parameter is optional unless the type adds trailing `required` — then every placement must supply the pair, same error as a missing delegated width
* `swing`, `reverse`, and `sill` stay on placements — per-placement facts, never product facts (a door's unstated `sill` is the level; a window's is required)

## Walls

A wall's line is stated one of two ways — by its **endpoints**, or by a **carrier** it rides plus two **extents** that cut it. Both are first-class; pick whichever states the facts you already know.
Carrier + extents is not a partition trick — an exterior wall running along a grid is the case it fits best. It is how a line-based element is placed — walls and `roomline`s today, every line element to come.

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
# by endpoints
wall EXT-1 W1 from (grid.A, grid.1) to (grid.C, grid.1) justify left

# by carrier + extents — the carrier fixes the line, two transverse datums cut it
wall EXT-1 W2 from grid.1 to grid.2 along grid.A justify left   # ALONG grid A — no offset, no side
wall STUD4 W7 from grid.1 to grid.2 at 4' right of wall.W2      # 4' clear of W2's core face
wall STUD4 from grid.1 to wall.W5.end at 9' left of grid.B      # grid carrier; a wall's line datum cuts too

# vertical facts — base and top name levels; one wall may span stories
wall EXT-1 W9 from (grid.A, grid.3) to (grid.B, grid.3) base level.L1 top level.L3   # a shaft wall, L1 up to L3
```

* Order is **category, type, id, placement** — `wall EXT-1 W1 from …` reads "an EXT-1 wall, called W1" (and rhymes with its `walltype EXT-1` declaration)
* id (`W1`) optional — required only to host doors/windows
* `justify`: `centerline` (default) | `left` | `right` — which face of the wall the drawn line is, in YOUR from → to direction (you typed it; you already know left from right). A side may name its **plane**: bare `left` is the finish face; `justify left foc` holds the structural **core** face (`fos`/`foc`/`fom` — face of stud/concrete/masonry, one plane, three field spellings), `justify left fof` says finish face out loud; `justify core.centerline` holds the core's own center (differs from `centerline` on an asymmetric assembly). Hold the core when finishes may change — it's the plane that doesn't move
* Everything is in the travel frame: layers stack left → right, door arcs open right; `flip` mirrors that content, never the body
* Draw perimeters clockwise with `justify left` and the outside faces out
* **Carrier** — `along <ref>` rides the location line ALONG a declared reference plane and `justify` places the body; `at <offset> <side> of <ref>` holds it off one instead. A wall carrier always needs both a side and an offset (`along wall.W3` is an error)
* **Extents** — any two transverse datums: grids, crossing walls, or wall line datums (`wall.W5.end`)
* `left`/`right` are the frame of the wall THIS statement declares — its own from → to travel — **never** the carrier's
* Derive the frame in a comment before picking a side — `# traveling W4→W5 (east), right = south` — and the side words stop biting
* A wall carrier with no plane named measures from the wall's **core face on your side** — `at 4' right of wall.W3` is 4' clear of W3's studs/block, same rule as `past wall.W3`; name another plane to measure elsewhere (`right of wall.W3.fof` the finish, `right of wall.W3.centerline` the center). Extents (`from wall.W1 to wall.W3`) are joins — the wall runs to the crossing wall's centerline. The wall must sit clearly on the stated side (zero or straddling offsets are errors)
* State each fact once: edit the offset and the wall, its openings, and its dimensions all move together
* **Vertical facts:** a wall's base is its level (the file's `level` statement, or the unit's one level) and its top is that level's top. `base <level> [± offset]` pins the base per instance (Revit's Base Constraint + Base Offset) — a wall from L1 to L4 is one element, and `base level.L1 top level.L4` says where it lives. `height 9'` and `top <level.L3|wall.W1.top> [± offset]` override the top as ever (`height` defaults to the rest of the base level's story)

## Element datums (anchors & accessors)

Every element projects datums you can reference — `<category>.<id>.<datum>` (`wall.W1.end`, `door.D1.jamb`).

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
wall EXT-1 W4 from wall.W1.end to (grid.C, grid.2)    # chain: W4 follows W1
wall STUD4 W5 from (grid.A + 12', grid.1) to (grid.A + 12', grid.2) top wall.W1.top
door 3070 D5 in wall.W1 at 6' past door.D1.jamb   # measured from D1's jamb, not its center
```

* Projection table — wall: `start · end · centerline · core.centerline · fos/foc/fom · fof · top · base` | door: `centerline · head · jamb · sill` + `hinge`/`strike` (swinging leaf) | window: `centerline · head · jamb · sill` | opening: `centerline · head · jamb · sill` (the base interface, nothing more)
* Line datums (`wall.W1.end`) fill a whole coordinate slot; plane datums (`wall.W1.centerline`, `wall.W1.core.centerline`, `wall.W1.top`) join expressions on their axis; opening datums are stations — reference them in directed measurements (`past door.D1.jamb`)
* **Wall planes, field notation:** `fos` / `foc` / `fom` = the structural core face (face of stud / concrete / masonry unit — ONE plane; spell it for the material), `fof` = finish face, `cl` = `centerline` anywhere it appears (`wall.W6.cl`, `door.D1.cl`, `to cl`), `core.centerline` = the core's center. Faces come in pairs, so they resolve where a tape can pick the side: `at 4' past wall.W6.foc` measures from the core face **facing** the opening (`past wall.W6`, with no plane named, means the same); in an expression, use `.centerline` / `.core.centerline`. `ff` is held for far face — finish face is `fof`
* `jamb.near`/`jamb.far` are framed by the **host wall** — near toward its start, far toward its end (the origin `at` measures from). Stable in any direction, so `at 2' past door.D1.jamb.far` reads as it sounds
* Bare `jamb` is **whichever jamb the measurement reaches first** — the one nearer the reference you measured from. Swap `past` for `short of` and it changes ends, which is why bare `jamb` always needs a direction word
* Opening datums are stations (lengths from the wall's start), so they do arithmetic in that wall's own `at`: `door 3070 D9 in wall.W1 at door.D1.jamb.far + 2'`
* Flip the swing and the jambs don't move; rehang the door and `hinge` follows
* Guardrail: anchor to declared datums (grids, levels) by default, to elements by exception — chains state genuinely relative intent, never a substitute for gridding
* Driver → driven, one direction: W2 follows W1, W1 never feels W2; cycles are errors

## Doors

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
door 3070 D1 in wall.W1 at 4' past grid.B   # the 3070 doortype states the size once, below
door 3070 D2 in wall.W5 at 2' past grid.1 swing left
door 3070 D3 in wall.W5 at 3' short of grid.2
door STOREFRONT D6 in wall.W1 at 20' width 6'-8"   # STOREFRONT delegates width; the placement supplies it
```

* The size lives on the `doortype`, not the line (see Door & window types) — a parameter pair is legal on a placement **only** when the type delegates that parameter
* `at` measures from the wall's start to the door **center** — a plain length is a station from that start (`at 4'-0"`), and everything below measures the same station off a datum instead
* `past <ref>` measures forward from a reference; `short of <ref>` holds back from one — both along the wall's from→to travel. A grid line or a datum like `door.D1.jamb.far` is a POINT: the tape runs from it to this opening's **center**. An ELEMENT reference with no datum named (`past door.D4`) measures **clear** — from the reference's facing jamb to this opening's facing jamb — so `at 0' past door.D4` is a flush sidelite, and `past door.D4.centerline` is the center-to-center spelling. A CROSSING WALL with no plane named (`past wall.W6`) is its **core face** facing this opening — face of stud/concrete/masonry, the plane a finish change never moves — the same as `past wall.W6.foc`; name another plane to measure elsewhere: `past wall.W6.centerline` (or `.cl`, the wall's center), `past wall.W6.fof` (finish face), `past wall.W6.core.centerline`. From a wall the landing stays this opening's center; add `to jamb` for clear
* Add `to jamb` to measure to the opening's **near jamb** instead of its center — the jamb the measurement reaches first, coming from the reference: `at 2' past grid.1 to jamb`. `to jamb.far` measures across to the far side instead ("the opening ends at 10'"): `at 10' to jamb.far`. (`to edge`, the older spelling, still parses; with an element reference, the near jamb is already the default landing.) `to centerline` overrides the other way — the measurement lands on the opening's **center**: `at 4' past door.D4 to centerline` puts D4's far jamb 4' from this door's centerline
* `swing` is the hinge side (wall's travel frame); doors open away from the wall's facing — add `reverse` to open toward it. Hardware handing verbatim: `swing left` = LH, `swing left reverse` = LHR
* Defaults: `swing right`; height and width come from the type, always

## Windows

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
window W-5040 WIN1 in wall.W2 at 6'-0" sill 3'-0"
window W-5040 WIN2 in wall.W2 at 4' past window.WIN1 sill 3'-0"   # 4' clear of WIN1's jamb
```

* Same binding rule as doors: size on the `windowtype`, placement facts on the line
* `sill` is **required** on every window and has no default — a sill height is where THIS window sits, not what the product is, so the type can't supply it
* It measures up from the **level**, not from the wall's base: two windows written `sill 3'-0"` sit at the same elevation even when one host wall starts lower. `sill 0'` is a legal answer

## Openings

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
opening O1 in wall.W1 at 14' width 3' height 3' sill 4'
```

* The type-less cutter: a hole has no product identity, so instance dimensions are *correct* here — not a workaround. `width` and `height` are both required (no type carries them); an unstated `sill` is the level itself — a floor-to-head passage
* `sill` measures up from the **level**, exactly as a window's does — a hole and a window sit on the same plane
* Same placement grammar as doors/windows (`past` / `short of` / `to jamb` / `to centerline`), and it projects the base datums (`opening.O1.jamb.far`)

## Rooms

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
room 210 name "OFFICE" at (7', 14')   # number (the id — any text: 210, 210A, A-101), name, point; boundary and area are derived
roomline from (grid.C, grid.2) to (grid.C, grid.3)   # closes a boundary where no wall does — an open-plan split
roomline RL1 from wall.W1 to grid.2 at 12' right of grid.A   # positions like a wall: carrier + extents, or a point pair
```

* A room point marks an enclosure — anchor it to declared datums only (grid expressions fine: `at (grid.A + 12', grid.1 + 8')`). Element datums (`wall.W1.end`) are errors here: a wall edit must never drag a room along. The id is the room NUMBER — unique, optional, any id text (`210`, `210A`, `A-101`, `210-1`; a hyphen inside a token is always part of a name, so subtraction is always spaced: `grid.2 - grid.1`) — and it is what doors and finishes will reference; `name` is the tag text
* The boundary is derived from the walls around the point (finish faces; doorways don't break it) and the area shows on the plan tag and the takeoff. A room whose walls don't close reads NOT ENCLOSED and the plan rings the open ends — close the wall, extend it, or draw a `roomline`; two room points in one enclosure each report the other
* `roomline` is a zero-width room separation line: no type, no geometry, no 3D — a hairline on the plan. It uses the same placement as a wall (point pair, or `from <extent> to <extent> at <offset> left|right of <ref>` / `along <grid>`), may reference grids and walls, and nothing references it — it only closes a boundary. Id optional

## Views

A view is a declared **query** over the model — never a container of elements. Every fact a view has is authorable on the `view` line itself; a `viewtype` is an OPTIONAL template — a named bundle of defaults — and a written fact on the view is a visible override. **A view owns its file:** each `view` statement lives in its own `views/` file (its page), with only `project`, `units`, and `viewtype` declarations allowed beside it.

```loft theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
# untyped: kind → id → the level it is "of" → [crop] → [facts] → [name]
view plan L1-plan of level.L1 scale 1/4" cut 4'-0" name "Level 1 Floor Plan"
view plan L1-min of level.L1               # every fact optional — scale defaults to 1/8", cut to 4'-0"
# or through a template: shared defaults, per-fact; the instance's own words win
viewtype plan A-PLAN scale 1/4" cut 4'-0" depth -10'-0"   # the view range: looks one story down
view A-PLAN L1-key of level.L1 scale 1/8"  # 1/8" here overrides the template's 1/4"
# the crop: lower-left corner to upper-right — what the view shows; corners are ordinary coordinates
view A-PLAN L1-kitchen of level.L1 from (grid.A - 5', grid.1 - 5') to (grid.C + 5', grid.2 + 5') name "Kitchen — Enlarged Plan"
# a section: "at" the cut plane, "to" the far clip — stations on one axis; the gaze is their sign
view section S1 at grid.1 + 4' to grid.3 name "Building Section"
view elevation E1 at grid.1 - 10' to grid.3 top level.L3 + 2' bottom level.L1   # trims anchor to LEVELS
view section S2 horizontal at 10' to 40'   # no grid named — the axis word says which way the plane runs
```

* **One view per file.** A file holding a `view` holds only that view — a second view, or any element beside one (a wall, a room, even a `walltype`), is an error teaching the split. `viewtype` is the exception: a type declaration lives anywhere, so a template may sit beside the one view that applies it, or with the other types. Declaring a view is the moment a project becomes a directory; a single file with no `view` still draws — the implicit plan of its level, at the 1/8" default
* Slot order is every statement's: category → type-or-kind → id → placement. The second word is a viewtype reference or a bare kind word (`plan`, `section`, `elevation`) for an untyped view; kind words can never name a viewtype. The id is what references the view (a token); `name "…"` is the title that prints and what the picker shows — optional, trailing everything. The placement is the kind's own: a plan is `of` a level (neither hosted `in` nor placed `at`; `of level.L9` with no such level is an unknown-datum error naming the levels you do have), a section or elevation is placed by its plane: `at <station> to <station>`
* The kinds are `plan`, `section`, `elevation`; `rcp` is a reserved word that names its era; a discipline (structural, MEP) is a filter on the query, never a kind
* **Sections and elevations** are one machine under two words — the drawing set's vocabulary. `at` is the cut plane's station, `to` the far clip's, each an ordinary station expression (grid refs preserved, wall anchors refused — a wall edit must not move the drawing); the view looks from `at` toward `to` and sees that far, so swapping them looks the other way, and `to` equal to `at` sees nothing. The axis comes from the grids the stations name; name none and the axis word leads: `vertical`/`horizontal` before `at`. Walls the plane crosses draw CUT — paper mass bounded by the cut pen, no poché (cut beside a wall, not on it: a plane down a wall's own line cuts it at full length); walls beyond draw in the projection pen, nearer covering farther, openings punched from cut walls and projected on beyond faces; the mass's outline profiles at the cut pen (the drawing is read by its silhouette); every level draws as a datum line with its name and elevation. Optional `top`/`bottom` trim the frame at LEVELS (`top level.L3 + 2'` — a bare length is a plan's range word, not a trim); untrimmed shows everything, ground to bulkhead
* **The facts** — `scale`, `cut`, and the range words `top`/`bottom`/`depth` — are each optional on both statements, any authoring order (canonical print order: scale cut top bottom depth). Resolution is per fact: the instance's word, else its viewtype's, else the language default — scale **1/8"** flat, cut **4'-0"**. Scale is a length literal in paper inches per model foot (`1/4"`, `1/8"`, `3/16"`, `1"`); it sizes the hatches, the text, and the line weights, so zooming the plan magnifies the drawing instead of changing its scale. Cut is a length above the level
* **The view range** — `top`, `bottom`, `depth` — are lengths relative to the level (a leading `-` reads below it). Defaults when unwritten: top = the level's top, bottom = the level, depth = bottom. What a plan shows is computed against them, never authored: an element crossing the cut plane draws **cut**; wholly between the cut and the top, **dashed** (above); wholly below the cut and above the depth, in the **projection pen** (beyond — `depth -10'` is how a plan shows the story below); outside the range, not at all. Openings follow their host. The relationships are errors with the fix written out — bottom above the cut, top below it, depth above bottom — checked over the effective values, so an authored word conflicts even with a defaulted one
* `from (x, y) to (x, y)` after the query is the **crop** — the rectangle the view shows (Revit's crop region), `from` the lower-left corner, `to` the upper-right. It is a placement on the view, before the facts; the corners are ordinary coordinates, so a crop to grids follows them when a bay widens, and a corner off an unknown grid is the usual unknown-datum error. Anchor corners to grids or literals, never to a wall (`from wall.W1.end` is refused like a room's point). An inside-out pair (`to` left of or below `from`) is an error with the swap written out, never silently fixed. Grid lines end on the crop, bubbles sit just past it, dimension strings stack just inside it (paper offsets from the standard, the same at every scale), and elements outside it clip — a grid the crop doesn't reach, a string to it, a string or room tag not wholly inside it, are hidden, never half-drawn. No crop: the view shows the whole model plus a margin
* A view takes no indented body — annotations (dimensions, tags, notes) are reserved for a future version. New projects start two files — `model.loft` and the plan's own `views/` file — no project ships with zero views

## Reserved keywords

Every major building category is reserved for future versions — using one is an error (not silently ignored). Highlights:

```loft-fragment theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/loft.json","/languages/loft-fragment.json"]}}
component  import  export  const  refplane  detail  sheet  schedule
floor  roof  ceiling  stair  ramp  railing  column  beam  shaft
curtainwall  duct  pipe  conduit  furniture  site  topo  zone  space
floortype  rooftype  materialtype  hatchtype  layertype  …
```

* `instance` is a reserved **value word**: the delegation marker inside type declarations, an error as a value anywhere else

## Words per statement

Every word each statement takes, from the category catalog — the same rows the parser reads. The type slot is `type`; a word's options follow it; a default sits in parentheses; the words reserved for a future version come last, with what will read them.

* `project` — no words
* `units` — no words
* `level` — elev · `above <level>|none` (the next level up by elevation) · height (the story to `above`; the topmost level borrows the story below) · reserved: `stack` (a real interleaved-sequence model (a garage's 8' levels beside the podium's 10' ones)); `label` (a bubble or tag printing text other than the id — the grid's bubble is the one that ships)
* `grid` — vertical|horizontal · at · label
* `walltype` — width · layers · note
* `doortype` — width · height · `u-<name>` · reserved: `fire-rating` (the `duration` value type (`90 min`, `2 hr`) — every parameter value carries units; until then the corpus spells it as text, `u-fire-rating "90"`); `hardware-set` (the door worksheet — `u-` today, a built-in candidate then); `kind` (a second kind of leaf — the door's and the window's declarations list what is coming (D0242))
* `windowtype` — width · height · `u-<name>` · reserved: `fire-rating` (the `duration` value type (`90 min`, `2 hr`) — every parameter value carries units; until then the corpus spells it as text, `u-fire-rating "90"`); `hardware-set` (the door worksheet — `u-` today, a built-in candidate then); `kind` (a second kind of leaf — the door's and the window's declarations list what is coming (D0242))
* `wall` — type · from · to · along · left · right · justify centerline|left|right|core.centerline (centerline) · flip (off) · base (the file's level) · top (the wall's level's top) · height · reserved: `function` (the wall worksheet's second pass — decided at decision 65(e), not yet a clause); `structural-usage` (the structure era); `room-bounding` (the furred or decorative wall that must not bound a room; bounding is per story since decision 74); `label` (a bubble or tag printing text other than the id — the grid's bubble is the one that ships); `phase`, `demolished` (the phasing era — phases are an ordered list declared in their own file, and a view looks at the model as of one)
* `door` — type · in · at · past · short · width · height · `u-<name>` · sill (the host level + 0') · swing left|right (right) · reverse (off) · reserved: `label` (a bubble or tag printing text other than the id — the grid's bubble is the one that ships); `phase`, `demolished` (the phasing era — phases are an ordered list declared in their own file, and a view looks at the model as of one)
* `window` — type · in · at · past · short · width · height · `u-<name>` · sill · reserved: `label` (a bubble or tag printing text other than the id — the grid's bubble is the one that ships); `phase`, `demolished` (the phasing era — phases are an ordered list declared in their own file, and a view looks at the model as of one)
* `opening` — in · at · past · short · width · height · sill (the host level + 0') · reserved: `label` (a bubble or tag printing text other than the id — the grid's bubble is the one that ships); `phase`, `demolished` (the phasing era — phases are an ordered list declared in their own file, and a view looks at the model as of one)
* `room` — at · reserved: `level` (a room that differs from its file's level); `department`, `occupancy` (`roomtype`, or `u-` on categories); `phase`, `demolished` (the phasing era — phases are an ordered list declared in their own file, and a view looks at the model as of one)
* `roomline` — from · to · along · left · right · reserved: `phase`, `demolished` (the phasing era — phases are an ordered list declared in their own file, and a view looks at the model as of one)
* `viewtype plan` — scale (1/8") · cut (4'-0") · top (the level's top) · bottom (the level + 0') · depth (the range bottom) · reserved: `units` (the metric / areas era — a tier on the view ?? viewtype ?? project cascade)
* `viewtype section|elevation` — scale (1/8") · reserved: `units` (the metric / areas era — a tier on the view ?? viewtype ?? project cascade)
* `view plan` — type · of · from · scale (1/8") · cut (4'-0") · top (the level's top) · bottom (the level + 0') · depth (the range bottom) · name · reserved: `phase` (the phasing era — the phase filter, discipline's twin (a filter on the query, never a kind)); `discipline` (the views/filters era — phase's twin (a filter on the query, never a kind); object styles stay one standard, the view's discipline picks the resolver cell the way its scale does)
* `view section|elevation` — type · vertical|horizontal · at · to · scale (1/8") · top · bottom · name · reserved: `phase` (the phasing era — the phase filter, discipline's twin (a filter on the query, never a kind)); `discipline` (the views/filters era — phase's twin (a filter on the query, never a kind); object styles stay one standard, the view's discipline picks the resolver cell the way its scale does)
