feat(N4): Restliche Module — Tasks/Kommunikation/Wiki/Reports/Agents/Tags/Search + Navigation + Dashboard-Schnittstelle (#368)
Check Cross-Plugin Imports / check (push) Has been cancelled

- Scope-Deklarationen: tasks only_mine, kommunikation conversation_ids, wiki category_ids (NEUE contracts.py), report_generator template_ids, automation agent_ids (module_key agents), tags tag_ids, unified_search entity_types dynamisch aus Provider-Registry
- Core-Beiträge: navigation default_route (Startseite) + dashboard widget_app_ids (Widget-TYP-Angebot, Layout bleibt Phase M)
- Backend-Filter (additive UND): /tasks (only_mine), /comm/conversations, /wiki/articles+/categories (Subtree), /reports/print-templates, /agents, /tags, /search GET+POST (entity_types-Schnitt), /miniapps?host=dashboard
- apply_entity_type_scope-Helper (requested ∧ scope)
- Frontend: WorkspaceSwitcher default_route-Navigation, Sidebar workspace-menu_order-Sortierung, workspaceStore moduleMenuOrder()
- Tests: 18/18 Deklarationen + 11/11 Filter (TDD), Frontend 2/2 + Store 18/18, tsc clean, Build OK
- Regression 64 passed (4 Kombi-Failures = Suite-Isolation, solo-bewiesen); Checker 0; Ruff = Vorbestand (Stash-bewiesen)
This commit is contained in:
Agent Zero
2026-09-01 23:23:15 +02:00
parent 26506a5027
commit 03dd477899
26 changed files with 1335 additions and 24 deletions
+85
View File
@@ -155,6 +155,23 @@ async def expand_folder_scope(
return result
def apply_entity_type_scope(
requested: list[str] | None,
scope_entity_types: Any,
) -> list[str] | None:
"""Intersect requested search entity types with the workspace scope (N4).
Pure AND: the effective set is requested ∧ scope. ``None`` means "no
restriction" on either side (search all). An empty result list means the
search legitimately yields nothing (scope excludes every requested type).
"""
if not isinstance(scope_entity_types, list) or not scope_entity_types:
return requested
if requested is None:
return list(scope_entity_types)
return [et for et in requested if et in set(scope_entity_types)]
def scope_uuid_set(raw: Any) -> set[uuid.UUID] | None:
"""Convert a scope dimension value into a set of UUIDs (Phase N3).
@@ -244,4 +261,72 @@ def get_scope_definitions() -> dict[str, list[dict[str, Any]]]:
modules.setdefault(parsed.module_key, []).extend(
dimension.model_dump() for dimension in parsed.dimensions
)
# ─── Core contributions (Phase N4) ──────────────────────────
# Core-owned modules (no plugin owns them) contribute through the same
# registry so the N2 editor renders them automatically.
for core_contribution in _core_scope_contributions():
parsed = _parse_contribution("core", core_contribution)
if parsed is None:
continue
modules.setdefault(parsed.module_key, []).extend(
dimension.model_dump() for dimension in parsed.dimensions
)
return modules
def _core_scope_contributions() -> list[dict[str, Any]]:
"""Scope contributions for core-owned modules (Phase N4).
- navigation: default_route per workspace ("Startseite") — where the
workspace switcher navigates to.
- dashboard: widget_app_ids — the workspace limits the OFFERED widget
types (admin context, workspace_widgets boundary). The personal
layout stays user-owned (Phase M boundary, user-corrected split).
"""
from app.core.permission_registry import CORE_PERMISSIONS
route_options = [
{"value": "/", "label": "Dashboard"},
{"value": "/contacts", "label": "Kontakte"},
]
# Every core permission module with a matching frontend route contributes
# a navigation option (dynamic, registry-derived — no hardcoded list).
known_routes = {"/", "/contacts", "/tasks", "/calendar", "/mail", "/dms", "/wiki", "/communication", "/reports", "/tags", "/search", "/agents", "/workflows"}
for perm in CORE_PERMISSIONS:
module = perm.get("module", "")
route = f"/{module}"
if route in known_routes and all(o["value"] != route for o in route_options):
route_options.append({"value": route, "label": module.title()})
return [
{
"module_key": "navigation",
"dimensions": [
{
"key": "default_route",
"label": "Startseite",
"control": "select",
"options": route_options,
"default": "/",
},
],
},
{
"module_key": "dashboard",
"dimensions": [
{
"key": "widget_app_ids",
"label": "Verfügbare Widgets",
"control": "multiselect",
"options": [],
"value_source": {
"endpoint": "/api/v1/miniapps?host=dashboard",
"items_path": "items",
"value_key": "app_id",
"label_key": "name",
},
},
],
},
]