Skip to content

UI Helper Modules

The UI layer is organized into focused helper modules that handle specific concerns. This separation improves testability and reduces coupling in the route handlers. The submodules live under the metaseed.ui.helpers package, whose __init__ re-exports their public functions for convenient access.

graph TD
    Routes[Route Handlers] --> H[metaseed.ui.helpers]
    H --> EH[entity_helpers]
    H --> TH[table_helpers]
    H --> NH[navigation_helpers]
    H --> FC[FormContext<br/>from metaseed.forms]

    EH --> State[AppState]
    TH --> State
    NH --> State
    FC --> State
Hold "Alt" / "Option" to enable pan & zoom

Form rendering

Form rendering context and value collection are exposed through the metaseed.ui.helpers package.

FormContext

Dataclass that encapsulates form rendering state, reducing parameter counts in template rendering. It is defined in metaseed.forms and re-exported from metaseed.ui.helpers.

from metaseed.ui.helpers import FormContext

ctx = FormContext(
    entity_type="Study",
    helper=facade.Study,
    values={"title": "My Study"},
    node_id="abc123",  # None for create forms
)

# Access field categories; each field is in exactly one
required = ctx.get_required_fields()  # required, not holding entities
optional = ctx.get_optional_fields()  # optional, not holding entities
nested = ctx.get_nested_fields()      # holding entities, required or not

A field that holds entities, a list of entities or a single entity, is rendered once: as its table under Related Entities, whose header marks the field required and a single-entity field as holding exactly one. A single-entity table offers no second row, and the add-row route refuses one (ADR 006).

Dataset toolbar

The open dataset's toolbar (Validate, List, Graph, the exports, Import) is one partial, partials/dataset_toolbar.html, shown on the dataset page and above every record form, so the graph can be switched on beside the record being edited. The form's first crumb, Entities, returns to the open dataset's page, /dataset/<name>/edit.

Editing a row's record

A table under Related Entities lists the records stored as children of the open record in that field. A record can also carry a copy of its children embedded in its own data, from a nested document or a dataset saved before its children were stored as records of their own; get_nested_items_for_edit uses that copy only for a field with no stored child records, as the Excel export does. A copy shown next to the records it duplicates showed values the records no longer held: a renamed Source kept its old name in the Study's table.

Every row has an Edit button. A row with a stored record (_node_id) opens that record in its own form. A row added with + Add has no stored record yet; its Edit opens the row in the nested form (/nested/<parent>/<field>/<row>). Saving the nested form stores the row as a record and writes the dataset, as the open record's own Save does; the confirmation appears only after the write. A save that reported success while only the session held the row lost the record when the session ended (#356).

Field notes

Hovering over a field's label in the entity form, required or optional, opens its note: the description, whether it is required, its type, an example, and its pattern, length or range, allowed values and the validation rules that name it. field_notes(helper, spec) in metaseed.ui.services.heading_note builds the notes for one entity type from heading_note, the builder the exported Excel headings use, so the form and the workbook describe a field in the same words. The label carries the note as data-note; static/js/field-notes.js draws it in one box attached to the page body, which stays open while the pointer is on it so a long note can be scrolled. metaseed-hub loads the same script and builds its notes with the same function.

Property Description
is_edit True if editing existing entity
description Entity description from spec
ontology_term Ontology term from spec

Functions

Function Purpose
collect_form_values(form_data, helper) Extract typed values from form submission; a list of any value type is split into one item per line
form_field_groups(fields) Split fields into required_fields, optional_fields and nested_fields, each field in one; the form routes use only this
filter_fields(fields, ...) Filter field list by required/nested criteria (in metaseed.forms)
get_field_data(helper) Get field metadata for template rendering
is_nested_field(field) Check if field dict represents nested entity: entity, or list whose items is not a value type in PRIMITIVE_TYPES
format_validation_errors(e) Convert ValidationError to user-friendly messages

entity_helpers

Handles entity traversal and nested item extraction.

Functions

Function Purpose
to_dict(item) Convert Pydantic model or dict to plain dict
walk_nested_entities(data, entity_type, facade) Recursively traverse nested entities, yielding (type, data) tuples
extract_nested_items(instance, helper) Extract nested items from instance as dict mapping
collect_entities_by_type(state, facade) Collect all entities (root and nested) organized by type

Example: Walking Nested Entities

from metaseed.ui.helpers.entity_helpers import walk_nested_entities

investigation_data = {
    "unique_id": "INV-1",
    "studies": [
        {"unique_id": "STU-1", "observation_units": [...]}
    ]
}

for entity_type, data in walk_nested_entities(investigation_data, "Investigation", facade):
    print(f"{entity_type}: {data.get('unique_id')}")
# Output:
# Study: STU-1
# ObservationUnit: ...

table_helpers

Handles inline table building for nested entity display.

Functions

Function Purpose
get_table_columns(facade, entity_type) Get column names for table (excludes nested fields)
get_table_column_info(facade, entity_type) Get complete column metadata (types, constraints, required)
build_inline_tables(state, facade, entity_type) Build table data for all nested fields of an entity
get_items_store(state, parent_type, field_name) Get correct items store based on nested edit context
format_table_rows(items) Format items list for table row rendering

Table Column Info

get_table_column_info returns a dictionary with:

Key Type Description
columns list[str] Column names to display
column_types dict[str, str] Field type per column
column_constraints dict[str, dict] Constraints per column
required_columns set[str] Required field names
has_nested_children bool Whether entity has nested children

Handles breadcrumb building and reference field resolution.

Functions

Function Purpose
build_breadcrumb(state) Build breadcrumb navigation from nested edit stack
build_ancestor_breadcrumb(state, node) Build breadcrumb from a node's containment chain (parent_id and parent_field, ADR 006), so an entity opened directly for editing still shows its ancestors and the field it sits in
error_response(request, templates, message) Create error response with notification template
get_reference_fields(profile, version, entity_type) Get all reference fields for an entity type
get_parent_id_fields(reference_fields, parent_type) Get fields that reference the parent entity
get_parent_identifier(state, parent_type, target_field) Get parent entity's identifier value

Reference Field Resolution

Reference fields link entities to their parents. The helper checks two sources:

  1. Field definitions with parent_ref attribute (e.g., parent_ref: Study.identifier)
  2. Validation rules with reference attribute (legacy)
from metaseed.ui.helpers.navigation_helpers import get_reference_fields

refs = get_reference_fields("miappe", "1.2", "Sample")
# Returns:
# {
#     "observation_unit_id": {
#         "target_entity": "ObservationUnit",
#         "target_field": "unique_id"
#     }
# }

Module Dependencies

entity_helpers      (standalone)
table_helpers       --> navigation_helpers (for reference fields)
navigation_helpers  --> specs.loader

FormContext is defined in metaseed.forms and re-exported from metaseed.ui.helpers. The helper modules depend on AppState for session state access.