Skip to content

Form modal doctrine + refactor roadmap#104

Open
frousselet wants to merge 16 commits into
mainfrom
docs/form-modal-doctrine
Open

Form modal doctrine + refactor roadmap#104
frousselet wants to merge 16 commits into
mainfrom
docs/form-modal-doctrine

Conversation

@frousselet

@frousselet frousselet commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Context

This PR establishes the form doctrine (rewrite of the Forms section of the brand guidelines) and acts as the tracking point for the refactor: every create / edit / delete form moves to a single, maximally reusable modal model.

Decisions made:

  • Real centered modals over a scrim (end of the side offcanvas).
  • Multi-step from v1: no vertical scroll, large forms split into steps.
  • Group/step breakdown declared in the Form (Python): single source of truth, near-empty entity templates.
  • One form per action (separate Create / Update), mandatory helper per field, clearly marked required fields, visible progress tracking.

The diff in this PR = doctrine in brand-guidelines.md + a CHANGELOG entry. The checklist below tracks the implementation to come (not included here).

Checklist

Shared building blocks (infra)

  • Single modal container, mounted once globally, loaded via HTMX, empty at rest
  • Scrim + context restoration (scroll / filters / selection) on close
  • Modal sizes driven by metadata (sm confirmation, md single-step, lg rich / multi-step)
  • Form shell (header title + icon, stepper, body, sticky footer)
  • Single field-rendering partial (label / control / helper / error), helper rendered unconditionally
  • Remove the old #itemDrawer / #drawer-form-content offcanvas and its CSS/JS

Step model (declarative on the Form)

  • Ordered groups metadata on the Form: (title, icon, [fields])
  • Shell reads this metadata and auto-generates the stepper + sections + field order
  • Design rule: ~6 fields max per step (no-scroll guarantee), enforceable
  • Per-step validation (Next gated on the step's required fields, Back free without losing input)

Progress and navigation

  • Multi-step stepper (done / current / upcoming states, aria-current="step")
  • Single-step completion meter ("N of M required", neutral until submit, aria-label)
  • Footer: Cancel | Back | Next, Next -> Save on the last step
  • Generic attribute-driven JS (zero per-entity JS)
  • Widget re-init (TomSelect, Jodit) after each HTMX swap and step change

Create vs Edit

  • Per entity: XBaseForm + thin XCreateForm / XUpdateForm subclasses
  • Separate endpoints and titles, identical shell and rendering
  • Create empty + focus first field; Edit pre-filled + audit fields visible (readonly where appropriate)

Server contract

  • Shared view mixin (renders the shell, re-renders the partial with errors without closing the modal)
  • Server-side validation is the source of truth; browser hints are nice-to-have
  • Polite ARIA announcement (#hx-live) of the error count after submit

Cross-cutting

  • Correct rendering in both light AND dark themes
  • Mobile care (multi-select, sticky footer, step layout)
  • Focus trap, Escape / scrim = Cancel, tab order = visual order, targets >= 36 px
  • prefers-reduced-motion honoured (.fw-pop entrance)
  • FR translation of helper + label for every field (_() / {% trans %}), no duplicate msgid
  • MCP tools and API endpoints unchanged / verified (UI-only refactor)

Documentation

  • Record the technical spec of the system (location TBD: dedicated doc vs brand-guidelines.md)
  • Update the /styleguide page (live component rendering)
  • Keep README.md and CHANGELOG.md up to date at each step

To decide later

  • Rollout sequencing (pilot-first vs big-bang)
  • Exact shape of the groups metadata API on the Form

Complete list of forms to adapt

App Form Template Done
context Scope context/templates/context/scope_form_modal.html [x]
context Issue context/templates/context/issue_form_modal.html [x]
context Stakeholder context/templates/context/stakeholder_form_modal.html [x]
context Objective context/templates/context/objective_form_modal.html [x]
context Role context/templates/context/role_form_modal.html [x]
context Activity context/templates/context/activity_form_modal.html [x]
context SWOT context/templates/context/swot_form_modal.html [x]
context Indicator context/templates/context/indicator_form_modal.html [x]
context Predefined indicator context/templates/context/indicator_predefined_form_modal.html [x]
assets Essential asset assets/templates/assets/essential_asset_form_modal.html [x]
assets Support asset assets/templates/assets/support_asset_form_modal.html [x]
assets Asset group assets/templates/assets/group_form_modal.html [x]
assets Supplier assets/templates/assets/supplier_form_modal.html [x]
assets Site assets/templates/assets/site_form_modal.html [x]
compliance Framework compliance/templates/compliance/framework_form_modal.html [ ]
compliance Finding compliance/templates/compliance/finding_form_modal.html [ ]
compliance Assessment compliance/templates/compliance/assessment_form_modal.html [ ]
compliance Action plan compliance/templates/compliance/action_plan_form_modal.html [ ]
compliance Mapping compliance/templates/compliance/mapping_form_modal.html [x]
compliance Assessment result compliance/templates/compliance/assessment_result_form_modal.html [ ]
risks Risk risks/templates/risks/risk_form_modal.html [ ]
risks ISO 27005 risk risks/templates/risks/iso27005_risk_form_modal.html [ ]
risks Risk assessment risks/templates/risks/assessment_form_modal.html [ ]
risks Treatment plan risks/templates/risks/treatment_plan_form_modal.html [ ]
risks Treatment action risks/templates/risks/treatment_action_form_modal.html [ ]
risks Risk acceptance risks/templates/risks/acceptance_form_modal.html [ ]
risks Threat risks/templates/risks/threat_form_modal.html [ ]
risks Vulnerability risks/templates/risks/vulnerability_form_modal.html [ ]

Total: 28 forms (9 context, 5 assets, 6 compliance, 8 risks).

🤖 Generated with Claude Code

claude added 2 commits June 10, 2026 20:50
Forms now follow a single reusable model: every form opens as an overlay
modal above the dimmed interface (never a new page), built from one shared
shell and field partial. Create and edit are distinct forms (one per
action, no instance.pk branching), forms never scroll vertically (large
forms split into a multi-step modal), the user always sees a step indicator
or a required-fields completion meter, required fields are marked and
counted, and every field carries a mandatory always-visible helper.

The retired single-column / two-column page-form and standalone delete-page
patterns are documented as superseded; deletion becomes a confirmation
modal. Updates the Components pointer and the CHANGELOG accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turn the global #itemDrawer container and the includes/modal_form.html
shell from a right-side Bootstrap offcanvas into a centered Bootstrap
modal. The swap region id (drawer-form-content) and the shared
HtmxFormMixin flow are kept identical, so every list trigger and all 28
entity form templates switch to modals with no per-entity change.

- base.html: #itemDrawer becomes `.modal` (centered, scrollable body via
  flex column + max-height), JS uses bootstrap.Modal and hidden.bs.modal
  while keeping the Back-button / history integration and the formSaved
  table-refresh path. CSS rescoped from offcanvas to modal chrome.
- includes/modal_form.html: modal header / body / footer, adds a
  modal_form_actions block for footer overrides (e.g. delete).
- swot_detail.html: hide the modal via bootstrap.Modal.getInstance.

First step of the modal form refactor; declarative steps, progress
tracking and per-field helpers land per form next. Full test suite green
(1917 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - commit c07c529 : centered modal container

First foundation step landed: the shared create/edit form container is now a centered Bootstrap modal instead of the right-side offcanvas drawer.

What changed (3 files):

  • templates/base.html : #itemDrawer is now a .modal (centered, body scrolls via flex column + max-height); the JS uses bootstrap.Modal / hidden.bs.modal and keeps the Back-button / history integration and the formSaved table-refresh path. Offcanvas CSS rescoped to modal chrome.
  • templates/includes/modal_form.html : modal header / body / footer; added a modal_form_actions block so footers can be overridden (e.g. delete confirmation).
  • context/templates/context/swot_detail.html : hides the modal via bootstrap.Modal.getInstance.

Why it is low-risk: the swap region id (drawer-form-content) and the shared HtmxFormMixin flow are unchanged, so all 28 entity forms + every list trigger switch to modals with zero per-entity change.

Checklist items covered:

  • Single modal container, mounted once globally, loaded via HTMX, empty at rest
  • Scrim (Bootstrap backdrop) + dimmed interface
  • Form shell (header title + icon, body, sticky footer)

Verification: full test suite green (1917 passed). Visual check in browser (light/dark, mobile) still pending.

Next: the reusable declarative engine (per-field partial with mandatory helper, group/step metadata on the Form, stepper + completion meter, generic JS), then migration form by form.

Introduce core.modal_forms with Step and SteppedFormMixin: a form
declares an ordered list of Step(title, icon, fields) as the single
source of truth for its grouping and ordering. One step renders
single-step (completion meter), two or more render as a multi-step
wizard. The declared steps must cover every visible field exactly once,
validated at instantiation (unknown / duplicate / uncovered field raises
ImproperlyConfigured) so a field never silently drops from a form.

Also adds includes/form_field.html, the single reusable field renderer
(label / control / helper / error), with the helper shown whenever
help_text is set per the Forms doctrine.

No form consumes the engine yet; this is the reusable foundation for the
per-form migration. Adds the PR progress-tracking working rule to
CLAUDE.md. 7 new unit tests, ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - commit 9e22bc3 : declarative step engine (data layer)

Added the reusable engine that makes a form's grouping the single source of truth, without any UI risk (no form consumes it yet).

What changed:

  • core/modal_forms.py : Step(title, icon, fields) + SteppedFormMixin. A form declares ordered steps; one step = single-step (completion meter), 2+ = multi-step wizard. Steps must cover every visible field exactly once - unknown / duplicate / uncovered fields raise ImproperlyConfigured at instantiation, so a field can never silently drop from a form.
  • templates/includes/form_field.html : the single reusable field renderer (label / control / helper / error); helper shown whenever help_text is set.
  • CLAUDE.md : recorded the PR progress-tracking working rule.

API locked (as proposed and approved):

class RoleCreateForm(SteppedFormMixin, forms.ModelForm):
    steps = [Step(_("Identity"), "person-badge", ["name", "description"]), ...]

Checklist item covered:

  • Ordered groups metadata on the Form: (title, icon, [fields])

Verification: 7 new unit tests (grouping, ordering, required counts, the 3 validation failures) - all green; ruff clean.

Next: wire the shell to auto-render steps + stepper + completion meter (presentation layer + generic JS/CSS), validated end to end by migrating a small pilot entity.

Wire the declarative engine into the shared shell and prove it end to end
on the Role create/edit forms.

Presentation (reusable, zero per-entity JS):
- includes/modal_form.html: dual mode. When the form is stepped it
  auto-renders the progress header (includes/form_progress.html) and the
  fields (includes/form_steps.html) and shows a Back/Next/Save footer;
  otherwise it keeps the legacy modal_form_fields block and Cancel/Save,
  so unmigrated forms are untouched.
- includes/form_progress.html: stepper (multi-step) or required-fields
  completion meter (single-step).
- includes/form_steps.html: one section per step, or all fields for a
  single step; renders each via includes/form_field.html.
- base.html: generic attribute-driven JS (step nav, per-step required
  gating, live meter, focus) and brand CSS for the stepper / meter.

Role pilot:
- RoleBaseForm(SteppedFormMixin, ScopedFormMixin, ModelForm) declares two
  steps (Identity / Scope & status) and a helper on every field; thin
  RoleCreateForm / RoleUpdateForm subclasses (one form per action).
- role_form_modal.html reduced to its header icon.
- FR translations for the new helper / step / control strings.

Also fixes multiline Django comments ({# #} cannot span lines and were
rendering as visible text) by switching them to {% comment %} blocks.

Verified in the browser: stepper, per-step validation gating, light and
dark themes, save + list refresh; legacy Activity modal still renders as a
plain centered modal. Full suite green (1924), ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - commit 2778013 : presentation layer + Role pilot (verified in browser)

The declarative engine is now wired into the shell and proven end to end on the Role create/edit forms. Launched the app (Docker) and drove it with a real browser.

What changed:

  • includes/modal_form.html : dual mode. Stepped form -> auto-renders progress header + fields + Back / Next / Save footer. Legacy form -> keeps its modal_form_fields block + Cancel / Save. Unmigrated forms untouched.
  • includes/form_progress.html : stepper (multi-step) or completion meter (single-step).
  • includes/form_steps.html : sections per step / all fields, rendered via form_field.html.
  • base.html : generic attribute-driven JS (step nav, per-step required gating, live meter, focus) + brand CSS for stepper/meter. Zero per-entity JS.
  • Role : RoleBaseForm + thin RoleCreateForm / RoleUpdateForm, two steps (Identity / Scope & status), a helper on every field; the template is now just its header icon. FR translations added.
  • Fix: multiline {# #} comments (which Django renders as visible text) switched to {% comment %}.

Verified in the browser (light + dark):

  • Stepper with done / current / upcoming pills, Back hidden on step 1, Next -> Save on the last step.
  • Per-step gating: empty required fields block Next with an inline message.
  • Helpers visible under every field; required * in muted grey.
  • Save -> modal closes, list refreshes with the new role.
  • Legacy Activity modal still renders as a plain centered modal (no stepper) -> backward compatibility holds.

Checklist items covered (10 ticked; Role marked done in the forms table): field partial, offcanvas removal, shell auto-render, per-step validation, stepper, footer nav, generic JS, widget re-init, base+create/update split, separate endpoints/titles.

Honestly not yet done / verified:

  • Single-step completion meter is implemented but not browser-verified (Role is multi-step). Will validate on the first single-step entity.
  • Edit pre-fill and server-side error re-render inside the stepped modal not yet exercised in the browser.
  • Modal-size metadata and the enforceable "~6 fields/step" cap not implemented.
  • Pre-existing console error e.target.closest is not a function (base.html delegated handlers at lines ~4081/4273/4308/4394, fired on table refresh) is not introduced by this work and is out of scope here.

Status: full suite green (1924), ruff clean. Next: roll out the remaining 27 forms (and verify the single-step meter + edit/error paths on an early one).

Second pilot, single-step this time, to exercise the completion meter and
the server-side error path:
- RequirementMappingBaseForm(SteppedFormMixin, ModelForm) with one
  declarative step and a helper on every field; thin
  RequirementMappingCreateForm / RequirementMappingUpdateForm subclasses.
- Mapping create/update views use the split forms and now set proper
  modal titles (New mapping / Edit mapping) - they were previously empty.
- mapping_form_modal.html reduced to its header icon.
- FR translations for the six field helpers.

Verified in the browser: live completion meter (0/3 -> 3/3), server-side
validation re-render keeping the modal open with input intact (duplicate
mapping non-field error), create + list refresh, and edit pre-fill. Full
suite green (1924), ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - commit 742c5a3 : single-step pilot (Mapping) + remaining engine paths verified

Migrated the inter-framework Mapping form as the single-step pilot, which exercises the paths the Role (multi-step) pilot could not. Driven in a real browser.

What changed:

  • RequirementMappingBaseForm(SteppedFormMixin, ModelForm) with one declarative step + a helper on every field; thin Create / Update subclasses.
  • Mapping views use the split forms and now set New mapping / Edit mapping titles (they were empty before).
  • mapping_form_modal.html reduced to its header icon; FR helper translations added.

Verified in the browser (all previously-pending paths now done):

  • Completion meter live: 0 of 3 required -> 3 of 3 as required fields fill; pre-filled 3 of 3 on edit.
  • Server-side error re-render: a duplicate (source, target) pair returned a non-field-error alert at the top of the form, modal stayed open, selections preserved -> confirms HtmxFormMixin.form_invalid + the shell's non_field_errors block.
  • Create success: modal closed, list refreshed with the new row.
  • Edit pre-fill: title Edit mapping, all three required selects pre-selected.

Note: empty-required Save is gated by the browser's native required attribute (no server round-trip), which is the intended nice-to-have; the server remains the source of truth (proven by the duplicate-mapping case).

Checklist: ticked single-step meter, shared view-mixin re-render, server-side-source-of-truth, create-empty/edit-prefill; Mapping marked done in the forms table (2 / 28 forms migrated: Role, Mapping).

Still pending: #hx-live error-count announcement (not yet checked); modal-size metadata; the enforceable ~6-fields/step cap; and the 26 remaining forms. The pre-existing e.target.closest console error persists and remains out of scope.

Status: full suite green (1924), ruff clean.

Batch 1 of the context app rollout. Each form is split into Create /
Update subclasses over a shared stepped base, with two declarative steps
(Identity + Assessment & status / Scope & status) and a helper on every
field; the modal templates are reduced to their header icon. FR
translations added for the new helpers and the "Assessment & status"
step.

Verified in the browser: the Issue create modal renders the two-step
stepper with all per-field helpers. Full suite green (1924), ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - commit d2c3c19 : context batch 1 (Issue, Activity, SWOT)

Three more context forms migrated to the modal engine, following the proven pilot pattern.

  • Issue, Activity, SWOT analysis: each split into Create / Update subclasses over a shared stepped base, two declarative steps (Identity + Assessment & status / Scope & status), a helper on every field. Templates reduced to their header icon; FR translations added.

Verified: Issue create modal renders the two-step stepper with all per-field helpers (browser). Activity and SWOT follow the identical pattern; full suite green (1924), ruff clean.

Forms table: Issue, Activity, SWOT ticked - 5 / 28 migrated (Role, Mapping, Issue, Activity, SWOT).

Remaining context: Scope (16 fields), Stakeholder (13), Objective (17), Indicator (15), Predefined indicator (14) - the larger ones, next.

Split into StakeholderCreateForm / StakeholderUpdateForm over a shared
stepped base with three declarative steps (Identity / Contact /
Assessment & status) and a helper on every field; template reduced to the
header icon. FR translations added. Full suite green (1924), ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - commit (Stakeholder)

Migrated the context Stakeholder form: Create / Update over a shared stepped base, three steps (Identity / Contact / Assessment & status), a helper on every field, template reduced to its icon, FR translations added.

Full suite green (1924), ruff clean. 6 / 28 migrated. Remaining context: Scope (16 fields), Objective (17), Indicator (15), Predefined indicator (14) - the large/threshold-heavy ones, next.

Batch 2 of the context rollout:
- Objective, Indicator and Predefined indicator forms split into Create /
  Update subclasses over a shared stepped base, three declarative steps
  each (Identity / Measurement or Format & thresholds / Scope & status or
  Review & status) and a helper on every field.
- The Indicator update view keeps its dynamic get_form_class (predefined
  vs standard) and the matching template selection, now returning the
  Update variants.
- Modal templates reduced to their header icon; FR translations added.

Scope is intentionally left on the legacy centered modal: its bespoke
icon picker must first become a reusable widget before it can join the
declarative engine.

Verified in the browser: the Objective create modal renders the three
steps (Identity / Measurement / Scope & status) with all per-field
helpers. Full suite green (1924), ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - commit cf86a82 : context batch 2 (Objective, Indicator, Predefined indicator)

Three more context forms migrated, each three-step.

  • Objective (Identity / Measurement / Scope & status), Indicator and Predefined indicator (Identity / Format & thresholds / Review & status): split into Create / Update over a shared stepped base, a helper on every field. The Indicator update view keeps its dynamic predefined-vs-standard form selection. Templates reduced to the header icon; FR translations added.

Verified: Objective create modal renders the three steps with all per-field helpers (browser). Indicator/Predefined share the pattern; full suite green (1924), ruff clean.

Forms table: Objective, Indicator, Predefined indicator ticked - 9 / 28 migrated.

Scope deferred (deliberate): it stays on the legacy centered modal because its bespoke ~100-line icon picker (popover + grid + search + JS bound to a hidden field) must first be turned into a reusable widget before the field can auto-render in a step. Flagging it as the one context form needing that follow-up. With that caveat, context is otherwise complete (7/8 on the engine).

Extract the Scope icon picker (preview button, clear button, searchable
Bootstrap-icon grid popover) into context.widgets.IconPickerWidget with a
widget template and generic, data-attribute-driven JS + CSS in base.html,
so it initialises anywhere - including after an HTMX swap into the modal.

The Scope form then joins the declarative engine as a four-step form
(Identity / Boundaries / Sites & people / Dates & tags) with a helper on
every field; its custom __init__ (parent/site/manager querysets) is kept,
and the icon field is now a visible, auto-rendered control. Template
reduced to the header icon; FR translations added.

This completes the context app: 8/8 forms on the modal engine.

Verified in the browser: the Scope create modal renders the four steps;
the icon picker opens its grid inside the modal, search works, and
selecting an icon updates the preview and closes the popover. Full suite
green (1924), ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - commit 24c57b7 : Scope migrated + reusable IconPickerWidget — context complete

The blocker for Scope (its bespoke icon picker) is resolved by turning it into a reusable widget.

  • context.widgets.IconPickerWidget: preview + clear buttons + a searchable Bootstrap-icon grid popover, with a widget template and generic, data-attribute-driven JS + CSS in base.html that re-initialises on every HTMX swap. Works anywhere, including inside the modal.
  • Scope joins the engine as a four-step form (Identity / Boundaries / Sites & people / Dates & tags), a helper on every field; its custom __init__ (parent / site / manager querysets) is preserved and icon is now a visible auto-rendered control.

Verified in the browser: Scope create modal renders the four steps; the icon picker opens its grid inside the modal, search filters, and selecting an icon updates the preview and closes the popover.

Forms table: Scope ticked — context is now complete, 8/8 on the engine. 10/28 overall.

Full suite green (1924), ruff clean.

Remaining: assets (5), compliance (4 left — Mapping done), risks (8).

A step entry can now be a list of cells rendered side by side instead of
always full width, with an optional per-cell width ("auto" or a 1-12
Bootstrap span). Field-coverage validation flattens rows via
Step.field_names(), so the exactly-once rule still holds. Rendering goes
through a new includes/form_row.html; single-cell rows stay plain
full-width fields, so every already-migrated form is unchanged.

Applied to the Scope form: the icon picker and name now share one row
([("icon", "auto"), "name"]). Documented in the Forms doctrine.

Verified in the browser: Scope step 1 shows icon + name on a single line,
each with its label and helper. 2 new unit tests (9 total in
test_modal_forms); full suite green (1926), ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - commit (multi-column rows in the engine)

Acting on the review feedback (icon + name should share a line): the declarative engine now supports multi-column rows.

  • A step entry is either a field name (full width, unchanged) or a list of cells laid out side by side, with an optional per-cell width: [[("icon", "auto"), "name"], "description"].
  • Step.field_names() flattens rows so the exactly-once coverage validation is unaffected; rendering goes through a new includes/form_row.html. Every already-migrated form is untouched (single-cell rows render exactly as before).
  • Applied to Scope: the icon picker (col-auto) and name (col-sm) now sit on one line, each keeping its label + helper. Documented in the Forms doctrine.

Verified in the browser (see attached layout): icon + name on a single line in Scope step 1.

2 new unit tests (9 in test_modal_forms), full suite green (1926), ruff clean. This is a reusable capability available to every form going forward (e.g. for pairing short fields).

Use the new multi-column rows to densify every migrated context form and
the compliance Mapping form (paired short selects, date pairs, threshold
pairs, contact email/phone, included/excluded sites, etc.), so steps read
without scrolling.

Fix the Scope icon + name row reported as misaligned: the icon cell was
`col-auto` and grew to the width of its helper (which fell back to the
model's long help_text once removed from the form), leaving a big gap.
The icon picker is a self-evident adornment, so it now carries no helper
(empty help_text overrides the model's) and its button height matches the
inputs. Forms doctrine refined accordingly (helper mandatory on data
fields; self-evident adornment controls may omit it).

Verified in the browser: Scope step 1 shows icon + name cleanly aligned
on one line with no stray gap. Full suite green (1926), ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - layout polish + form optimization

Addressed the alignment feedback and optimized the already-migrated forms.

Icon + name row fix: the icon cell (col-auto) was growing to the width of its helper — and when I first removed the form helper, Django fell back to the model's long help_text ("Nom de classe Bootstrap Icons…"), making the gap worse. Fix: the icon picker is a self-evident adornment, so it now carries no helper (empty help_text overrides the model's) and its button height matches the inputs. Doctrine refined: helper mandatory on data fields; self-evident adornment controls may omit it.

Layout optimization: applied multi-column rows to every migrated context form + the Mapping form — paired short selects (type/category), date pairs, threshold min/max, contact email/phone, included/excluded sites, etc. Steps now read denser without scrolling.

Verified in the browser: Scope step 1 icon + name aligned cleanly on one line, no stray gap. Full suite green (1926), ruff clean. No new forms migrated this commit (refinement) — still 10/28; remaining: assets (5), compliance (4), risks (8).

The stepper already labels the current step, so the repeated section
heading (icon + title) above the fields was redundant and ate vertical
space. Remove it from form_steps.html and delete the now-unused
.mform-step-head CSS. Single-step forms were unaffected (no header).

Verified in the browser: the Scope modal goes straight from the stepper
to the fields. Full suite green (1926).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - remove redundant per-step header

Per feedback: the stepper already names the current step, so the section heading (icon + title, e.g. "IDENTITÉ") above the fields was a duplicate that wasted vertical space. Removed it from form_steps.html and deleted the unused .mform-step-head CSS. Single-step forms were already headerless.

Verified in the browser: the Scope modal now goes straight from the stepper to the fields. Full suite green (1926).

Batch 1 of the assets rollout:
- AssetGroupBaseForm + Create/Update subclasses: two steps (Identity /
  Scope & status), [type, owner] paired on a row, a helper per field.
- SiteBaseForm + Create/Update subclasses: two steps (Identity /
  Location & tags), [type, status] paired; its custom __init__ (parent
  site tree choices) is preserved.
- Both view pairs now set modal titles (New/Edit), previously empty.
- Modal templates reduced to their header icon; FR translations added.

The Supplier form is deferred: its logo upload needs a reusable
image-upload widget (like the icon picker) before it can auto-render.

Verified in the browser: the Site create modal renders its two steps with
the [type, status] row and per-field helpers. Full suite green (1926),
ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - assets batch 1 (Asset group, Site)

  • Asset group: 2 steps (Identity / Scope & status), [type, owner] paired, helper per field.
  • Site: 2 steps (Identity / Location & tags), [type, status] paired; custom __init__ (parent-site tree) preserved.
  • Both now set proper modal titles (were empty); templates reduced to the header icon; FR translations added.

Verified: Site create modal renders its two steps with the paired row and per-field helpers (browser). Full suite green (1926), ruff clean.

Forms table: Asset group, Site ticked — 12/28 migrated.

Supplier deferred (logo upload needs a reusable image-upload widget, like the icon picker). Remaining assets: essential asset, support asset (both large, multi-step) + Supplier. Then compliance (4) and risks (8).

Assets batch 2 (the two large asset forms):
- EssentialAssetBaseForm + Create/Update: four steps (Identity / Security
  needs / Continuity & data / Relations & status); CIA levels on a 3-col
  row, MTD/RTO/RPO on a row, data_classification + personal_data paired.
- SupportAssetBaseForm + Create/Update: four steps (Identity / Hardware &
  network / Lifecycle / Relations & status); hardware and date fields
  paired across rows.
- Helper on every field; both view pairs now set modal titles (were
  empty); templates reduced to the header icon.
- FR translations added using the existing "bien" vocabulary (matching
  the app); the asset helper strings are shared between the two forms.

Verified in the browser: the Essential asset create modal renders the
four steps with the paired Type/Category and Owner/Custodian rows and
per-field helpers. Full suite green (1926), ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - assets batch 2 (Essential asset, Support asset)

The two large asset forms, four steps each with dense column rows.

  • Essential asset: Identity / Security needs / Continuity & data / Relations & status. CIA levels on a 3-column row, MTD/RTO/RPO on a row, data_classification + personal_data paired.
  • Support asset: Identity / Hardware & network / Lifecycle / Relations & status. Hardware and date fields paired across rows.
  • Helper on every field; both now set modal titles (were empty); FR translations use the existing bien vocabulary (asset helper strings shared between the two forms).

Verified: Essential asset create modal renders the four steps with paired Type/Category and Owner/Custodian rows and per-field helpers (browser). Full suite green (1926), ruff clean.

Forms table: Essential asset, Support asset ticked — 14/28 migrated; assets 4/5 (Supplier deferred: logo upload widget needed). Remaining: Supplier, compliance (4), risks (8).

…complete)

Generalise the supplier logo upload into context.widgets.ImageUploadWidget
(square preview + camera button + clear) with generic data-attribute JS in
base.html that resizes the chosen file to a 128px PNG data-URI client-side
(reusing resizeImageFile, now included globally), so it works inside the
modal with no multipart submit. The logo / logo_resized field pair is
consolidated into a single `logo` data-URI field; save() and the full-page
supplier template are updated accordingly.

The Supplier form joins the declarative engine as a four-step form
(Identity / Contact / Contract / Scope & status), with the logo and name
on one row and a helper on every field; modal titles set.

Engine: the shell now also renders form.hidden_fields, so any hidden field
submits even when no step lists it.

This completes the assets app: 5/5 forms on the modal engine.

Verified in the browser: the Supplier create modal renders the four steps,
the logo widget sits inline with the name, and its file picker / resize /
clear wiring is live (resizeImageFile available). Full suite green (1926),
ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Progress - reusable ImageUploadWidget + Supplier (assets complete)

The Supplier blocker (logo upload) is resolved with a reusable image-upload widget, mirroring how the icon picker unblocked Scope.

  • context.widgets.ImageUploadWidget: square preview + camera button + clear, with generic data-attribute JS in base.html that resizes the chosen file to a 128px PNG data-URI client-side (no multipart) — works in the modal. resizeImageFile is now included globally.
  • The logo / logo_resized pair is consolidated into a single logo data-URI field; save() and the full-page supplier template updated.
  • Supplier joins the engine: 4 steps (Identity / Contact / Contract / Scope & status), logo + name on one row, helper per field, modal titles set.
  • Engine fix: the shell now emits form.hidden_fields, so any hidden field submits even when no step lists it.

Verified in the browser: Supplier create modal renders the four steps; logo widget sits inline with the name and its picker/resize/clear wiring is live.

Forms table: Supplier ticked — assets complete (5/5). 15/28 overall. Full suite green (1926), ruff clean. Remaining: compliance (4), risks (8).

Submitting a multi-step form failed with "An invalid form control with
name=... is not focusable": the browser tried to validate/focus a
constrained field (e.g. contact_email, website) on a hidden step, which
aborted the submit. Multi-step forms now carry `novalidate`; validation
is gated per step in JS and enforced server-side (the source of truth),
which re-renders the modal on error.

Verified in the browser: a supplier is created end to end through the
four steps. Full suite green (1926).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frousselet

Copy link
Copy Markdown
Owner Author

Fix - multi-step submit (reported on supplier save)

An invalid form control with name='contact_email'/'website' is not focusable: on submit, the browser's native validation tried to focus a constrained field sitting on a hidden step, which aborted the submit.

Fix: multi-step forms now carry novalidate — per-step gating (JS) + server-side validation (source of truth, re-renders the modal on error) replace native blocking. Single-step and legacy forms keep native validation.

Verified end to end in the browser: created a supplier (SUPP-1) through all four steps; modal closed and the list refreshed. Full suite green (1926).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants