Add cross-module operational and interaction contracts

This commit is contained in:
2026-07-31 22:48:07 +02:00
parent 4cb334c912
commit b65b48832b
23 changed files with 593 additions and 13 deletions
+7 -2
View File
@@ -1,6 +1,6 @@
import { useRef, useState, useEffect } from "react";
import { Bell, Check, LogOut, Settings, UserCircle } from "lucide-react";
import type { ApiSettings, AuthInfo, AuthTenantMembership, AuthUpdate, LoginResponse, SearchRuntimeUiCapability, ViewsRuntimeUiCapability } from "../types";
import type { ActingContextRuntimeUiCapability, ApiSettings, AuthInfo, AuthTenantMembership, AuthUpdate, LoginResponse, SearchRuntimeUiCapability, ViewsRuntimeUiCapability } from "../types";
import HelpMenu from "./HelpMenu";
import LanguageMenu from "./LanguageMenu";
import LoginModal from "../features/auth/LoginModal";
@@ -37,6 +37,8 @@ export default function Titlebar({ settings, auth, onAuthChange, maintenanceMode
const projection = useEffectiveView();
const viewsRuntime = usePlatformUiCapability<ViewsRuntimeUiCapability>("views.runtime");
const ViewSelector = viewsRuntime?.Selector ?? null;
const actingRuntime = usePlatformUiCapability<ActingContextRuntimeUiCapability>("access.actingContext");
const ActingContextSelector = actingRuntime?.Selector ?? null;
const searchRuntime = usePlatformUiCapability<SearchRuntimeUiCapability>("search.runtime");
const GlobalSearch = searchRuntime?.GlobalSearch ?? null;
const canUseGlobalSearch = Boolean(
@@ -60,7 +62,7 @@ export default function Titlebar({ settings, auth, onAuthChange, maintenanceMode
);
const showTenantControl = Boolean(activeTenant && (canSwitchTenant || canAdministerTenants));
const showContextSelectors = Boolean(
auth && ((activeTenant && showTenantControl) || ViewSelector)
auth && ((activeTenant && showTenantControl) || ViewSelector || ActingContextSelector)
);
const notificationsAvailable = modules.some((module) => module.id === "notifications" && module.routes?.some((route) => route.path === "/notifications"));
const notificationSummary = useSharedNotificationSummary(settings, {
@@ -206,6 +208,9 @@ export default function Titlebar({ settings, auth, onAuthChange, maintenanceMode
{ViewSelector &&
<ViewSelector settings={settings} auth={auth} projection={projection} />
}
{ActingContextSelector &&
<ActingContextSelector settings={settings} auth={auth} onAuthChange={onAuthChange} />
}
</div>
}
</div>
+5
View File
@@ -33,6 +33,9 @@
.titlebar-actions { grid-column: 2; display: flex; align-items: center; justify-self: end; min-width: 0; gap: 10px; }
.titlebar.has-global-search .titlebar-actions { grid-column: 3; }
.titlebar-context-selectors { display: flex; align-items: center; min-width: 0; gap: 12px; }
.acting-context-selector { display: inline-flex; align-items: center; min-width: 0; gap: 7px; color: var(--muted); }
.acting-context-selector select { width: auto; max-width: 260px; min-height: 32px; border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--text-strong); font: inherit; font-weight: 700; padding: 5px 24px 5px 8px; }
.acting-context-selector select:hover, .acting-context-selector select:focus-visible { background: var(--titlebar-hover-bg); }
.tenant-selector { height: 40px; min-width: 210px; border: var(--border-line); border-radius: var(--radius-sm); background: var(--surface); display: flex; align-items: center; padding: 0 12px; gap: 5px; box-shadow: var(--shadow-xs); }
.tenant-label, .tenant-caret, .muted { color: var(--muted); }
.block { display: block; }
@@ -265,6 +268,8 @@
.docs-outline { padding-top: 22px; }
.docs-sidebar-header { padding: 0 16px 16px; }
.docs-sidebar-header .section-title { padding: 0 6px 12px; }
.docs-version-selector { display: grid; gap: 5px; margin-top: 12px; color: var(--muted); font-size: 12px; font-weight: 700; }
.docs-version-selector select { width: 100%; min-height: 34px; border: var(--border-line); border-radius: var(--radius-sm); background: var(--input-bg); color: var(--text); font: inherit; padding: 5px 8px; }
.docs-audience-toggle { width: 100%; }
.docs-tree { display: grid; gap: 2px; padding: 0 10px 24px; }
.docs-tree-node { min-width: 0; }
+11
View File
@@ -92,11 +92,22 @@ export type PrincipalContext = {
api_key_id?: string | null;
session_id?: string | null;
service_account_id?: string | null;
acting_assignment_id?: string | null;
acting_for_account_id?: string | null;
email?: string | null;
display_name?: string | null;
};
export type ActingContextSelectorProps = {
settings: ApiSettings;
auth: AuthInfo;
onAuthChange: (auth: AuthUpdate | null, accessToken?: string) => void;
};
export type ActingContextRuntimeUiCapability = {
Selector: ComponentType<ActingContextSelectorProps>;
};
export type AuthInfo = {
user: AuthUser;
// Backwards-compatible active tenant alias returned by older/newer APIs.