feat(webui): govern actions, quick access, and metrics

Refs #264, #285, #289
This commit is contained in:
2026-08-19 18:47:46 +02:00
parent 41db78c201
commit ffaab543d2
31 changed files with 753 additions and 125 deletions
+25 -4
View File
@@ -21,6 +21,7 @@ import SelectionList, { SelectionListItem, SelectionListItemContent } from "../s
import StatePanel from "../src/components/StatePanel";
import WorkspaceLayout from "../src/components/WorkspaceLayout";
import WorkspaceFrame from "../src/components/WorkspaceFrame";
import WorkspaceActionBar from "../src/components/WorkspaceActionBar";
// @ts-expect-error Refreshable pages must provide a Reload action.
const refreshableWithoutReload = <PageActionBar variant="detail" refreshable />;
@@ -52,9 +53,9 @@ const editorActionBarMarkup = renderToStaticMarkup(
<PageActionBar
variant="editor"
refreshable
dirty
state="dirty"
label="Editor actions"
reloadAction={<button type="button">Reload</button>}
reloadAction={{ onReload: () => undefined, label: "Reload" }}
contextActions={<button type="button">Preview</button>}
helpAction={<button type="button">Help</button>}
destructiveActions={<button type="button">Delete</button>}
@@ -73,13 +74,14 @@ assert(editorActionBarMarkup.indexOf('data-page-action-slot="reload"') < editorA
assert(editorActionBarMarkup.indexOf('data-page-action-slot="help"') < editorActionBarMarkup.indexOf('data-page-action-slot="discard"'), "help precedes editor persistence actions");
assert(editorActionBarMarkup.indexOf('data-page-action-slot="destructive"') < editorActionBarMarkup.indexOf('data-page-action-slot="discard"'), "destructive editor actions are separated from persistence actions");
assert(editorActionBarMarkup.includes('data-page-action-separation="destructive"'), "destructive actions expose their visual boundary");
assert(editorActionBarMarkup.includes('role="group" aria-label="Destructive actions"'), "destructive actions expose an accessible named group");
assert(editorActionBarMarkup.indexOf('data-page-action-slot="discard"') < editorActionBarMarkup.indexOf('data-page-action-slot="save"'), "save remains the far-right editor action");
const cleanEditorActionBarMarkup = renderToStaticMarkup(
<PlatformLanguageProvider>
<PageActionBar
variant="editor"
dirty={false}
state="clean"
discardAction={{ label: "Discard" }}
saveAction={{ label: "Save" }}
/>
@@ -94,13 +96,32 @@ const collectionActionBarMarkup = renderToStaticMarkup(
<PageActionBar
variant="collection"
refreshable
reloadAction={<button type="button">Reload</button>}
reloadAction={{ onReload: () => undefined, label: "Reload" }}
createAction={<button type="button">Create</button>}
/>
</PlatformLanguageProvider>
);
assert(collectionActionBarMarkup.indexOf('data-page-action-slot="reload"') < collectionActionBarMarkup.indexOf('data-page-action-slot="create"'), "collection creation remains the far-right action");
const workspaceEditorActionBarMarkup = renderToStaticMarkup(
<PlatformLanguageProvider>
<WorkspaceActionBar
scope="editor-pane"
variant="editor"
state="invalid"
refreshable
reloadAction={{ onReload: () => undefined, loading: true }}
destructiveActions={<button type="button">Delete</button>}
discardAction={{ label: "Discard" }}
saveAction={{ label: "Save" }}
/>
</PlatformLanguageProvider>
);
assert(workspaceEditorActionBarMarkup.includes('data-workspace-action-scope="editor-pane"'), "workspace actions expose their pane scope");
assert(workspaceEditorActionBarMarkup.includes('data-page-refresh-state="reloading"'), "reload activity is centrally projected");
assert(workspaceEditorActionBarMarkup.includes('data-page-dirty-state="invalid"'), "invalid editor state remains visible");
assert((workspaceEditorActionBarMarkup.match(/disabled=""/g) ?? []).length >= 2, "invalid editors disable Reload while active and Save while invalid");
const gridMarkup = renderToStaticMarkup(
<ContentGrid columns={3} gap="compact" collapseAt="wide">
<GridItem>A</GridItem>