Enforce the shared WebUI pattern language
This commit is contained in:
@@ -82,9 +82,18 @@ retaining the standard inset, and `embedded` owns neither scroll nor inset.
|
|||||||
owns module/resource subnavigation plus content; its `split` variant owns
|
owns module/resource subnavigation plus content; its `split` variant owns
|
||||||
collection/detail panes. It centralizes pane sizing, internal scroll,
|
collection/detail panes. It centralizes pane sizing, internal scroll,
|
||||||
responsive collapse/stacking, accessible pane labels and workspace help
|
responsive collapse/stacking, accessible pane labels and workspace help
|
||||||
identity. `PageHeader` remains available when an exceptional canvas needs only
|
identity. `WorkspaceFrame` is the outer full-height module frame and owns
|
||||||
the shared heading. Specialized layouts such as `AdminPageLayout` compose
|
container or application-viewport height, overflow, surface, landmark, help,
|
||||||
these lower-level Core contracts; they do not repeat markup or responsive CSS.
|
and accessible-name behavior. `PageHeader` remains available when an
|
||||||
|
exceptional canvas needs only the shared heading. Specialized layouts such as
|
||||||
|
`AdminPageLayout` compose these lower-level Core contracts; they do not repeat
|
||||||
|
markup or responsive CSS.
|
||||||
|
|
||||||
|
Composite workspaces whose selected contribution supplies its own semantic
|
||||||
|
heading may use `PageLayout` with its visible header delegated. This preserves
|
||||||
|
the central inset, loading boundary, help identity, and content frame without
|
||||||
|
adding a duplicate heading. It is not permission to recreate the page header
|
||||||
|
locally on ordinary headed pages.
|
||||||
|
|
||||||
Module CSS may arrange domain content inside a shared layout. It must not
|
Module CSS may arrange domain content inside a shared layout. It must not
|
||||||
override Core layout internals or copy the outer page, dialog, toolbar, form or
|
override Core layout internals or copy the outer page, dialog, toolbar, form or
|
||||||
@@ -102,16 +111,34 @@ Migration is incremental and enforceable:
|
|||||||
5. promote the next repeated structure only after its variants and extension
|
5. promote the next repeated structure only after its variants and extension
|
||||||
points are understood.
|
points are understood.
|
||||||
|
|
||||||
|
The current page-frame and workspace migration has no legacy exceptions. New
|
||||||
|
raw frames fail the focused layout contract instead of entering a new baseline.
|
||||||
|
|
||||||
The current structural vocabulary is:
|
The current structural vocabulary is:
|
||||||
|
|
||||||
- `ActionToolbar`, `ToolbarGroup`, and `ToolbarSpacer` own action alignment,
|
- `ActionToolbar`, `ToolbarGroup`, and `ToolbarSpacer` own action alignment,
|
||||||
density, grouping, accessible toolbar naming, help identity, and responsive
|
distribution, density, grouping, panel/section surfaces, accessible toolbar
|
||||||
wrapping. Modules may add surface padding or domain-specific presentation;
|
naming, help identity, and responsive wrapping. Modules may add
|
||||||
they do not recreate the flex/wrap skeleton.
|
domain-specific presentation; they do not recreate the flex/wrap skeleton.
|
||||||
|
- `WorkspaceFrame` and `WorkspaceLayout` own application-viewport framing,
|
||||||
|
surfaces, overflow, list/detail and navigation/content pane geometry,
|
||||||
|
accessible region identity, and responsive pane behavior. Modules own only
|
||||||
|
the domain regions placed inside those contracts.
|
||||||
|
- `FilterBar` owns submitted or live filter/search arrangement, wrapping,
|
||||||
|
width and surface. `SelectionList`, `SelectionListItem`, and
|
||||||
|
`SelectionListItemContent` own selectable resource navigation and its
|
||||||
|
title/description/leading-icon geometry. `CountBadge` owns compact numeric
|
||||||
|
emphasis. Modules retain filter behavior, selection state, and count meaning.
|
||||||
|
- `StatePanel` owns whole-surface, compact, inline and fill state presentation
|
||||||
|
for empty, unavailable, blocked, warning and recoverable-error compositions.
|
||||||
|
Modules provide the cause, consequence, permitted action and authority.
|
||||||
- `ContentGrid`, `FormGrid`, `FormLayout`, and `GridItem` own equal-column
|
- `ContentGrid`, `FormGrid`, `FormLayout`, and `GridItem` own equal-column
|
||||||
geometry, standard gaps, alignment, spans, native form semantics, and named
|
geometry, standard gaps, alignment, spans, native form semantics, and named
|
||||||
responsive collapse points. A module-local grid remains appropriate only
|
responsive collapse points. A module-local grid remains appropriate only
|
||||||
when unequal tracks or domain visualization semantics are material.
|
when unequal tracks or domain visualization semantics are material.
|
||||||
|
- `ContentSection` owns repeated bordered or subtle content-section surfaces,
|
||||||
|
density, stacked flow and surrounding rhythm without prescribing a domain
|
||||||
|
heading or body schema.
|
||||||
- `FormSection` owns form-section heading, description, actions, content flow,
|
- `FormSection` owns form-section heading, description, actions, content flow,
|
||||||
separation, and panel presentation. It does not own field values,
|
separation, and panel presentation. It does not own field values,
|
||||||
validation, permissions, or domain wording.
|
validation, permissions, or domain wording.
|
||||||
@@ -129,15 +156,22 @@ The current structural vocabulary is:
|
|||||||
`DialogSection` own the footer action flow, native form flow, and body
|
`DialogSection` own the footer action flow, native form flow, and body
|
||||||
grouping used inside it. Modules compose fields and consequences rather than
|
grouping used inside it. Modules compose fields and consequences rather than
|
||||||
recreating dialog anatomy.
|
recreating dialog anatomy.
|
||||||
|
- `DefinitionPalette`, `DefinitionPaletteGroup`, `DefinitionPaletteItem`, and
|
||||||
|
`DefinitionNodeIcon`, together with the shared definition-canvas classes,
|
||||||
|
own reusable graph-editor palette, canvas-control, node-icon, port and empty
|
||||||
|
overlay visuals. Workflow/Dataflow retain node types, shapes, edges,
|
||||||
|
validation and execution semantics. `FloatingStatus` owns the common
|
||||||
|
non-shifting activity overlay.
|
||||||
|
|
||||||
Raw toolbar tags, the former generic grid and property-list classes, raw
|
Raw toolbar tags, the former generic grid and property-list classes, retired
|
||||||
dialog-form wrappers, and module-local definitions of these contracts are
|
module-local shells/states/metrics/badges, raw dialog-form wrappers, and
|
||||||
rejected by the focused workspace checks. Dialog widths matching the Core size
|
module-local definitions of these contracts are rejected by the focused
|
||||||
scale must use `Dialog size`; other local widths require a reviewed exception
|
workspace checks. Dialog widths matching the Core size scale must use `Dialog
|
||||||
and may only decrease. The next promotion candidates are assignment/picker
|
size`; other local widths require a reviewed exception and may only decrease.
|
||||||
composition, filter/search composition, and unified empty/error state
|
Remaining local layout is acceptable only for unequal-track domain editors,
|
||||||
composition. Their APIs must remain composable; a central component is not a
|
visualizations, trees, timelines, data tables, or domain-specific multi-pane
|
||||||
single oversized page template.
|
interaction. Generic resemblance alone is not a reason to create one oversized
|
||||||
|
page template, while exact repeated structural anatomy must be promoted.
|
||||||
|
|
||||||
## Surface Archetypes
|
## Surface Archetypes
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,35 @@
|
|||||||
# Shared WebUI Primitive Inventory
|
# Shared WebUI Primitive Inventory
|
||||||
|
|
||||||
This 2026-08-18 inventory records the implementation state after the
|
This 2026-08-18 inventory records the implementation state after the
|
||||||
product-wide toolbar, grid, form-section, dialog-anatomy, metric-group, and
|
product-wide structural consolidation and second duplicate-rule audit. It is
|
||||||
description-list migrations. It is
|
evidence for enforcement, not a substitute for the normative
|
||||||
evidence for prioritization, not a substitute for the normative
|
|
||||||
[interface pattern language](../architecture/INTERFACE_PATTERN_LANGUAGE.md).
|
[interface pattern language](../architecture/INTERFACE_PATTERN_LANGUAGE.md).
|
||||||
|
|
||||||
## Implemented And Enforced
|
## Implemented And Enforced
|
||||||
|
|
||||||
| Contract | Adoption evidence | Ownership now enforced |
|
| Contract | Adoption evidence | Ownership now enforced |
|
||||||
| --- | ---: | --- |
|
| --- | ---: | --- |
|
||||||
| `ActionToolbar` and groups | 39 source files | Raw module-prefixed toolbar elements and local toolbar component definitions are rejected. |
|
| `ActionToolbar` and groups | 50 source files | Raw module-prefixed toolbar elements and local toolbar definitions are rejected. Distribution, wrapping, density, grouping and panel/section surfaces are Core-owned. |
|
||||||
| `ContentGrid` | 15 source files | Former `dashboard-grid` and `settings-grid` wrappers are rejected. |
|
| `PageLayout` / `WorkspaceLayout` / `WorkspaceFrame` | 25 / 16 / 17 source files | Headed page anatomy, full-height viewport frames and navigation/list-detail panes no longer repeat inset, heading, notices, loading, shell height, surface, overflow or pane geometry. The raw page-frame and raw workspace exception baselines are both empty. |
|
||||||
| `FormGrid` and `FormLayout` | 53 source files | Former `form-grid` and `admin-form-grid` wrappers are rejected; equal-column dialog and editor grids were migrated even when they had module-prefixed names. |
|
| `FilterBar` | 14 source files | Catalogue and pane search/filter rows share width, surface, layout and wrapping. |
|
||||||
| `FormSection` | 2 representative source files | The reusable section API, variants, responsive action placement, and heading anatomy are Core-owned; broader adoption is incremental. |
|
| `SelectionList` family | 19 source files | Resource navigation shares selection, title/description, leading-icon and truncation anatomy. |
|
||||||
| `DialogActions` | Every Core `Dialog` footer | Footer wrapping and alignment are no longer repeated by consumers. |
|
| `StatePanel` | 25 source files | Whole-surface, compact and fill empty/blocked/error states replace module-local state shells. |
|
||||||
| `DialogForm` | 6 source files | Raw `*-dialog-form` form wrappers are rejected. |
|
| `CountBadge` | 8 source files | Notification, folder, search, postbox and graph counts use one compact badge contract. |
|
||||||
| `DialogSection` | 6 source files | Repeated dialog field/content grouping is available as a shared primitive. |
|
| `ContentSection` | 5 source files | Repeated bordered/subtle editor sections and compact provenance panels share surface, density, flow and rhythm. |
|
||||||
| `MetricGrid` | 29 uses in 25 source files | The former `metric-grid` class and module-specific column overrides were removed. Core now owns 1–5/auto-fit columns, minimum width, density, block/inset/zero spacing, and collapse. |
|
| `ContentGrid` | 22 source files | Equal-column content geometry and former dashboard/settings/assignment copies are Core-owned. |
|
||||||
| `DescriptionList` and `DescriptionItem` | 51 lists in 28 source files; item composition in 11 files | The former `admin-details-grid` and `detail-list` classes were removed. Core now owns stacked and inline property layouts, density, term width, wrapping, and collapse while preserving native `dl`/`dt`/`dd` semantics. |
|
| `FormGrid` and `FormLayout` | 53 source files | Former generic/admin grids and equal-column dialog/editor copies use named collapse points and native form semantics. |
|
||||||
| Standard dialog sizing | 9 consumers and 6 duplicated width rules removed | Confirmations and compatible Calendar, credential, Campaign, Files, and Voting dialogs use the Core small/large/wide scale. The 61 remaining specialized selectors across 22 CSS files are registered and decrease-only. |
|
| `MetricGrid` / `MetricCard` | 31 / 33 source files | Module-local metric helpers, grids and card visual definitions were removed. |
|
||||||
|
| `DescriptionList` and `DescriptionItem` | 28 source files | Former generic property grids use semantic `dl`/`dt`/`dd` composition with central density and collapse. |
|
||||||
|
| `DefinitionPalette`, node/canvas visuals and `FloatingStatus` | 2 Dataflow/Workflow consumers each | The copied graph palette, canvas controls, minimap, node icon/port, empty overlay and activity overlay definitions are Core-owned; graph semantics remain local. |
|
||||||
|
| `DialogActions`, `DialogForm`, `DialogSection` | every Core footer / 6 / 6 source files | Footer action flow, native dialog form flow and dialog content grouping are Core-owned. |
|
||||||
|
| Standard dialog sizing | 61 reviewed specialized selectors | Any width matching the Core 460/560/680/1040/1440px scale must use `Dialog size`; the remaining decrease-only exceptions are explicit. |
|
||||||
|
|
||||||
`tools/checks/check-shared-webui-primitives.py` verifies Core exports and
|
`tools/checks/check-shared-webui-primitives.py` verifies Core exports and
|
||||||
ownership, representative consumers, the absence of the retired raw anatomy,
|
ownership, representative consumers, the absence of the retired raw anatomy,
|
||||||
and composition of every `Dialog` footer through `DialogActions`.
|
and composition of every `Dialog` footer through `DialogActions`.
|
||||||
|
`tools/checks/check-shared-webui-layouts.py` additionally requires the reviewed
|
||||||
|
Core and module consumers and rejects any raw page or workspace frame; there
|
||||||
|
are no remaining allow-listed layout exceptions.
|
||||||
|
|
||||||
## Dialog Width Classification
|
## Dialog Width Classification
|
||||||
|
|
||||||
@@ -36,31 +42,33 @@ Their exact selector set lives in
|
|||||||
for a new selector, a stale baseline entry, or any local width that duplicates
|
for a new selector, a stale baseline entry, or any local width that duplicates
|
||||||
the Core scale.
|
the Core scale.
|
||||||
|
|
||||||
## Remaining Promotion Candidates
|
## Audit Result And Deliberate Local Ownership
|
||||||
|
|
||||||
The post-migration scan still finds domain-specific grids, but the repeated
|
The second scan compared exact CSS declaration bodies and JSX anatomy across
|
||||||
generic metric and property-list geometry is gone. Remaining grids are mostly
|
every WebUI module after migration. All repeated generic structural candidates
|
||||||
unequal-track editors, visualizations, workflow facts, import mappings, and
|
found in that pass were promoted: viewport frames, catalogue/list shells,
|
||||||
collection layouts. The next useful candidates depend on interaction semantics:
|
filters, selectable lists, state panels, count badges, section frames,
|
||||||
|
equal-column grids, section headers, metrics, and definition-editor chrome.
|
||||||
|
The final legacy-baseline pass also migrated Access administration, Core
|
||||||
|
Settings, Docs, Mail bounce processing, and Organizations to the shared page
|
||||||
|
and workspace layouts and removed their copied responsive geometry.
|
||||||
|
|
||||||
| Priority | Remaining pattern | Evidence | Proposed contract |
|
The remaining cross-module declaration matches are not independent component
|
||||||
| --- | --- | ---: | --- |
|
anatomy. They are small token-based rules such as ellipsis, muted captions,
|
||||||
| 1 | Empty and collection state anatomy | More than 70 module-prefixed empty/state class uses remain; only loading, alerts, blockers, and DataGrid empty actions are centralized | A composable `StatePanel`/`CollectionState` covering empty, recoverable error, permission block, partial result, and next action without hiding domain consequences. First distinguish a whole-surface state from a compact empty row or optional-value placeholder. |
|
uppercase terms, or flex-column containment applied to different semantic
|
||||||
| 2 | Filter and search rows | More than 50 filter/search class tokens cover simple text search, facets, popovers, result counts, active filters, bulk selection, and overlay search | Define `FilterBar` only after the input/submit, live filtering, facet, result-summary, and bulk-action accessibility variants have been compared. Plain action placement already uses `ActionToolbar`. |
|
elements. Moving those rules into a component would erase meaning; their
|
||||||
| 3 | Assignment/picker groups | `admin-assignment-grid`: 5 uses in 4 files | Promote a selection/assignment composition only after its list, search, empty, policy, and permission variants are compared; plain geometry can already use `ContentGrid`. |
|
visual values already come from Core tokens. Remaining larger local layouts
|
||||||
| 4 | Repeated domain fact and statistic grids | `campaign-header-grid` (8), `review-flow-fact-grid` (6), `postbox-form-grid` (5), plus smaller families | Compare semantics before promotion. Some can use `ContentGrid` or `DescriptionList`; others intentionally own unequal tracks or workflow visualization. |
|
are deliberately domain-owned:
|
||||||
|
|
||||||
The other custom grids are mostly bounded domain visualizations or unequal-track
|
- unequal-track editors, import mappings and schema/data tables;
|
||||||
editors: conflict mappings, import mapping, campaign review facts, charts,
|
- calendar time grids, charts, graph node shapes and graph edge semantics;
|
||||||
calendar time views, connector synchronization, records lists, and definition
|
- file/mail/postbox/records explorer panes whose interaction contracts differ;
|
||||||
editors. They should remain module-owned unless a second domain demonstrates
|
- timelines, evidence histories, recipient compositions and policy-specific
|
||||||
the same semantics and interaction contract. A shared primitive should not be
|
detail sections;
|
||||||
created merely because two implementations both use CSS Grid.
|
- compact list-row internals that cannot preserve their semantics through
|
||||||
|
`SelectionListItemContent`.
|
||||||
|
|
||||||
## Next Audit
|
A future candidate is promoted only when a new audit identifies repeated
|
||||||
|
structure plus the same responsive, accessibility and interaction contract.
|
||||||
The next bounded slice should compare whole-surface empty states across list,
|
The enforcement script prevents regression for the patterns centralized in
|
||||||
detail, permission, capability, and recoverable-error contexts and promote only
|
this pass and maintains the reviewed dialog-width baseline.
|
||||||
their shared anatomy. Filter/search composition follows after its live versus
|
|
||||||
submitted filtering and bulk-selection behavior is explicit. Assignment grids
|
|
||||||
remain deferred until their permission and policy variants are understood.
|
|
||||||
|
|||||||
@@ -45,12 +45,20 @@ REQUIRED_CONSUMERS = (
|
|||||||
pathlib.Path("govoplan-campaign/webui/src/features/operator/OperatorQueuePage.tsx"),
|
pathlib.Path("govoplan-campaign/webui/src/features/operator/OperatorQueuePage.tsx"),
|
||||||
pathlib.Path("govoplan-campaign/webui/src/features/reports/AggregateReportsPage.tsx"),
|
pathlib.Path("govoplan-campaign/webui/src/features/reports/AggregateReportsPage.tsx"),
|
||||||
pathlib.Path("govoplan-campaign/webui/src/features/templates/TemplatesPage.tsx"),
|
pathlib.Path("govoplan-campaign/webui/src/features/templates/TemplatesPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-access/webui/src/features/admin/AdminPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-core/webui/src/features/settings/SettingsPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-docs/webui/src/features/docs/DocsPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-mail/webui/src/features/mail/MailBouncePage.tsx"),
|
||||||
)
|
)
|
||||||
REQUIRED_WORKSPACE_CONSUMERS = (
|
REQUIRED_WORKSPACE_CONSUMERS = (
|
||||||
pathlib.Path("govoplan-campaign/webui/src/features/campaigns/CampaignWorkspace.tsx"),
|
pathlib.Path("govoplan-campaign/webui/src/features/campaigns/CampaignWorkspace.tsx"),
|
||||||
pathlib.Path("govoplan-campaign/webui/src/features/campaigns/CampaignModulePage.tsx"),
|
pathlib.Path("govoplan-campaign/webui/src/features/campaigns/CampaignModulePage.tsx"),
|
||||||
pathlib.Path("govoplan-campaign/webui/src/features/templates/TemplatesPage.tsx"),
|
pathlib.Path("govoplan-campaign/webui/src/features/templates/TemplatesPage.tsx"),
|
||||||
pathlib.Path("govoplan-approvals/webui/src/features/approvals/ApprovalsPage.tsx"),
|
pathlib.Path("govoplan-approvals/webui/src/features/approvals/ApprovalsPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-access/webui/src/features/admin/AdminPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-core/webui/src/features/settings/SettingsPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-docs/webui/src/features/docs/DocsPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-organizations/webui/src/features/organizations/OrganizationsPage.tsx"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ CENTRAL_COMPONENTS = {
|
|||||||
"ContentGrid": pathlib.Path(
|
"ContentGrid": pathlib.Path(
|
||||||
"govoplan-core/webui/src/components/ContentGrid.tsx"
|
"govoplan-core/webui/src/components/ContentGrid.tsx"
|
||||||
),
|
),
|
||||||
|
"ContentSection": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/ContentSection.tsx"
|
||||||
|
),
|
||||||
"FormGrid": pathlib.Path("govoplan-core/webui/src/components/ContentGrid.tsx"),
|
"FormGrid": pathlib.Path("govoplan-core/webui/src/components/ContentGrid.tsx"),
|
||||||
"FormLayout": pathlib.Path(
|
"FormLayout": pathlib.Path(
|
||||||
"govoplan-core/webui/src/components/ContentGrid.tsx"
|
"govoplan-core/webui/src/components/ContentGrid.tsx"
|
||||||
@@ -68,6 +71,48 @@ CENTRAL_COMPONENTS = {
|
|||||||
"MetricGrid": pathlib.Path(
|
"MetricGrid": pathlib.Path(
|
||||||
"govoplan-core/webui/src/components/MetricGrid.tsx"
|
"govoplan-core/webui/src/components/MetricGrid.tsx"
|
||||||
),
|
),
|
||||||
|
"MetricCard": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/MetricCard.tsx"
|
||||||
|
),
|
||||||
|
"FilterBar": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/FilterBar.tsx"
|
||||||
|
),
|
||||||
|
"StatePanel": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/StatePanel.tsx"
|
||||||
|
),
|
||||||
|
"CountBadge": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/CountBadge.tsx"
|
||||||
|
),
|
||||||
|
"SelectionList": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/SelectionList.tsx"
|
||||||
|
),
|
||||||
|
"SelectionListItem": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/SelectionList.tsx"
|
||||||
|
),
|
||||||
|
"SelectionListItemContent": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/SelectionList.tsx"
|
||||||
|
),
|
||||||
|
"WorkspaceLayout": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/WorkspaceLayout.tsx"
|
||||||
|
),
|
||||||
|
"WorkspaceFrame": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/WorkspaceFrame.tsx"
|
||||||
|
),
|
||||||
|
"DefinitionPalette": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/DefinitionPalette.tsx"
|
||||||
|
),
|
||||||
|
"DefinitionPaletteGroup": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/DefinitionPalette.tsx"
|
||||||
|
),
|
||||||
|
"DefinitionPaletteItem": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/DefinitionPalette.tsx"
|
||||||
|
),
|
||||||
|
"DefinitionNodeIcon": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/DefinitionNodeIcon.tsx"
|
||||||
|
),
|
||||||
|
"FloatingStatus": pathlib.Path(
|
||||||
|
"govoplan-core/webui/src/components/FloatingStatus.tsx"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
REQUIRED_CONSUMERS = {
|
REQUIRED_CONSUMERS = {
|
||||||
"ActionToolbar": (
|
"ActionToolbar": (
|
||||||
@@ -82,6 +127,11 @@ REQUIRED_CONSUMERS = {
|
|||||||
pathlib.Path("govoplan-campaign/webui/src/features/campaigns/GlobalSettingsPage.tsx"),
|
pathlib.Path("govoplan-campaign/webui/src/features/campaigns/GlobalSettingsPage.tsx"),
|
||||||
pathlib.Path("govoplan-notifications/webui/src/features/notifications/NotificationSettingsPanel.tsx"),
|
pathlib.Path("govoplan-notifications/webui/src/features/notifications/NotificationSettingsPanel.tsx"),
|
||||||
),
|
),
|
||||||
|
"ContentSection": (
|
||||||
|
pathlib.Path("govoplan-datasources/webui/src/features/datasources/DatasourcesPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-dist-lists/webui/src/features/distributionLists/DistributionListsPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-templates/webui/src/features/templates/TemplatesPage.tsx"),
|
||||||
|
),
|
||||||
"FormGrid": (
|
"FormGrid": (
|
||||||
pathlib.Path("govoplan-core/webui/src/components/mail/MailServerSettingsPanel.tsx"),
|
pathlib.Path("govoplan-core/webui/src/components/mail/MailServerSettingsPanel.tsx"),
|
||||||
pathlib.Path("govoplan-calendar/webui/src/features/calendar/CalendarEventDialog.tsx"),
|
pathlib.Path("govoplan-calendar/webui/src/features/calendar/CalendarEventDialog.tsx"),
|
||||||
@@ -114,10 +164,91 @@ REQUIRED_CONSUMERS = {
|
|||||||
pathlib.Path("govoplan-campaign/webui/src/features/operator/OperatorQueuePage.tsx"),
|
pathlib.Path("govoplan-campaign/webui/src/features/operator/OperatorQueuePage.tsx"),
|
||||||
pathlib.Path("govoplan-notifications/webui/src/features/notifications/NotificationSummaryWidget.tsx"),
|
pathlib.Path("govoplan-notifications/webui/src/features/notifications/NotificationSummaryWidget.tsx"),
|
||||||
),
|
),
|
||||||
|
"MetricCard": (
|
||||||
|
pathlib.Path("govoplan-admin/webui/src/features/admin/AdminOverviewPanel.tsx"),
|
||||||
|
pathlib.Path("govoplan-approvals/webui/src/features/approvals/ApprovalsPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-campaign/webui/src/features/campaigns/ReviewSendPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-voting/webui/src/features/voting/VotingPage.tsx"),
|
||||||
|
),
|
||||||
|
"FilterBar": (
|
||||||
|
pathlib.Path("govoplan-cases/webui/src/features/cases/CasesPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-dataflow/webui/src/features/dataflow/DataflowPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-records/webui/src/features/records/RecordsPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-tasks/webui/src/features/tasks/TasksPage.tsx"),
|
||||||
|
),
|
||||||
|
"StatePanel": (
|
||||||
|
pathlib.Path("govoplan-committee/webui/src/features/committee/CommitteePage.tsx"),
|
||||||
|
pathlib.Path("govoplan-notifications/webui/src/features/notifications/NotificationCenterPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-postbox/webui/src/features/postbox/PostboxPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-risk-compliance/webui/src/features/riskCompliance/RiskCompliancePage.tsx"),
|
||||||
|
),
|
||||||
|
"CountBadge": (
|
||||||
|
pathlib.Path("govoplan-core/webui/src/layout/Titlebar.tsx"),
|
||||||
|
pathlib.Path("govoplan-mail/webui/src/features/mail/MailboxPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-search/webui/src/features/search/SearchPage.tsx"),
|
||||||
|
),
|
||||||
|
"SelectionListItemContent": (
|
||||||
|
pathlib.Path("govoplan-approvals/webui/src/features/approvals/ApprovalsPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-datasources/webui/src/features/datasources/DatasourcesPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-voting/webui/src/features/voting/VotingPage.tsx"),
|
||||||
|
),
|
||||||
|
"WorkspaceLayout": (
|
||||||
|
pathlib.Path("govoplan-dataflow/webui/src/features/dataflow/DataflowPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-notifications/webui/src/features/notifications/NotificationCenterPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-workflow/webui/src/features/workflow/WorkflowPage.tsx"),
|
||||||
|
),
|
||||||
|
"WorkspaceFrame": (
|
||||||
|
pathlib.Path("govoplan-cases/webui/src/features/cases/CasesPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-portal/webui/src/features/portal/PortalPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-records/webui/src/features/records/RecordsPage.tsx"),
|
||||||
|
),
|
||||||
|
"DefinitionPalette": (
|
||||||
|
pathlib.Path("govoplan-dataflow/webui/src/features/dataflow/DataflowPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-workflow/webui/src/features/workflow/WorkflowPage.tsx"),
|
||||||
|
),
|
||||||
|
"DefinitionNodeIcon": (
|
||||||
|
pathlib.Path("govoplan-dataflow/webui/src/features/dataflow/DataflowNode.tsx"),
|
||||||
|
pathlib.Path("govoplan-workflow/webui/src/features/workflow/WorkflowNode.tsx"),
|
||||||
|
),
|
||||||
|
"FloatingStatus": (
|
||||||
|
pathlib.Path("govoplan-dataflow/webui/src/features/dataflow/DataflowPage.tsx"),
|
||||||
|
pathlib.Path("govoplan-workflow/webui/src/features/workflow/WorkflowPage.tsx"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
LEGACY_CSS_SELECTOR = re.compile(
|
LEGACY_CSS_SELECTOR = re.compile(
|
||||||
r"(?<![-\w])\.(?:admin-details-grid|detail-list|metric-grid)(?![-\w])"
|
r"(?<![-\w])\.(?:admin-details-grid|detail-list|metric-grid)(?![-\w])"
|
||||||
)
|
)
|
||||||
|
RETIRED_LOCAL_CSS_CLASSES = {
|
||||||
|
"admin-assignment-grid",
|
||||||
|
"approval-metrics",
|
||||||
|
"dataflow-node-count",
|
||||||
|
"dataflow-shell",
|
||||||
|
"datasources-metrics",
|
||||||
|
"datasources-shell",
|
||||||
|
"dist-lists-metrics",
|
||||||
|
"dist-lists-shell",
|
||||||
|
"notifications-count",
|
||||||
|
"notifications-empty-state",
|
||||||
|
"notifications-shell",
|
||||||
|
"review-flow-execution-summary",
|
||||||
|
"review-flow-fact-grid",
|
||||||
|
"risk-metrics",
|
||||||
|
"search-filter-count",
|
||||||
|
"tasks-empty-detail",
|
||||||
|
"tasks-shell",
|
||||||
|
"templates-shell",
|
||||||
|
"voting-metrics",
|
||||||
|
"voting-shell",
|
||||||
|
"workflow-shell",
|
||||||
|
"dataflow-palette-items",
|
||||||
|
"workflow-palette-items",
|
||||||
|
"dataflow-working-indicator",
|
||||||
|
"workflow-working-indicator",
|
||||||
|
"datasources-detail-section",
|
||||||
|
"dist-lists-section",
|
||||||
|
"templates-section",
|
||||||
|
}
|
||||||
|
LOCAL_METRIC_HELPER = re.compile(r"\b(?:function\s+Metric\b|const\s+Metric\s*=)")
|
||||||
CSS_BLOCK = re.compile(r"([^{}]+)\{([^{}]*)\}")
|
CSS_BLOCK = re.compile(r"([^{}]+)\{([^{}]*)\}")
|
||||||
CSS_COMMENT = re.compile(r"/\*.*?\*/", re.DOTALL)
|
CSS_COMMENT = re.compile(r"/\*.*?\*/", re.DOTALL)
|
||||||
DIALOG_CLASS = re.compile(r"\.([A-Za-z0-9_-]*(?:dialog|modal)[A-Za-z0-9_-]*)", re.IGNORECASE)
|
DIALOG_CLASS = re.compile(r"\.([A-Za-z0-9_-]*(?:dialog|modal)[A-Za-z0-9_-]*)", re.IGNORECASE)
|
||||||
@@ -234,10 +365,16 @@ def main() -> int:
|
|||||||
)
|
)
|
||||||
|
|
||||||
for path, content in style_text.items():
|
for path, content in style_text.items():
|
||||||
legacy_match = LEGACY_CSS_SELECTOR.search(CSS_COMMENT.sub("", content))
|
uncommented = CSS_COMMENT.sub("", content)
|
||||||
|
legacy_match = LEGACY_CSS_SELECTOR.search(uncommented)
|
||||||
if legacy_match:
|
if legacy_match:
|
||||||
line = content.count("\n", 0, legacy_match.start()) + 1
|
line = content.count("\n", 0, legacy_match.start()) + 1
|
||||||
errors.append(f"Legacy shared layout selector is not allowed: {path}:{line}")
|
errors.append(f"Legacy shared layout selector is not allowed: {path}:{line}")
|
||||||
|
for class_name in sorted(RETIRED_LOCAL_CSS_CLASSES):
|
||||||
|
retired = re.search(rf"(?<![-\w])\.{re.escape(class_name)}(?![-\w])\s*(?:,|\{{)", uncommented)
|
||||||
|
if retired:
|
||||||
|
line = content.count("\n", 0, retired.start()) + 1
|
||||||
|
errors.append(f"Retired module-local shared anatomy selector is not allowed: {path}:{line} ({class_name})")
|
||||||
|
|
||||||
dialog_exceptions = dialog_width_exceptions(style_text)
|
dialog_exceptions = dialog_width_exceptions(style_text)
|
||||||
baseline = exception_baseline()
|
baseline = exception_baseline()
|
||||||
@@ -259,6 +396,10 @@ def main() -> int:
|
|||||||
if path != owner and definition.search(content):
|
if path != owner and definition.search(content):
|
||||||
errors.append(f"Module-local {name} definition is not allowed: {path}")
|
errors.append(f"Module-local {name} definition is not allowed: {path}")
|
||||||
|
|
||||||
|
for path, content in source_text.items():
|
||||||
|
if LOCAL_METRIC_HELPER.search(content):
|
||||||
|
errors.append(f"Module-local Metric helper is not allowed; compose MetricCard directly: {path}")
|
||||||
|
|
||||||
usage_counts: dict[str, int] = {}
|
usage_counts: dict[str, int] = {}
|
||||||
for name in CENTRAL_COMPONENTS:
|
for name in CENTRAL_COMPONENTS:
|
||||||
usage = re.compile(rf"<{name}\b")
|
usage = re.compile(rf"<{name}\b")
|
||||||
@@ -291,7 +432,7 @@ def main() -> int:
|
|||||||
|
|
||||||
migrated = ", ".join(
|
migrated = ", ".join(
|
||||||
f"{name}={usage_counts[name]} files"
|
f"{name}={usage_counts[name]} files"
|
||||||
for name in ("ActionToolbar", "ContentGrid", "FormGrid", "FormSection", "DialogForm", "DialogSection", "MetricGrid", "DescriptionList")
|
for name in ("ActionToolbar", "WorkspaceFrame", "WorkspaceLayout", "FilterBar", "StatePanel", "SelectionList", "CountBadge", "DefinitionPalette", "DefinitionNodeIcon", "FloatingStatus", "ContentSection", "ContentGrid", "FormGrid", "FormSection", "DialogForm", "DialogSection", "MetricGrid", "MetricCard", "DescriptionList")
|
||||||
)
|
)
|
||||||
print(f"Shared WebUI primitive contract passed: {migrated}; {len(dialog_exceptions)} reviewed dialog width exceptions; no raw legacy anatomy.")
|
print(f"Shared WebUI primitive contract passed: {migrated}; {len(dialog_exceptions)} reviewed dialog width exceptions; no raw legacy anatomy.")
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -1,6 +1,2 @@
|
|||||||
# Existing raw page frames. Remove an entry when that surface adopts the Core
|
# Raw page-frame exceptions. Keep this file empty: module pages use the Core
|
||||||
# PageLayout contract. New entries are rejected by check-shared-webui-layouts.py.
|
# PageLayout contract and new exceptions are rejected by the layout checker.
|
||||||
govoplan-access/webui/src/features/admin/AdminPage.tsx
|
|
||||||
govoplan-core/webui/src/features/settings/SettingsPage.tsx
|
|
||||||
govoplan-docs/webui/src/features/docs/DocsPage.tsx
|
|
||||||
govoplan-mail/webui/src/features/mail/MailBouncePage.tsx
|
|
||||||
|
|||||||
@@ -1,6 +1,2 @@
|
|||||||
# Existing raw full-canvas workspaces. Remove an entry when that surface adopts
|
# Raw workspace exceptions. Keep this file empty: module workspaces use the Core
|
||||||
# WorkspaceLayout. New entries are rejected by check-shared-webui-layouts.py.
|
# WorkspaceLayout contract and new exceptions are rejected by the layout checker.
|
||||||
govoplan-access/webui/src/features/admin/AdminPage.tsx
|
|
||||||
govoplan-core/webui/src/features/settings/SettingsPage.tsx
|
|
||||||
govoplan-docs/webui/src/features/docs/DocsPage.tsx
|
|
||||||
govoplan-organizations/webui/src/features/organizations/OrganizationsPage.tsx
|
|
||||||
|
|||||||
Reference in New Issue
Block a user