Feature Specification: Configuration Form

Created: 2026-09-23

Status: Implemented

Input: Reconstructed from the code at 0bf4ecd36 (panel/forms-island-adapter/panel-adapter.ts, panel/src/panel/deployment_value.py, views.py, urls.py, templates/deployment_form.html, templates/deployment_section_form.html, static/deployment_submit.js, panel/nix/package.nix), panel/README.md (section "Section form"), panel/docs/index.md, api/specification.md (section "Schema and migration sources"), the specifications of nix-module-form (pull request nix-module-form#27), pull requests #1017, #1022, #1056, #1073, #1102, #1115, #1117, #1188, #1200, #1219, #1329, #1343, #1791 and #1795, and the design notes of the maintainers.

User Scenarios & Testing (mandatory)

User Story 1 - Edit a configuration in a form made from the schema (Priority: P1)

The panel gets the JSON Schema of the configuration from api, and shows a form that nix-module-form makes from it. The operator edits the form. The panel stores the value that the form gives. The panel does not know the options.

Why this priority: the options change with each version of core. The form must follow without a change in the panel.

Independent Test: in apps-panel, the operator edits a deployment in the form and saves it. test_schema.py proves that the panel server does not serve or read the schema.

Acceptance Scenarios:

  1. Given a deployment without a pin, When the form opens, Then the browser gets the schema from GET /schema of api.
  2. Given a deployment with a module source pin, When the form opens, Then the browser gets the schema from POST /schema/get_for with a bearer token, and polls while api answers 202.
  3. Given a deployment that has no value, When the form opens, Then the form starts from the defaults of the schema, with the seed document of the panel configuration (NEW_DEPLOYMENT_SEED) laid over them.
  4. Given an error of api, When the form cannot open, Then the page shows the message of api as text, disables Deploy and Validate, and keeps Save.

User Story 2 - See what changed since the last deploy (Priority: P1)

The form marks the fields that differ from the configuration that is deployed now. Deploy is disabled when nothing changed.

Why this priority: the operator must know what a deploy will change.

Independent Test: run panel-python-tests (test_deployment_form.py). The baseline is the deployed configuration from api, not the submitted one.

Acceptance Scenarios:

  1. Given a deployed configuration, When the operator changes a field, Then the form marks the field, and Deploy is enabled.
  2. Given a submitted configuration that failed, When the form opens, Then the baseline is the last deployed configuration, and the operator can deploy again.
  3. Given a deployment that was never deployed, or that api does not know, When the form opens, Then the form has no baseline.

User Story 3 - Keep old values after a schema change (Priority: P2)

When the options change, the form moves old values to their new names with the rename table of api, and shows the schema changes against the deployed version. Values that cannot move stay under their old names, and the page says so.

Independent Test: in apps-panel, a value with an old name opens under the new name.

Acceptance Scenarios:

  1. Given a value with a renamed option, When the form opens, Then the value shows under the new name.
  2. Given a value that cannot move, When the form opens, Then a banner lists the old and new names.

User Story 4 - Edit one section (Priority: P3)

From the list of instances, the operator opens the section that configures one instance. api gives the sections of the configuration and their places (POST /configuration/sections), and the schema and the renames of one section (?section=<id>). The page shows the schema of that section only, and saves the value of the section back into the whole value. The panel reads no place of a section and moves no path through the renames itself.

Independent Test: run panel-python-tests (test_deployment_section_form.py).

Acceptance Scenarios:

  1. Given a saved deployment, When the operator opens /deployments/<pk>/section/<id>/, Then the page shows the form of that section, with the label that api gives.
  2. Given a configuration that has no place for the value of the section, When the operator opens the page, Then the page says to save the deployment first.
  3. Given a section that api does not declare, When the operator opens the page, Then the page shows the 400 message of api.
  4. Given a deployed configuration, When the operator opens the page, Then the diff compares with the value at the deployed path of the section that api gives.
  5. Given the address /deployments/<pk>/<id>/, When the operator opens it, Then the panel redirects to /deployments/<pk>/section/<id>/.

Edge Cases

  • The schema build of a pin takes longer than 90 seconds: the form stops and shows an error.
  • Validation finds errors: Validate lists them. Save still stores the draft.
  • The operator changes the branch of a union: the form keeps the values that both branches share.

Requirements (mandatory)

Functional Requirements

  • FR-001: The panel MUST get the schema, the rename table and the facts about a configuration from api.
  • FR-002: The panel MUST render the configuration with nix-module-form from the schema, and MUST NOT have a form that names options.
  • FR-003: The panel MUST store and submit the value without change, except the renames that the rename table of api gives.
  • FR-004: The diff baseline MUST be the configuration that api reports as deployed.
  • FR-005: The panel MUST keep Save available when the schema or the form fails.
  • FR-006: The panel MUST show messages of api and of the schema as text.
  • FR-007: The panel MUST start a value that was never saved from the root default of the schema, with the seed document of its configuration (NEW_DEPLOYMENT_SEED) laid over it, and MUST NOT read the option names in the seed.

Key Entities

  • Schema: the JSON Schema that api serves for a module source, optionally for one section.
  • Rename table: the renames of options that api serves for a module source, optionally for one section.
  • Section: a part of the configuration that a form edits on its own, as api gives it: an id, a label, its path in the configuration, its path in the deployed configuration, and whether it changed.
  • Baseline: the deployed configuration and module source, from api.
  • Adapter: panel/forms-island-adapter/panel-adapter.ts, the panel side of nix-module-form.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: A change of the options in core needs no change in the panel.
  • SC-002: The Python, TypeScript and templates of the panel contain no option names of the configuration.

Assumptions

  • api serves the sections of a configuration, and the schema and the renames of one section, re-rooted at the options of that section.

Known Deviations

  • The VM checks and the screenshot scripts hard-code option names of core (deployment-method, applications, mastodon, pixelfed, peertube, enable, domain, subdomain): checks/apps-panel/ (interact-with-panel.py, extra-test-script.py, nixosTest.nix), nix/tests.nix, nix/test-script.py, nix/seed-screenshots.py and nix/take-screenshots.py (#2127). A change of the configuration model breaks these tests.