Goldie Blocks
Authoring

Documents, Modules, and Blocks

Author's-eye view of the YAML/Markdown shape — module file layout, ingestion guidance, and pointers to the schema reference

This page is the authoring view of the content model: what a module file looks like on disk, how ordering and references work in source, and when to split content into a new module. For storage shape, lifecycle, lineage, and version mechanics, see the schema reference:

  • Modules — module identity, lifecycle, data-pool rules, version pinning
  • Blocks — block kinds, field entries, rendering model, numbering resolution
  • Documents — document object/version split, template defaults
  • Derived Selection Facets — performs / consumes / flow.adapt

A document is assembled from modules; modules contain ordered blocks; every block is content or data:

Document → Module[] → Block[]     # block.kind: content | data

The module is the sequencing and reusable legal unit. The block is the universal leaf unit: data blocks capture a group of fields (one block per interview step) and hold their collected values; content blocks express the data as composed prose. There is no separate section / block-group level — module-level structure is the only grouping.

Workspace and Project

A Project is a Folio that contains Documents. In the UI a Project Stack shows those Documents as its Substacks, by the same recursive containment model that makes a Module Stack show its Blocks.

Workspace and Project are stored Folio kinds in folios, with keys, handles, owners and live heads. Workspace contains Projects; Project contains Documents through the ordinary parent and pin mechanism. The Workspace canvas is Home, showing its Projects. Cards expose Label and About in place; only their bottom Open action navigates.

One Workspace is provisioned per browser through the interim door. Container reads take an explicit Folio key and check the Workspace's browser binding; Document reads and edits keep their existing session authority. Work Bar crumbs follow Document → Project → Workspace, with no parent above Workspace. Organisation is not a Folio. Sign-in and team membership remain future work.

Workspace and Project operation-target entries remain undefined, with refusal coverage. The interim creation and metadata door does not invent insertion, arrangement or lifecycle operations for those kinds. See The Workspace Is Home; the Project Is the Sharing Grain.

Folio Insertion

Insertion belongs to the parent containment relationship, not to the child in isolation: a Document adds a Module, a Module adds a Block, and a Project adds a Document. The parent operation decides whether and where the new Folio enters its arrangement; the child-kind adapter mints the stable identity and initial version that make that Folio. UI labels name the child (Add Module, Add Block, Add Document), never the Stack furniture.

This rule does not collapse creation into generic CRUD. Placement, parent invariants, and the child's initial version payload remain distinct concerns.

The operation contract and current menus apply this parent-owned rule. Complete persisted Builder authoring for Module/Block insertion remains in the Builder-first MVP; an available local menu action does not claim reload-safe persistence.

Folio Lifecycle and Removal

Delete and removal are deliberately different operation families, and each applies on its own kind of card.

Delete is the reversible lifecycle operation, on any Folio. Delete / Put back (the on-screen labels; delete and restore in code) take a Document, a Module or a Block off the active list and put it back on — list visibility: deleting a container also deletes its owned descendants after confirmation. Saved editions remain stored; restoring the container restores the same deletion cohort. Permanent, unrecoverable destruction is a separate decision this product does not build; there is no purge, no cron, and no "Deleted" page today.

Remove is arrangement, not lifecycle. Remove Module and Remove Block change the parent Folio's arrangement: the assembled child no longer belongs in that Document or Module, which is why the row names what it removes the folio from ("Remove from this document", "Remove from this module") rather than the folio itself. A removed folio is not deleted — search still finds it — and a deleted folio is not removed from its parent's arrangement; the two facts are independent.

Delete/Put back and Remove both use the existing unified executors. Since 2026-09-09 the card menus offer only Delete; the Remove executor is kept for the Library and is not reachable from a Module or Block menu.

Folio Duplication

Duplicate is source-owned copy-write branching, not ordinary insertion. The source Folio determines the snapshot and lineage the branch inherits; its parent arrangement determines where the new branch goes—normally immediately after its source in the same sibling scope. The UI may simply say Duplicate, but the operation contract must preserve both halves.

Document duplication is persisted. Module and Block duplication currently uses the local arrangement transition and therefore does not yet survive reload.

Folio Library Journey

Consolidated 2026-09-25 into Libraries: what a Library is, its tables, saving, search, insertion, access, the Library menu (formerly Library menu readiness here) and the open questions.

Folio Reviewed Edits

A reviewed edit is a Folio operation family realised through the canonical Proposed Change lifecycle: propose → diff → revise → accept or reject → apply. The shared contract owns authority, pending and stale-base state, acceptance/rejection semantics, and the rule that an agent describes its edit as proposed and awaiting acceptance.

A kind-and-facet adapter owns the editable representation, complete-source requirement, compiler/validator, and applied patch. Direct collected-value writes remain immediate writes under their own guards; they are not Proposed Changes.

One resolved operation contract supplies the Block agent's instruction, allowed output schema, and outcome fold. Proposed Change acceptance separately reconstructs trusted current facts and requires the matching available descriptor before applying the content, About, or Field-structure validator.

Folio Operation Menus

A Folio action menu is a projection of the available operations, not a second operation authority. The operation vocabulary owns canonical action names, grouping and order, required capability, and destructive posture. Its UI adapter supplies a callback and any local disabled reason, then projects the available operations into the menu.

Facet navigation (for example, About this module) is not a Folio operation: it changes the viewed face rather than the Folio or its arrangement. Icons, triggers, focus handling, confirmations, and notifications remain UI concerns.

Availability is a pure projection of static operation metadata and current Folio/relationship facts, not capability alone. Capability makes an operation eligible; lifecycle, reuse stage, classification, lock state, parent, sibling position, and other kind rules decide whether it is available. The UI binds the handler and may explain a local disabled reason, but must not present an unexecutable action.

Canonical Module File

A module file uses two top-level namespaces: module and blocks. Field declarations live on kind: data blocks — there is no standalone data: namespace, no computed:, no ui:, and no top-level flow:. Required fields use the DSL ! marker.

module:
  handle!: termination
  label!: Termination
  description: Termination rights and consequences
  flow:
    condition: '@data.has_termination_rights'

blocks:
  - handle!: termination_details
    label: Termination details
    kind: data
    guide: |
      Establish how termination should work before drafting the clause.
    fields:
      allows_termination_for_convenience|YesNo!:
        label: Allows Termination For Convenience
      notice_period|Duration!:
        label: Notice Period
      notice_deadline|date: = completion_date - notice_period

  - handle!: termination_for_convenience_clause
    label: Termination for convenience clause
    kind: content
    flow:
      condition: '@data.allows_termination_for_convenience = "Yes"'
    classification:
      taxonomy: termination
      detail: concise
      protects: both
    display:
      start_level: 3
    content: |
      Either party may terminate this agreement...

  - handle!: termination_notice_summary
    label: Termination notice summary
    kind: content
    display:
      numbering: {paragraphs: no}
    content: |
      Notice period: @data.notice_period.

Notes for authors:

  • A block's handle! is required authored identity. A UI may derive a proposed handle from a person-entered label, but an agent writing a block always emits the handle. Human-readable @ references use that handle and compile to the stable blk_ identity underneath. Block label: is optional display text and never reference authority.
  • A data block's fields: holds collected fields and computed values. A field declared key|type: = expression is computed; without = it is collected from the user. Per-field label is the human-facing prompt; block-level guide describes the block and tells Goldie how to run the step.
  • Leave a collected field without default unless the author deliberately intends to pre-answer it. See DSL Syntax → Literal Values.
  • One data block per interview step: a cohesive group of fields asked, shown, conditioned, or repeated together. Field paths are unique across all data blocks of one module.
  • Content blocks read the module data namespace (@data.notice_period) — never a block handle. Moving a field between data blocks never breaks content.
  • blocks is an ordered list, and its order is sequence position — nothing declares it. See Ordering Is Sequence Position.
  • flow.condition on a block gates rendering (or, for data blocks, whether the step is asked); on the module it gates whether the module enters the document at all.
  • flow.condition carries data state only — it never encodes where a block renders. Nothing does: every folio item lands on the document, and the placement keys that once said otherwise (display.targets, then display.surfaces) are retired — see Permissions and Visibility → One venue and Placement is never a condition. On blocks a condition may use hasValues — true once every reference in the block resolves — for hide-until-complete behavior without hand-listing fields (see DSL Spec → hasValues).
  • display.* is optional — a block's start_level and numbering flags resolve through the module and then the document when omitted (see Blocks → The Display Facet and Numbering).
  • For the full field catalog, see Modules → Authored Snapshot and Blocks → Version Snapshot.

Three Independent Axes

These are commonly conflated. Keep them separate:

  • block.kind — what the block is: content or data. Modules have no type; a data pool is just a module whose blocks are all data blocks. See Modules → Data Pools.
  • Derived selection facets (performs, consumes) — what behaviors and reads the compiler infers from source. Not authored. See Derived Selection Facets.
  • Placement — no longer an axis at all. display.targets, and the display.surfaces key that replaced it, are both retired (2026-08-25): there is one venue and a block does not declare where it renders. What a block is and what it does were always separate from where it went, and that separation now needs no key to express. See Permissions and Visibility → One venue.

Formal block attributes such as editorial detail level and the position the drafting protects belong in the About card's classification and signal tags; do not recreate them as free-form collected fields. flow.condition branches on actual document data, such as a factual answer or an explicit drafting choice. See Folios Schema → tags.

Data Pool Modules

The canonical handle for a document's primary data pool is literally data_pool. Other modules read its facts through cross-module references:

module:
  handle!: guardians
  label: Guardianship Provisions
  flow:
    condition: '@data_pool.data.has_minor_children = "Yes"'

blocks:
  - handle!: guardian_details
    label: Guardian details
    kind: data
    fields:
      primary_guardian_name|PersonLegalName!:
        question: Who should be appointed as guardian?
      primary_guardian_address|StreetAddressAU:

  - handle!: guardian_appointment
    label: Guardian appointment
    kind: content
    content: |
      I appoint @data.primary_guardian_name as guardian of my children.

For pool sharing, write authority, and the multi-pool naming convention, see Modules → Data Pools.

References in Source

One @ syntax addresses field values and block citations. Authored references use module and block handles or field paths, never labels:

@data.notice_period
@termination.data.notice_period
@termination_for_convenience_clause
@termination_for_convenience_clause:number

The compiler binds readable handles to stable fld_ and blk_ identities before persistence. Renaming a handle therefore updates the projected source without breaking the reference. Legacy {{ ... }} interpolation and [[module#^block]] wiki links are not supported.

The full reference grammar, modifiers, termination rules, and typed failures are in DSL Syntax → References. Identifier scoping (handle vs key vs label) is in Modules → Identifier Model and Blocks → Identifier Model. Labels are safe to edit because they are never reference authority.

Ordering Across Source and Storage

In an offline authored Markdown file, order is position in the YAML sequence. Modules are sequenced within a document and blocks within their module. The authored entry carries no order property: being third in the list means being third.

modules:
  - handle!: lead_capture     # first
  - handle!: guardians        # second
  - handle!: executors        # third

To reorder offline source, move the entry. To delete it, remove it and let the remaining entries close up. There is no authored pointer to repair, first-item marker, or absolute position number.

The database uses a different representation. Each moduleVersions or blockVersions row owns a gapped integer at order.position, scoped to its document or module. Database reads sort sibling rows by that number; the parent does not store an ordered child array. App moves update the row's position rather than rewriting a YAML sequence.

At the import/export boundary, relative order is translated in both directions:

  • Import: sequence index becomes order.position, initially spaced as 1000, 2000, 3000, and so on within each sibling scope.
  • Export: rows are sorted by order.position and emitted as a YAML sequence; the numeric position is omitted from authored source.

Only relative order round-trips. The database may use 1500 for an insertion between 1000 and 2000; exporting and re-importing can normalize those numbers while preserving the same sequence. See YAML ↔ JSON Mapping → Ordering.

flow.after is retired from authored source. It stored a relative anchor — "render me after the sibling named X" — which conflated ordering with inclusion and broke as soon as conditions entered: if X is conditioned out, the anchored item has nowhere to go, and every recovery rule is a guess. It also made cycles representable, cost three writes per move, and required a topological sort on every read.

Order and inclusion are separate concerns: stored position is absolute (order.position, unaffected by any runtime answer) and inclusion is conditional (flow.condition). Authored sequence position is the offline notation for that stored scalar. A block that only applies on one branch carries that branch's condition and sits in its natural position; if the branch does not fire it renders nothing and the blocks after it close up.

See the archived plan 2026-07-30-block-ordering-model. Stored form is now the absolute order.position scalar.

Conditional Runs

Both a per-block condition and a module condition exist, deliberately:

  • Condition the module when the run is the module's whole reason to exist — guardians, business assets, a testamentary trust. Written once; every block inside inherits it. The module is also the unit that moves as a group.
  • Condition the block when the run is a detail inside a module that stands on its own — one or two blocks that only apply on a branch.

A condition names a value, never the existence of another block. "Appears if the previous block appeared" cannot express branching: the block a branch depends on is usually the unconditional question that creates the branch, so existence cannot distinguish two branches sitting behind it.

Lists and Variants

List rendering belongs on block metadata via joinFor or repeatFor (with optional sibling intro/outro blocks), not inline in content:. For full grammar, see DSL Syntax → Block Iteration.

Conditional selection is data-driven: a flow.condition such as @data.allows_termination_for_convenience = "Yes" determines whether a block renders from an actual document answer. Formal descriptive facets such as detail level remain in classification; metadata does not directly drive rendering.

Optional App/Library Metadata

Local skills do not need access, classification, or signal to interview or render. Those fields are app-tier metadata for sharing, search, review, ranking, and reuse:

signal:
  rating: gold        # curated tier: gold | silver | bronze
  readability: 82     # computed score
  popularity: 0.74    # adoption signal

Signals never control rendering. They inform recommendations, drafting feedback, and ranking only.

UI Behavior (Editor)

Default editor rules:

  • modules are collapsible; blocks are not
  • module labels sit as subtle left-gutter anchors
  • block labels appear above blocks on edit/hover/focus, and are UI chrome — not document text
  • block actions (change display type, change level, edit label, move, remove) open from the label/control surface

Ingestion Rules

Create a new module when:

  • there is a part heading
  • there is a major section or topic shift
  • clauses move into a distinct legal concept
  • the unit should be reusable, swappable, or versioned independently

Do not combine unrelated concepts into one module — termination and indemnity are separate modules even when they sit next to each other in the source.

Common module boundaries: Definitions, Interpretation, Conditions precedent, Payment, Confidentiality, Warranties, Indemnity, Limitation of liability, Termination, Boilerplate, Execution.

On this page