feat: preserve full-page launch context

This commit is contained in:
2026-08-18 21:32:25 +02:00
parent da29de71e5
commit d44ed78e5c
4 changed files with 66 additions and 14 deletions
+7 -2
View File
@@ -37,5 +37,10 @@ owner module.
Quick Access is not an authorization boundary. Every contribution keeps its
own permission requirements and View surface. Full-page routes remain the
canonical fallback. Disabling this module removes the rail without making any
domain state unavailable through its owning module.
canonical fallback. Launch context version 1 contains only the tenant/account
identity, safe active-object reference, acting-assignment identifiers,
temporal selection, exact View identity, and return route. Cross-tenant object
references are discarded and unknown context versions are ignored. The
destination reauthorizes every read and effect. Disabling this module removes
the rail without making any domain state unavailable through its owning
module.
@@ -121,6 +121,9 @@ DOCUMENTATION = (
"Messages combines enabled Mail, Postbox, and future chat contributions in one overlay. "
"Personal settings can reorder or hide items that remain available under system, tenant, "
"permission, and View policy. Every item retains a link to its complete owning page. "
"That launch carries a versioned, bounded reference to the current object, acting assignment, "
"temporal selection, View revision, and return location. The destination rechecks access, and "
"the breadcrumb return action restores the originating route without copying protected content."
),
layer="configured",
documentation_types=("user",),
@@ -140,6 +143,8 @@ DOCUMENTATION = (
"Eine Kategorie in der rechten Leiste oeffnet kompakte Werkzeuge, ohne die aktuelle Aufgabe zu verlassen. "
"Nachrichten fuehrt Beitraege aus Mail, Postfach und kuenftigen Chat-Modulen in einer Einblendung zusammen. "
"Persoenliche Einstellungen koennen alle durch System, Mandant, Berechtigungen und Ansicht zugelassenen Eintraege ordnen oder ausblenden. "
"Beim Oeffnen der vollstaendigen Seite werden nur versionierte, begrenzte Verweise auf Objekt, handelnde Zuordnung, "
"Zeitbezug, Ansichtsversion und Ruecksprungort uebergeben. Das Ziel prueft den Zugriff erneut."
),
}
},
@@ -159,6 +164,7 @@ DOCUMENTATION = (
"The catalogue follows installed module registrations. System settings constrain tenants; "
"tenant settings constrain users. An item may remain available, be blocked, or be forced. "
"Views and permissions form additional ceilings and Quick Access never grants access to domain data. "
"Launch context version 1 rejects cross-tenant active-object references and unknown context versions fail closed."
),
layer="configured",
documentation_types=("admin",),
@@ -171,6 +177,7 @@ DOCUMENTATION = (
"Der Katalog folgt den Registrierungen installierter Module. Systemeinstellungen begrenzen Mandanten, "
"Mandanteneinstellungen begrenzen Benutzer. Ein Eintrag kann verfuegbar, gesperrt oder erzwungen sein. "
"Ansichten und Berechtigungen bilden weitere Grenzen; Schnellzugriff erteilt selbst keinen Datenzugriff. "
"Startkontext Version 1 verwirft mandantenfremde Objektverweise; unbekannte Versionen werden abgelehnt."
),
}
},
+30 -3
View File
@@ -13,6 +13,9 @@ import {
DismissibleAlert,
IconButton,
LoadingFrame,
dispatchQuickAccessResult,
quickAccessLaunchState,
useGuardedNavigate,
usePlatformLanguage,
usePlatformUiCapabilities,
type QuickAccessRailProps,
@@ -32,7 +35,7 @@ const iconByCategory: Record<string, LucideIcon> = {
files: Files
};
export default function QuickAccessRail({ settings, auth, tools }: QuickAccessRailProps) {
export default function QuickAccessRail({ settings, auth, tools, launchContext }: QuickAccessRailProps) {
const [effective, setEffective] = useState<EffectiveQuickAccess | null>(null);
const [activeCategoryId, setActiveCategoryId] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
@@ -40,6 +43,7 @@ export default function QuickAccessRail({ settings, auth, tools }: QuickAccessRa
const drawerRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLButtonElement | null>(null);
const location = useLocation();
const navigate = useGuardedNavigate();
const contributions = usePlatformUiCapabilities<QuickAccessToolsUiCapability>("quickAccess.tools");
const { translateText } = usePlatformLanguage();
const renderers = useMemo(
@@ -125,6 +129,11 @@ export default function QuickAccessRail({ settings, auth, tools }: QuickAccessRa
setActiveCategoryId(category.id);
}
function openFullPage(path: string) {
closeDrawer(false);
navigate(path, { state: quickAccessLaunchState(launchContext) });
}
return (
<>
<aside className="quick-access-rail" aria-label="i18n:govoplan-quick-access.quick_access">
@@ -187,10 +196,28 @@ export default function QuickAccessRail({ settings, auth, tools }: QuickAccessRa
<strong>{translateText(tool.label)}</strong>
{tool.description ? <small>{translateText(tool.description)}</small> : null}
</div>
{tool.full_page_path ? <Link to={tool.full_page_path} onClick={() => closeDrawer(false)}>i18n:govoplan-quick-access.open_full_page</Link> : null}
{tool.full_page_path ? (
<button
type="button"
className="quick-access-full-page-link"
onClick={() => openFullPage(tool.full_page_path!)}
>
i18n:govoplan-quick-access.open_full_page
</button>
) : null}
</div>
{renderer
? renderer.render({ settings, auth, close: () => closeDrawer(), active: true })
? renderer.render({
settings,
auth,
close: () => closeDrawer(),
active: true,
launchContext,
complete: (result) => {
dispatchQuickAccessResult(tool.id, launchContext, result);
closeDrawer();
}
})
: <p className="quick-access-tool-unavailable">i18n:govoplan-quick-access.compact_view_unavailable</p>}
</section>
);
+18 -5
View File
@@ -34,7 +34,7 @@
display: grid;
place-items: center;
border: 0;
border-radius: 4px;
border-radius: var(--radius-sm);
color: var(--muted);
background: transparent;
cursor: pointer;
@@ -61,7 +61,7 @@
margin: 11px auto;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
border-radius: var(--radius-round);
animation: quick-access-spin 0.8s linear infinite;
}
@@ -76,7 +76,7 @@
display: flex;
flex-direction: column;
border-left: var(--border-line);
box-shadow: -12px 0 30px rgb(0 0 0 / 18%);
box-shadow: var(--shadow-drawer-side);
background: var(--panel);
}
@@ -141,12 +141,25 @@
justify-content: space-between;
}
.quick-access-tool-heading > a {
.quick-access-tool-heading > a,
.quick-access-full-page-link {
flex: 0 0 auto;
color: var(--accent);
font-size: 12px;
}
.quick-access-full-page-link {
border: 0;
background: transparent;
font-family: inherit;
font-weight: 700;
padding: 0;
cursor: pointer;
}
.quick-access-full-page-link:hover,
.quick-access-full-page-link:focus-visible { text-decoration: underline; }
.quick-access-settings {
min-width: 0;
display: grid;
@@ -250,7 +263,7 @@
height: min(70vh, 680px);
border-top: var(--border-line);
border-left: 0;
box-shadow: 0 -12px 30px rgb(0 0 0 / 18%);
box-shadow: var(--shadow-drawer-bottom);
}
.quick-access-preference-row {