Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Decision records

Explanation

Metadata-driven UI

Decision record for the rule that Cluster ERP screens, forms, lists, and actions come from server metadata and generic client code holds no module-specific rule.

Status: Accepted, stated in the project's written engineering rules. This record covers the rule that the screens of Cluster ERP are described by module metadata on the server and drawn by generic client code. It applies fully to the web app in webapp/ and to the record screens of the desktop client. The metadata classes are documented on metadata model.

Context

Cluster ERP has 28 modules, each with many entities. The project's written engineering rules state that the web app is fully metadata-driven: screen, form, list, and action behaviour comes from metadata, and rules embedded only in the interface are forbidden. The metadata test records what happened before the rule was enforced: rules "used to hide in the web client" and were moved to metadata, i18n, or data (tests/test_metadata_driven_rules.py:1-5). Further rationale is not determined from code.

Decision

  • Every module returns a ModuleMeta from get_metadata (src/cluster_erp/core/module_base.py:73-74). src/cluster_erp/core/metadata.py defines 45 classes for entities, fields, columns, actions, tabs, facets, and the sidebar (counted with grep -c "^class ").
  • The server publishes the metadata of all loaded modules at /api/v1/_meta (src/cluster_erp/server/app.py:1519-1546).
  • Generic client code renders lists, forms, and record cards from it. Generic code does not compare against business module names and does not hold module paths or permissions.
  • A module-specific screen that metadata cannot express is a custom widget in that module's own gated bundle, declared in metadata (tests/test_metadata_driven_rules.py:158-180).

The flowchart shows the path from a module to a screen.

flowchart LR
 mod["Module.get_metadata"] --> eff["effective_module_meta"]
 eff --> meta["GET /api/v1/_meta"]
 meta --> web["webapp generic engines"]
 meta --> desk["desktop MetadataStore"]
 desk --> gen["widgets/generic views"]
 meta --> bundle["gated_apps bundle custom widget"]

In prose: the server merges each module's metadata with tenant-defined fields in effective_module_meta and serves the result once per process with an ETag (src/cluster_erp/server/app.py:1531-1544). The web engines gen-list.js, gen-form.js, and gen-detail.js render from it. The desktop client loads the same document into its metadata store (src/cluster_erp/desktop/core/metadata_store.py:21) and builds views in src/cluster_erp/desktop/widgets/generic/. Custom widgets live in gated_apps/<module>/app.js (tests/test_metadata_driven_rules.py:175-180).

Consequences

  • A new entity screen needs metadata only; see add a screen with metadata.
  • Platform features are found by capability, not by module name. The record-documents provider is declared with provides (src/cluster_erp/core/metadata.py:731-735, tests/test_metadata_driven_rules.py:63-74).
  • Home widgets are declared by modules with home_widgets and rendered by the module's bundle (src/cluster_erp/core/metadata.py:739-740).
  • Party roles appear on the party card through FacetMeta; see party unification.
  • Permission codes are seeded from the entities in metadata at start-up (src/cluster_erp/server/app.py:642-652).

Where the code enforces it

Mechanism What it checks Source
test_generic_web_engines_do_not_branch_on_business_module_names Five generic engines compare against no business module name tests/test_architecture_boundaries.py:75-87
test_generic_web_code_names_no_module_paths_permissions_or_language_columns Eleven generic files name no module path, module permission, language column, or language branch tests/test_metadata_driven_rules.py:85-112
test_every_is_active_column_declares_the_active_flag_formatter List behaviour comes from the column's declared formatter tests/test_metadata_driven_rules.py:36-48
test_home_widgets_are_module_metadata_rendered_by_module_bundles Home widgets are module metadata; home.js names none tests/test_metadata_driven_rules.py:158-193
test_working_calendar_is_the_calendars_modules_custom_tab The working calendar is a custom tab, not generic code tests/test_metadata_driven_rules.py:196-214

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13