feat: add UI conformance and launch context

This commit is contained in:
2026-08-18 21:32:25 +02:00
parent 7685a103e8
commit 8a925782ab
35 changed files with 1125 additions and 150 deletions
+126
View File
@@ -0,0 +1,126 @@
import type {
ActiveObjectReference,
AuthInfo,
EffectiveViewProjection,
QuickAccessLaunchContext,
QuickAccessResult
} from "../types";
import type { TemporalDataSelection } from "./temporal";
export const QUICK_ACCESS_LAUNCH_CONTEXT_VERSION = "1" as const;
export const QUICK_ACCESS_LAUNCH_STATE_KEY = "govoplanQuickAccessLaunch";
export const QUICK_ACCESS_RESULT_EVENT = "govoplan:quick-access-result";
export type QuickAccessLaunchContextInput = {
pathname: string;
search?: string;
hash?: string;
historyIndex?: number | null;
auth: AuthInfo;
activeObject?: ActiveObjectReference | null;
temporalContext: TemporalDataSelection;
viewContext?: EffectiveViewProjection | null;
};
export function createQuickAccessLaunchContext({
pathname,
search = "",
hash = "",
historyIndex = null,
auth,
activeObject = null,
temporalContext,
viewContext = null
}: QuickAccessLaunchContextInput): QuickAccessLaunchContext {
const tenant = auth.active_tenant ?? auth.tenant;
const principal = auth.principal;
return {
contractVersion: QUICK_ACCESS_LAUNCH_CONTEXT_VERSION,
origin: {
pathname: normalizePathname(pathname),
search: normalizeSearch(search),
hash: normalizeHash(hash),
historyIndex: Number.isInteger(historyIndex) ? historyIndex ?? null : null
},
tenantId: tenant.id,
accountId: auth.user.account_id,
activeObject: activeObject?.tenantId === tenant.id ? activeObject : null,
actingContext: principal?.acting_assignment_id || principal?.acting_for_account_id
? {
assignmentId: principal.acting_assignment_id ?? null,
actingForAccountId: principal.acting_for_account_id ?? null
}
: null,
temporalContext: {
validityMode: temporalContext.validityMode,
validAt: temporalContext.validAt ?? null,
recordedAt: temporalContext.recordedAt ?? null
},
viewContext: viewContext?.activeViewId
? {
viewId: viewContext.activeViewId,
revisionId: viewContext.activeRevisionId,
name: viewContext.activeViewName
}
: null
};
}
export function quickAccessLaunchState(
context: QuickAccessLaunchContext
): Record<string, QuickAccessLaunchContext> {
return { [QUICK_ACCESS_LAUNCH_STATE_KEY]: context };
}
export function quickAccessLaunchContextFromState(
value: unknown
): QuickAccessLaunchContext | null {
if (!isRecord(value)) return null;
const candidate = value[QUICK_ACCESS_LAUNCH_STATE_KEY];
if (!isRecord(candidate) || candidate.contractVersion !== QUICK_ACCESS_LAUNCH_CONTEXT_VERSION) {
return null;
}
if (!isRecord(candidate.origin) || typeof candidate.origin.pathname !== "string") {
return null;
}
if (
typeof candidate.tenantId !== "string"
|| typeof candidate.accountId !== "string"
|| !candidate.origin.pathname.startsWith("/")
) {
return null;
}
return candidate as QuickAccessLaunchContext;
}
export function quickAccessReturnPath(context: QuickAccessLaunchContext): string {
return `${context.origin.pathname}${context.origin.search}${context.origin.hash}`;
}
export function dispatchQuickAccessResult(
toolId: string,
launchContext: QuickAccessLaunchContext,
result: QuickAccessResult
): void {
if (typeof window === "undefined") return;
window.dispatchEvent(new CustomEvent(QUICK_ACCESS_RESULT_EVENT, {
detail: { toolId, launchContext, result }
}));
}
function normalizePathname(value: string): string {
return value.startsWith("/") ? value : "/";
}
function normalizeSearch(value: string): string {
return !value ? "" : value.startsWith("?") ? value : "";
}
function normalizeHash(value: string): string {
return !value ? "" : value.startsWith("#") ? value : "";
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}