diff --git a/docs/INTERFACE_PATTERN_MIGRATION.md b/docs/INTERFACE_PATTERN_MIGRATION.md
index 25e35d6..895bd0a 100644
--- a/docs/INTERFACE_PATTERN_MIGRATION.md
+++ b/docs/INTERFACE_PATTERN_MIGRATION.md
@@ -29,5 +29,8 @@ submissions, receipts, and handoff execution.
The module uses shared controls, dialogs, blockers, field help, statuses,
loading, empty/error states, disabled reasons, confirmations, and unsaved-draft
-guards. Existing responsive list/editor layouts remain bounded. English and
-German catalogues cover the owned route and editor vocabulary.
+guards. The catalogue action row now uses the shared responsive
+`ActionToolbar`; equal-column definition/editor groups use `FormGrid` and its
+named narrow-layout collapse policy. These layout contracts do not change
+filter scope, permissions, revision semantics, or publication consequences.
+English and German catalogues cover the owned route and editor vocabulary.
diff --git a/src/govoplan_forms/backend/manifest.py b/src/govoplan_forms/backend/manifest.py
index 95da738..1890f92 100644
--- a/src/govoplan_forms/backend/manifest.py
+++ b/src/govoplan_forms/backend/manifest.py
@@ -194,7 +194,8 @@ manifest = ModuleManifest(
summary="Create immutable, versioned schemas consumed by Forms Runtime and institutional services.",
body=(
"Each revision fixes field types, options, constraints, draft, attachment, signature, policy, and handoff requirements. "
- "Publishing is explicit; existing submissions continue to retain their exact revision."
+ "Publishing is explicit; existing submissions continue to retain their exact revision. The catalogue action row and "
+ "definition field groups reflow at narrow widths without changing filter scope, permissions, or lifecycle effects."
),
layer="configured",
documentation_types=("admin", "user"),
diff --git a/webui/src/features/forms/FormDefinitionDialog.tsx b/webui/src/features/forms/FormDefinitionDialog.tsx
index 2b48c5f..1106e8b 100644
--- a/webui/src/features/forms/FormDefinitionDialog.tsx
+++ b/webui/src/features/forms/FormDefinitionDialog.tsx
@@ -1,6 +1,6 @@
import { ArrowDown, ArrowUp, Eye, Languages, Plus, Trash2 } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
-import {
+import { FormGrid,
Button,
ConfirmDialog,
Dialog,
@@ -198,7 +198,7 @@ export default function FormDefinitionDialog({
{error &&
{error}}
-
+
setDraft({ ...draft, title: event.target.value })} />
@@ -260,7 +260,7 @@ export default function FormDefinitionDialog({
placeholder="Optional instructions announced before the Form"
/>
-
+
Fields
@@ -333,19 +333,19 @@ export default function FormDefinitionDialog({
function ConstraintFields({ field, disabled, onChange }: { field: FormFieldDefinition; disabled: boolean; onChange: (value: Record
) => void }) {
if (["text", "multiline_text", "email"].includes(field.value_type)) {
return (
-
+
onChange(patchConstraint(field.constraints, "min_length", event.target.value))} />
onChange(patchConstraint(field.constraints, "max_length", event.target.value))} />
onChange(patchTextConstraint(field.constraints, "pattern", event.target.value))} />
-
+
);
}
if (["integer", "number"].includes(field.value_type)) {
return (
-
+
onChange(patchConstraint(field.constraints, "minimum", event.target.value))} />
onChange(patchConstraint(field.constraints, "maximum", event.target.value))} />
-
+
);
}
return null;
@@ -367,7 +367,7 @@ function ConditionFields({
const predicate = condition?.kind === "predicate" ? condition : null;
const candidates = fields.filter((item) => item.key !== currentKey && item.key.trim());
return (
-
+
+
);
}
@@ -506,7 +506,7 @@ function DefinitionPreview({ definition, previous }: { definition: FormDefinitio
return (
Preview and revision changes
-
+
{definition.title || "Untitled Form"}
{(definition.pages?.length ? definition.pages : [defaultPage(definition.fields)]).map((page) =>
@@ -517,7 +517,7 @@ function DefinitionPreview({ definition, previous }: { definition: FormDefinitio
)}
Changes{changed.map((item) => - {item}
)}
-
+
);
}
diff --git a/webui/src/features/forms/FormsPage.tsx b/webui/src/features/forms/FormsPage.tsx
index 6a9af8e..848e7f1 100644
--- a/webui/src/features/forms/FormsPage.tsx
+++ b/webui/src/features/forms/FormsPage.tsx
@@ -1,6 +1,6 @@
import { Download, Pencil, Plus, RefreshCw, Search, Upload } from "lucide-react";
import { useCallback, useEffect, useRef, useState, type FormEvent } from "react";
-import {
+import { ActionToolbar,
ActionBlockerHint,
Button,
Dialog,
@@ -145,7 +145,7 @@ export default function FormsPage({ settings, auth }: PlatformRouteContext) {
return (
-
+
{error && {error}}
{!canWrite && }
diff --git a/webui/src/styles/forms.css b/webui/src/styles/forms.css
index 5a69d35..c89502d 100644
--- a/webui/src/styles/forms.css
+++ b/webui/src/styles/forms.css
@@ -145,12 +145,6 @@
justify-content: flex-end;
}
-.form-definition-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 12px 16px;
-}
-
.form-definition-wide {
grid-column: 1 / -1;
}
@@ -229,13 +223,6 @@
grid-column: 2 / -1;
}
-.form-field-constraints,
-.form-field-condition {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 9px;
-}
-
.form-composition-section {
display: flex;
flex-direction: column;
@@ -302,9 +289,7 @@
}
.form-preview-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 18px;
+ min-width: 0;
}
.form-preview-grid > div {
@@ -374,14 +359,10 @@
}
@media (max-width: 620px) {
- .form-definition-grid,
- .form-field-constraints,
- .form-field-condition,
.form-page-editor-heading,
.form-section-editor,
.form-localization-editor,
- .form-localization-fields,
- .form-preview-grid {
+ .form-localization-fields {
grid-template-columns: 1fr;
}