feat: add View-scoped quick access presentation

This commit is contained in:
2026-08-19 18:47:46 +02:00
parent cdbf2bf80b
commit 877dced738
5 changed files with 125 additions and 8 deletions
+21 -2
View File
@@ -306,12 +306,30 @@ manifest = ModuleManifest(
"so they can always be inspected and changed. The titlebar eye "
"opens the selector and is accented while a specialized View is "
"active. Hidden functions remain protected by their normal "
"permission checks."
"permission checks. A revision may recommend Quick Access tools or "
"focus the rail to a task-specific subset. These fields affect presentation "
"only: unavailable, context-incompatible, or unauthorized tools stay absent, "
"and an unusable focus falls back to the normal effective rail. Workflow uses "
"the same behavior by resolving the exact immutable View revision."
),
layer="available",
documentation_types=("admin", "user"),
audience=("administrator", "power_user", "workflow_designer"),
related_modules=("access", "admin", "policy", "workflow_engine"),
translations={
"de": {
"title": "Aufgabenbezogene Ansichten",
"summary": "Die sichtbare Oberflaeche auf die fuer eine Aufgabe benoetigten Module und Funktionen begrenzen, ohne Berechtigungen zu aendern.",
"body": (
"Ansichten sind versionierte Darstellungsprojektionen. Module melden ihre waehlbaren Oberflaechen ueber "
"den Plattformvertrag. Eine Revision darf Schnellzugriffswerkzeuge empfehlen oder die Leiste auf eine "
"aufgabenbezogene Teilmenge fokussieren. Das aendert nur die Darstellung: nicht verfuegbare, unpassende "
"oder unberechtigte Werkzeuge bleiben verborgen; ein nicht nutzbarer Fokus faellt auf die normale wirksame "
"Leiste zurueck. Workflow erhaelt dasselbe Verhalten durch die genaue unveraenderliche Ansichtsversion. "
"Ausgeblendete Funktionen bleiben durch ihre normalen Berechtigungspruefungen geschuetzt."
),
}
},
links=(
DocumentationLink(
label="Views administration",
@@ -358,7 +376,8 @@ manifest = ModuleManifest(
"Inherited definitions or assignments must be changed in their owning scope."
" Product-area grouping, ordering, and labels are presentation metadata in the same revision; "
"they cannot expose a hidden surface or grant authority. Grouped navigation is the sensible default, "
"while flat navigation preserves the complete authorized tool rail."
"while flat navigation preserves the complete authorized tool rail. Quick Access recommendation and "
"focus ids are likewise revisioned presentation metadata and never authorize a contribution."
),
documentation_types=("admin",),
audience=("administrator", "power_user", "workflow_designer"),
+33 -1
View File
@@ -52,8 +52,15 @@ LOCKOUT_ADMIN_SURFACE_IDS = {
}
_KEY_RE = re.compile(r"[^a-z0-9]+")
_PRESENTATION_ID_RE = re.compile(r"^[a-z][a-z0-9-]{1,79}$")
_QUICK_ACCESS_TOOL_ID_RE = re.compile(r"^[a-z][a-z0-9_-]*(?:\.[a-z0-9_-]+)+$")
_PRESENTATION_KEYS = frozenset(
{"navigation_mode", "product_area_order", "product_area_labels"}
{
"navigation_mode",
"product_area_order",
"product_area_labels",
"quick_access_recommended_tool_ids",
"quick_access_focused_tool_ids",
}
)
@@ -419,6 +426,31 @@ def normalize_view_presentation(
labels[area_id] = label
normalized["product_area_labels"] = labels
for field_name, label in (
("quick_access_recommended_tool_ids", "recommended Quick Access tools"),
("quick_access_focused_tool_ids", "focused Quick Access tools"),
):
raw_tool_ids = value.get(field_name)
if raw_tool_ids is None:
continue
if not isinstance(raw_tool_ids, list) or len(raw_tool_ids) > 100:
raise ViewsValidationError(
f"View {label} must be a list of at most 100 ids"
)
tool_ids: list[str] = []
for raw_id in raw_tool_ids:
tool_id = str(raw_id).strip()
if not _QUICK_ACCESS_TOOL_ID_RE.fullmatch(tool_id):
raise ViewsValidationError(
f"Invalid Quick Access tool id in View presentation: {tool_id!r}"
)
if tool_id in tool_ids:
raise ViewsValidationError(
f"Duplicate Quick Access tool id in View presentation: {tool_id}"
)
tool_ids.append(tool_id)
normalized[field_name] = tool_ids
if available_product_area_ids is not None:
available = {str(item) for item in available_product_area_ids}
referenced = set(normalized.get("product_area_order", ())) | set(