@xenide-io/suite-client 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +51 -8
- package/dist/index.d.ts +51 -8
- package/dist/index.js +146 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +145 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors/codes.ts","../src/errors/normalize.ts","../src/api/create-api-client.ts","../src/auth/create-auth-return-path.ts","../src/workspace/access.ts","../src/workspace/subscription.ts","../src/workspace/create-brand-settings.ts","../src/workspace/create-workspace-appearance.ts","../src/workspace/create-workspace-appearance-api.ts","../src/sync/create-cross-app-sync.ts","../src/sync/create-live-refresh.ts","../src/notifications/create-notifications-client.ts","../src/ai/suite-ai-job.ts","../src/webhooks/webhook-activity-actions.ts","../src/seo/create-json-ld.ts","../src/hooks/use-is-night.ts"],"sourcesContent":["// AUTO-GENERATED from ERROR_CODES.md by scripts/error-codes/generate.py. Do not edit.\n\nexport const ERROR_CODES = {\n AUTH_001: 'E-AUTH-001',\n AUTH_002: 'E-AUTH-002',\n AUTH_003: 'E-AUTH-003',\n VALID_001: 'E-VALID-001',\n DATA_001: 'E-DATA-001',\n DATA_002: 'E-DATA-002',\n LIMIT_001: 'E-LIMIT-001',\n NET_001: 'E-NET-001',\n EXT_001: 'E-EXT-001',\n EXT_002: 'E-EXT-002',\n SYS_001: 'E-SYS-001',\n SYS_002: 'E-SYS-002',\n CLIENT_001: 'E-CLIENT-001',\n CLIENT_002: 'E-CLIENT-002',\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\n\nexport const DEFAULT_ERROR_CODE: ErrorCode = 'E-SYS-001';\n\nexport const ERROR_COPY: Record<ErrorCode, string> = {\n 'E-AUTH-001': 'Sign-in was not accepted. Check your details and try again.',\n 'E-AUTH-002': 'Your session has ended. Sign in again to continue.',\n 'E-AUTH-003': 'You do not have access to this. Ask an administrator for access.',\n 'E-VALID-001': 'Some details need correcting before this can be saved.',\n 'E-DATA-001': 'We could not find what you asked for. It may have been removed.',\n 'E-DATA-002': 'This conflicts with existing information. Review it and try again.',\n 'E-LIMIT-001': 'Too many requests right now. Wait a moment and try again.',\n 'E-NET-001': 'We could not reach the service. Try again in a moment.',\n 'E-EXT-001': 'The <capability> is unavailable right now. Try again shortly.',\n 'E-EXT-002': 'The <capability> could not complete that request. Try again, or adjust the input.',\n 'E-SYS-001': 'Something went wrong on our side. We have recorded it. Try again.',\n 'E-SYS-002': 'This feature is temporarily unavailable while we carry out maintenance.',\n 'E-CLIENT-001': 'You appear to be offline. Check your connection and try again.',\n 'E-CLIENT-002': 'We could not read the response. Reload the page and try again.',\n};\n\nexport const ERROR_RETRYABLE: Record<ErrorCode, boolean> = {\n 'E-AUTH-001': false,\n 'E-AUTH-002': true,\n 'E-AUTH-003': false,\n 'E-VALID-001': false,\n 'E-DATA-001': false,\n 'E-DATA-002': false,\n 'E-LIMIT-001': true,\n 'E-NET-001': true,\n 'E-EXT-001': true,\n 'E-EXT-002': true,\n 'E-SYS-001': true,\n 'E-SYS-002': false,\n 'E-CLIENT-001': true,\n 'E-CLIENT-002': true,\n};\n\nconst ERROR_CODE_SET = new Set<string>(Object.values(ERROR_CODES));\n\nexport function isErrorCode(value: unknown): value is ErrorCode {\n return typeof value === 'string' && ERROR_CODE_SET.has(value);\n}\n\nexport function copyFor(code: ErrorCode, capability?: string): string {\n const copy = ERROR_COPY[code] ?? ERROR_COPY[DEFAULT_ERROR_CODE];\n if (copy.includes('<capability>')) {\n return copy.replace('<capability>', capability ?? 'service');\n }\n return copy;\n}\n","import {\n DEFAULT_ERROR_CODE,\n ERROR_CODES,\n ERROR_RETRYABLE,\n copyFor,\n isErrorCode,\n type ErrorCode,\n} from './codes';\n\n/**\n * The single normalised shape every caught failure is reduced to before any\n * component sees it. `message` is always resolved from the local registry;\n * upstream text is never carried through.\n */\nexport type AppError = {\n code: ErrorCode;\n message: string;\n correlationId?: string;\n retryable: boolean;\n};\n\nconst STATUS_TO_CODE: Record<number, ErrorCode> = {\n 400: ERROR_CODES.VALID_001,\n 401: ERROR_CODES.AUTH_001,\n 403: ERROR_CODES.AUTH_003,\n 404: ERROR_CODES.DATA_001,\n 405: ERROR_CODES.SYS_001,\n 409: ERROR_CODES.DATA_002,\n 422: ERROR_CODES.VALID_001,\n 429: ERROR_CODES.LIMIT_001,\n 500: ERROR_CODES.SYS_001,\n 502: ERROR_CODES.EXT_002,\n 503: ERROR_CODES.NET_001,\n};\n\nexport function codeForStatus(status: number): ErrorCode {\n if (STATUS_TO_CODE[status]) return STATUS_TO_CODE[status];\n if (status >= 500) return ERROR_CODES.SYS_001;\n if (status >= 400) return ERROR_CODES.VALID_001;\n return ERROR_CODES.SYS_001;\n}\n\nfunction build(\n code: ErrorCode,\n correlationId?: string,\n retryable?: boolean,\n capability?: string,\n): AppError {\n return {\n code,\n message: copyFor(code, capability),\n ...(correlationId ? { correlationId } : {}),\n retryable: retryable ?? ERROR_RETRYABLE[code],\n };\n}\n\nfunction extractEnvelope(\n input: unknown,\n): { code: string; correlationId?: string; retryable?: boolean } | null {\n if (typeof input !== 'object' || input === null) return null;\n const error = (input as Record<string, unknown>).error;\n if (typeof error !== 'object' || error === null) return null;\n const record = error as Record<string, unknown>;\n return {\n code: typeof record.code === 'string' ? record.code : '',\n correlationId:\n typeof record.correlation_id === 'string'\n ? record.correlation_id\n : undefined,\n retryable:\n typeof record.retryable === 'boolean' ? record.retryable : undefined,\n };\n}\n\n/**\n * Reduce an unknown value to an `AppError`. Accepts only the envelope shape;\n * every other key on the input is discarded. Unknown or absent codes fall back.\n */\nexport function normalizeError(\n input: unknown,\n fallback: ErrorCode = DEFAULT_ERROR_CODE,\n capability?: string,\n): AppError {\n const envelope = extractEnvelope(input);\n if (!envelope) return build(fallback, undefined, undefined, capability);\n const resolved = isErrorCode(envelope.code) ? envelope.code : fallback;\n return build(\n resolved,\n envelope.correlationId,\n envelope.retryable,\n capability,\n );\n}\n\n/** Normalise a non-2xx `Response` without ever reading its raw message. */\nexport async function appErrorFromResponse(\n response: Response,\n capability?: string,\n): Promise<AppError> {\n const fallback = codeForStatus(response.status);\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n body = undefined;\n }\n return normalizeError(body, fallback, capability);\n}\n\n/** Normalise a thrown fetch failure: offline, aborted, or unreachable. */\nexport function normalizeThrown(input: unknown, capability?: string): AppError {\n if (typeof navigator !== 'undefined' && navigator.onLine === false) {\n return build(ERROR_CODES.CLIENT_001, undefined, undefined, capability);\n }\n if (input instanceof DOMException && input.name === 'AbortError') {\n return build(ERROR_CODES.NET_001, undefined, undefined, capability);\n }\n return build(ERROR_CODES.NET_001, undefined, undefined, capability);\n}\n\n/** Display string for a failure surface: capability copy plus the code. */\nexport function formatAppError(error: AppError): string {\n return `${error.message} (Reference: ${error.code})`;\n}\n","import { appErrorFromResponse, formatAppError } from '../errors/normalize';\n\nexport interface ApiClientConfig {\n /** localStorage key namespace, e.g. `tt` or `tides`. */\n storagePrefix: string;\n /** Backend refresh endpoint, e.g. `/api/turtletime/auth/refresh/`. */\n refreshPath: string;\n /** Overrides `NEXT_PUBLIC_API_URL` / `http://localhost:8000`. */\n baseUrl?: string;\n accessTokenKey?: string;\n refreshTokenKey?: string;\n workspaceKey?: string;\n /** Suite-wide workspace cookie name. */\n workspaceCookieName?: string;\n /** Overrides `NEXT_PUBLIC_WORKSPACE_COOKIE_DOMAIN`. */\n workspaceCookieDomain?: string;\n /** Fired when the active workspace changes in this tab. */\n onWorkspaceChange?: (id: string | null) => void;\n /** sessionStorage key prefixes to clear on sign-out. */\n sessionStorageClearPrefixes?: string[];\n}\n\nexport type ApiFetch = (\n path: string,\n options?: RequestInit,\n) => Promise<Response>;\n\n/**\n * Build a browser API client scoped to one ShellStack app. Every app shares the\n * same fetch/refresh/workspace behaviour; only the storage namespace and\n * refresh path differ.\n */\nexport function createApiClient(config: ApiClientConfig) {\n const API_URL =\n config.baseUrl ??\n process.env.NEXT_PUBLIC_API_URL ??\n 'http://localhost:8000';\n\n // Suite-wide workspace selection. Cookies (unlike localStorage) are shared\n // across ports/subdomains, so every product app reads the same value. Set\n // NEXT_PUBLIC_WORKSPACE_COOKIE_DOMAIN (e.g. \".xenide.io\") to share across\n // subdomains in production; leave unset for localhost.\n const SHARED_WORKSPACE_COOKIE =\n config.workspaceCookieName ?? 'xenide_active_workspace_id';\n const SHARED_WORKSPACE_COOKIE_DOMAIN =\n config.workspaceCookieDomain ??\n process.env.NEXT_PUBLIC_WORKSPACE_COOKIE_DOMAIN;\n\n const ACCESS_KEY = config.accessTokenKey ?? `${config.storagePrefix}_access_token`;\n const REFRESH_KEY =\n config.refreshTokenKey ?? `${config.storagePrefix}_refresh_token`;\n const WORKSPACE_KEY =\n config.workspaceKey ?? `${config.storagePrefix}_active_workspace_id`;\n\n let accessToken: string | null = null;\n let refreshToken: string | null = null;\n let activeWorkspaceId: string | null = null;\n\n let authFailureHandler: (() => void) | null = null;\n let authFailureFired = false;\n let refreshPromise: Promise<boolean> | null = null;\n\n function readSharedWorkspaceId(): string | null {\n if (typeof document === 'undefined') return null;\n const prefix = `${SHARED_WORKSPACE_COOKIE}=`;\n const entry = document.cookie\n .split('; ')\n .find((item) => item.startsWith(prefix));\n if (!entry) return null;\n const raw = entry.slice(prefix.length);\n if (!raw) return null;\n try {\n return decodeURIComponent(raw) || null;\n } catch {\n return raw;\n }\n }\n\n function writeSharedWorkspaceId(id: string | null): void {\n if (typeof document === 'undefined') return;\n const value = id ? encodeURIComponent(id) : '';\n const maxAge = id ? 60 * 60 * 24 * 365 : 0;\n const domain = SHARED_WORKSPACE_COOKIE_DOMAIN\n ? `; domain=${SHARED_WORKSPACE_COOKIE_DOMAIN}`\n : '';\n document.cookie = `${SHARED_WORKSPACE_COOKIE}=${value}; path=/; max-age=${maxAge}; SameSite=Lax${domain}`;\n }\n\n function onAuthFailure(handler: () => void): void {\n authFailureHandler = handler;\n authFailureFired = false;\n }\n\n function triggerAuthFailure(): void {\n if (authFailureFired) return;\n authFailureFired = true;\n authFailureHandler?.();\n }\n\n function setTokens(access: string, refresh: string): void {\n accessToken = access;\n refreshToken = refresh;\n if (typeof window !== 'undefined') {\n window.localStorage.setItem(ACCESS_KEY, access);\n window.localStorage.setItem(REFRESH_KEY, refresh);\n }\n }\n\n function getAccessToken(): string | null {\n if (accessToken) return accessToken;\n if (typeof window !== 'undefined') {\n accessToken = window.localStorage.getItem(ACCESS_KEY);\n }\n return accessToken;\n }\n\n function getRefreshToken(): string | null {\n if (refreshToken) return refreshToken;\n if (typeof window !== 'undefined') {\n refreshToken = window.localStorage.getItem(REFRESH_KEY);\n }\n return refreshToken;\n }\n\n function setActiveWorkspaceId(\n id: string | null,\n options?: { share?: boolean },\n ): void {\n const changed = activeWorkspaceId !== id;\n activeWorkspaceId = id;\n if (typeof window !== 'undefined') {\n if (id) {\n window.localStorage.setItem(WORKSPACE_KEY, id);\n } else {\n window.localStorage.removeItem(WORKSPACE_KEY);\n }\n if (changed) config.onWorkspaceChange?.(id);\n }\n // Only an explicit choice should update the suite-wide selection; a fallback\n // default must not clobber the workspace another app is using.\n if (options?.share) writeSharedWorkspaceId(id);\n }\n\n function getActiveWorkspaceId(): string | null {\n if (activeWorkspaceId) return activeWorkspaceId;\n if (typeof window !== 'undefined') {\n activeWorkspaceId =\n readSharedWorkspaceId() ?? window.localStorage.getItem(WORKSPACE_KEY);\n }\n return activeWorkspaceId;\n }\n\n function restoreWorkspace(): void {\n if (typeof window !== 'undefined') {\n activeWorkspaceId =\n readSharedWorkspaceId() ?? window.localStorage.getItem(WORKSPACE_KEY);\n }\n }\n\n function clearAuth(): void {\n accessToken = null;\n refreshToken = null;\n activeWorkspaceId = null;\n if (typeof window !== 'undefined') {\n window.localStorage.removeItem(ACCESS_KEY);\n window.localStorage.removeItem(REFRESH_KEY);\n window.localStorage.removeItem(WORKSPACE_KEY);\n const prefixes = config.sessionStorageClearPrefixes ?? [];\n if (prefixes.length > 0) {\n try {\n for (let i = window.sessionStorage.length - 1; i >= 0; i -= 1) {\n const key = window.sessionStorage.key(i);\n if (key && prefixes.some((prefix) => key.startsWith(prefix))) {\n window.sessionStorage.removeItem(key);\n }\n }\n } catch {\n // ignore storage access errors\n }\n }\n }\n }\n\n function restoreTokens(): void {\n if (typeof window !== 'undefined') {\n accessToken = window.localStorage.getItem(ACCESS_KEY);\n refreshToken = window.localStorage.getItem(REFRESH_KEY);\n activeWorkspaceId =\n readSharedWorkspaceId() ?? window.localStorage.getItem(WORKSPACE_KEY);\n }\n }\n\n async function refreshAccessToken(): Promise<boolean> {\n if (refreshPromise) return refreshPromise;\n refreshPromise = refreshAccessTokenInternal().finally(() => {\n refreshPromise = null;\n });\n return refreshPromise;\n }\n\n async function refreshAccessTokenInternal(): Promise<boolean> {\n const refresh = getRefreshToken();\n if (!refresh) {\n triggerAuthFailure();\n return false;\n }\n\n try {\n const res = await fetch(`${API_URL}${config.refreshPath}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({ refresh }),\n });\n if (!res.ok) {\n clearAuth();\n triggerAuthFailure();\n return false;\n }\n const data = await res.json();\n setTokens(data.access, data.refresh);\n authFailureFired = false;\n return true;\n } catch {\n clearAuth();\n triggerAuthFailure();\n return false;\n }\n }\n\n async function apiFetch(\n path: string,\n options: RequestInit = {},\n ): Promise<Response> {\n const token = getAccessToken();\n const headers: Record<string, string> = {\n Accept: 'application/json',\n ...(options.headers as Record<string, string>),\n };\n\n const body = options.body;\n const isFormData =\n typeof FormData !== 'undefined' && body instanceof FormData;\n\n if (!isFormData) {\n headers['Content-Type'] = 'application/json';\n }\n\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n const wsId = getActiveWorkspaceId();\n if (wsId) {\n headers['X-Active-Workspace-Id'] = wsId;\n }\n\n const url = path.startsWith('http') ? path : `${API_URL}${path}`;\n\n async function doFetch(): Promise<Response> {\n try {\n return await fetch(url, { ...options, headers });\n } catch {\n throw new Error(\n `Could not reach the API at ${API_URL}. Is the Django backend running?`,\n );\n }\n }\n\n let res = await doFetch();\n\n if (res.status === 401 && token) {\n const refreshed = await refreshAccessToken();\n if (refreshed) {\n const newToken = getAccessToken();\n if (newToken) {\n headers['Authorization'] = `Bearer ${newToken}`;\n }\n res = await doFetch();\n if (res.status === 401) {\n triggerAuthFailure();\n }\n }\n }\n\n return res;\n }\n\n async function parseApiError(res: Response): Promise<string> {\n // Never render upstream text or a status number: normalise, then show the code.\n return formatAppError(await appErrorFromResponse(res));\n }\n\n const api = {\n get: (path: string) => apiFetch(path),\n\n post: (path: string, body?: unknown, options: RequestInit = {}) =>\n apiFetch(path, {\n ...options,\n method: 'POST',\n body:\n body instanceof FormData\n ? body\n : body\n ? JSON.stringify(body)\n : undefined,\n }),\n\n put: (path: string, body?: unknown) =>\n apiFetch(path, {\n method: 'PUT',\n body:\n body instanceof FormData\n ? body\n : body\n ? JSON.stringify(body)\n : undefined,\n }),\n\n patch: (path: string, body?: unknown) =>\n apiFetch(path, {\n method: 'PATCH',\n body:\n body instanceof FormData\n ? body\n : body\n ? JSON.stringify(body)\n : undefined,\n }),\n\n delete: (path: string) => apiFetch(path, { method: 'DELETE' }),\n };\n\n return {\n API_URL,\n onAuthFailure,\n setTokens,\n getAccessToken,\n getRefreshToken,\n setActiveWorkspaceId,\n getActiveWorkspaceId,\n restoreWorkspace,\n clearAuth,\n restoreTokens,\n refreshAccessToken,\n parseApiError,\n api,\n };\n}\n\nexport type ApiClient = ReturnType<typeof createApiClient>;\nexport type Api = ApiClient['api'];\n","export interface AuthReturnPathConfig {\n /** sessionStorage key holding the in-app return path, e.g. `tt_auth_return_to`. */\n returnKey: string;\n /** sessionStorage key caching the portal handoff payload. */\n handoffKey?: string;\n defaultNext?: string;\n portalCallbackPath?: string;\n redeemPath?: string;\n}\n\nexport interface PortalHandoffPayload {\n code?: string;\n token?: string;\n access?: string;\n refresh?: string;\n}\n\nexport interface PortalHandoffRead {\n code: string | null;\n token: string | null;\n refresh: string | null;\n next: string;\n}\n\nexport type AuthReturnPath = ReturnType<typeof createAuthReturnPath>;\n\nexport function createAuthReturnPath(config: AuthReturnPathConfig) {\n const RETURN_KEY = config.returnKey;\n const HANDOFF_KEY = config.handoffKey ?? 'ss_portal_handoff';\n const DEFAULT_NEXT = config.defaultNext ?? '/today';\n const PORTAL_CALLBACK_PATH =\n config.portalCallbackPath ?? '/auth/portal/callback';\n const REDEEM_PATH = config.redeemPath ?? '/api/portal/auth/switch/redeem';\n\n function isSafeAuthReturnPath(\n path: string | null | undefined,\n ): path is string {\n if (\n !path ||\n path[0] !== '/' ||\n path.startsWith('//') ||\n path.startsWith('/\\\\')\n ) {\n return false;\n }\n if (path.includes('\\\\') || path.includes('://')) return false;\n return true;\n }\n\n function stashAuthReturnPath(path: string | null | undefined): void {\n if (typeof window === 'undefined' || !isSafeAuthReturnPath(path)) return;\n sessionStorage.setItem(RETURN_KEY, path);\n }\n\n function portalHandoffUrl(\n base: string,\n data: PortalHandoffPayload,\n next?: string,\n fallbackNext = DEFAULT_NEXT,\n ): string {\n const dest = isSafeAuthReturnPath(next) ? next : fallbackNext;\n const hash = new URLSearchParams();\n if (data.code) {\n hash.set('code', data.code);\n } else {\n const token = data.token || data.access || '';\n if (token) hash.set('token', token);\n if (data.refresh) hash.set('refresh', data.refresh);\n }\n hash.set('next', dest);\n return `${base.replace(/\\/$/, '')}${PORTAL_CALLBACK_PATH}#${hash.toString()}`;\n }\n\n function cachedHandoff(fallbackNext: string): PortalHandoffRead | null {\n try {\n const raw = sessionStorage.getItem(HANDOFF_KEY);\n if (!raw) return null;\n const parsed = JSON.parse(raw) as PortalHandoffRead;\n if (!parsed.code && !parsed.token) return null;\n return {\n code: parsed.code || null,\n token: parsed.token || null,\n refresh: parsed.refresh || null,\n next: isSafeAuthReturnPath(parsed.next) ? parsed.next : fallbackNext,\n };\n } catch {\n sessionStorage.removeItem(HANDOFF_KEY);\n return null;\n }\n }\n\n function readPortalHandoff(fallbackNext = DEFAULT_NEXT): PortalHandoffRead {\n const cached = cachedHandoff(fallbackNext);\n if (cached) return cached;\n const hash = new URLSearchParams(window.location.hash.replace(/^#/, ''));\n const query = new URLSearchParams(window.location.search);\n const nextRaw = hash.get('next') || query.get('next');\n const result: PortalHandoffRead = {\n code: hash.get('code') || query.get('code'),\n token: hash.get('token') || query.get('token'),\n refresh: hash.get('refresh') || query.get('refresh'),\n next: isSafeAuthReturnPath(nextRaw) ? nextRaw : fallbackNext,\n };\n if (result.code || result.token) {\n sessionStorage.setItem(HANDOFF_KEY, JSON.stringify(result));\n }\n // The callback's hard location.replace removes the fragment from history.\n // Calling history.replaceState here makes Next.js remount the callback before\n // its one-time code redemption has completed.\n return result;\n }\n\n let redeemInflight: Promise<PortalHandoffRead> | null = null;\n\n async function redeemPortalHandoff(\n apiUrl: string,\n fallbackNext = DEFAULT_NEXT,\n ): Promise<PortalHandoffRead> {\n redeemInflight ??= (async () => {\n const { code, token, refresh, next } = readPortalHandoff(fallbackNext);\n if (!code) return { code: null, token, refresh, next };\n const response = await fetch(`${apiUrl.replace(/\\/$/, '')}${REDEEM_PATH}`, {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ code }),\n });\n if (!response.ok) return { code: null, token: null, refresh: null, next };\n const data = (await response.json()) as PortalHandoffPayload;\n sessionStorage.removeItem(HANDOFF_KEY);\n return {\n code: null,\n token: data.token || data.access || null,\n refresh: data.refresh || null,\n next,\n };\n })();\n return redeemInflight;\n }\n\n function readAuthReturnPath(): string | null {\n if (typeof window === 'undefined') return null;\n const path = sessionStorage.getItem(RETURN_KEY);\n return isSafeAuthReturnPath(path) ? path : null;\n }\n\n function signInHrefWithReturn(fallbackRedirect?: string | null): string {\n const path = isSafeAuthReturnPath(fallbackRedirect)\n ? fallbackRedirect\n : readAuthReturnPath();\n if (!path) return '/login';\n return `/login?redirect=${encodeURIComponent(path)}`;\n }\n\n function registerHrefWithReturn(returnPath: string): string {\n return `/register?redirect=${encodeURIComponent(returnPath)}`;\n }\n\n return {\n isSafeAuthReturnPath,\n stashAuthReturnPath,\n portalHandoffUrl,\n readPortalHandoff,\n redeemPortalHandoff,\n readAuthReturnPath,\n signInHrefWithReturn,\n registerHrefWithReturn,\n };\n}\n","/** Copy and helpers for workspace seats vs per-app access (shared core model). */\n\nexport const WORKSPACE_SEAT_HELP =\n 'Each member uses one workspace seat. Inviting someone reserves a seat until the invite is revoked or accepted.';\n\nexport const ORGANISATION_SEAT_HELP =\n 'Each organisation member uses one seat across all your workspaces. One invite gives them access to every workspace in your organisation.';\n\nexport const TIDES_ACCESS_HELP = 'Tides — project management.';\n\nexport const TURTLETIME_ACCESS_HELP =\n 'TurtleTime — time tracking add-on (Basic plan and above).';\n\nexport const FREE_PLAN_SEAT_HELP =\n 'Free personal workspaces include one product app only. Create an organisation on ShellStack for both apps and integration.';\n\nexport function hasAppAccess(\n canAccessTurtletime: boolean,\n canAccessTides: boolean,\n): boolean {\n return canAccessTurtletime || canAccessTides;\n}\n\nexport function canDisableAppAccess(\n app: 'turtletime' | 'tides',\n canAccessTurtletime: boolean,\n canAccessTides: boolean,\n): boolean {\n if (app === 'turtletime') return canAccessTides;\n return canAccessTurtletime;\n}\n","/** Per-workspace subscription tier helpers (mirrors core/subscriptions.py). */\n\nexport type SubscriptionTier =\n | 'free'\n | 'plus'\n | 'ai_plus'\n | 'basic'\n | 'pro'\n | 'enterprise';\n\nexport interface WorkspaceSubscription {\n tier: SubscriptionTier;\n tier_label: string;\n limits: {\n seats?: number | null;\n integrated_seats: number | null;\n tides_only_seats: number | null;\n };\n usage: {\n seats?: number;\n integrated_seats: number;\n tides_only_seats: number;\n };\n}\n\nexport type SeatType = 'tides' | 'integrated';\n\nconst TIER_LABELS: Record<SubscriptionTier, string> = {\n free: 'Free',\n plus: 'Plus',\n ai_plus: 'AI Plus',\n basic: 'Basic',\n pro: 'Pro',\n enterprise: 'Enterprise',\n};\n\nexport function tierLabel(tier: SubscriptionTier): string {\n if (tier === 'basic' || tier === 'pro') return 'Plus';\n return TIER_LABELS[tier] ?? 'Free';\n}\n\nexport function resolveSubscriptionTier(\n workspace:\n | {\n subscription?: { tier?: string } | null;\n subscription_tier?: string | null;\n }\n | null\n | undefined,\n): SubscriptionTier {\n const tier = workspace?.subscription?.tier ?? workspace?.subscription_tier;\n if (\n tier === 'plus' ||\n tier === 'ai_plus' ||\n tier === 'basic' ||\n tier === 'pro' ||\n tier === 'enterprise' ||\n tier === 'free'\n ) {\n return tier === 'basic' || tier === 'pro' ? 'plus' : tier;\n }\n return 'free';\n}\n\nexport function tierAllowsTurtletime(tier: SubscriptionTier): boolean {\n return tier !== 'free';\n}\n\nexport function defaultInviteSeatType(tier: SubscriptionTier): SeatType {\n return tierAllowsTurtletime(tier) ? 'integrated' : 'tides';\n}\n\nexport function tierAllowsTidesOnlySeats(): boolean {\n return false;\n}\n\nexport function seatTypeFromFlags(\n canAccessTurtletime: boolean,\n canAccessTides: boolean,\n): SeatType {\n if (canAccessTurtletime && canAccessTides) return 'integrated';\n return 'tides';\n}\n\nexport function flagsFromSeatType(seatType: SeatType): {\n can_access_turtletime: boolean;\n can_access_tides: boolean;\n} {\n if (seatType === 'integrated') {\n return { can_access_turtletime: true, can_access_tides: true };\n }\n return { can_access_turtletime: false, can_access_tides: true };\n}\n\nexport function seatTypeLabel(seatType: SeatType): string {\n return seatType === 'integrated' ? 'Tides + TurtleTime' : 'Tides';\n}\n\nexport function seatTypeDescription(\n tier: SubscriptionTier,\n seatType: SeatType,\n): string {\n if (seatType === 'integrated') {\n return 'Project management and time tracking with TT↔Tides integration.';\n }\n if (!tierAllowsTidesOnlySeats()) {\n return 'Project management included on the Free plan.';\n }\n return 'Project management only — no TurtleTime time tracking.';\n}\n\nexport function planSeatSummary(\n subscription: WorkspaceSubscription | null | undefined,\n): string {\n if (!subscription) return '';\n const { tier, limits, usage } = subscription;\n const seatCap = limits.seats ?? limits.integrated_seats;\n const seatCount = usage.seats ?? usage.integrated_seats;\n if (tier === 'enterprise') {\n return 'Unlimited seats';\n }\n if (seatCap != null) {\n return `${seatCount}/${seatCap} seats`;\n }\n return '';\n}\n","export interface BrandSettingsConfig {\n /** Fallback title when the workspace has not customised one. */\n defaultTitle: string;\n /** Window event dispatched when the brand changes. */\n changedEvent: string;\n}\n\nexport interface WorkspaceBrandPayload {\n brand_logo?: string | null;\n brand_title?: string | null;\n brand_show_title?: boolean;\n}\n\nexport type BrandSettings = ReturnType<typeof createBrandSettings>;\n\nexport function createBrandSettings(config: BrandSettingsConfig) {\n const DEFAULT_BRAND_TITLE = config.defaultTitle;\n const BRAND_SETTINGS_CHANGED = config.changedEvent;\n\n interface WorkspaceBrandState {\n logo: string | null;\n title: string | null;\n showTitle: boolean;\n }\n\n const brandState: WorkspaceBrandState = {\n logo: null,\n title: null,\n showTitle: true,\n };\n\n function notifyBrandChange(): void {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new Event(BRAND_SETTINGS_CHANGED));\n }\n }\n\n function applyWorkspaceBrand(payload: WorkspaceBrandPayload): void {\n if ('brand_logo' in payload) {\n brandState.logo = payload.brand_logo ?? null;\n }\n if ('brand_title' in payload) {\n const title = payload.brand_title;\n brandState.title = title?.trim() ? title.trim() : null;\n }\n if ('brand_show_title' in payload && payload.brand_show_title !== undefined) {\n brandState.showTitle = payload.brand_show_title;\n }\n notifyBrandChange();\n }\n\n function getBrandLogo(): string | null {\n return brandState.logo;\n }\n\n function getCustomBrandTitle(): string | null {\n return brandState.title;\n }\n\n function getBrandShowTitle(): boolean {\n return brandState.showTitle;\n }\n\n /** Title shown in navigation — null when hidden. */\n function getDisplayBrandTitle(): string | null {\n if (!getBrandShowTitle()) return null;\n const custom = getCustomBrandTitle();\n if (custom === null) return DEFAULT_BRAND_TITLE;\n return custom || DEFAULT_BRAND_TITLE;\n }\n\n /** @deprecated Brand is persisted via workspace appearance API. */\n function setBrandLogo(dataUrl: string | null): void {\n brandState.logo = dataUrl;\n notifyBrandChange();\n }\n\n /** @deprecated Brand is persisted via workspace appearance API. */\n function setCustomBrandTitle(title: string | null): void {\n brandState.title = title?.trim() ? title.trim() : null;\n notifyBrandChange();\n }\n\n /** @deprecated Brand is persisted via workspace appearance API. */\n function setBrandShowTitle(show: boolean): void {\n brandState.showTitle = show;\n notifyBrandChange();\n }\n\n /** @deprecated Brand is persisted via workspace appearance API. */\n function resetBrandTitle(): void {\n brandState.title = null;\n brandState.showTitle = true;\n notifyBrandChange();\n }\n\n return {\n DEFAULT_BRAND_TITLE,\n BRAND_SETTINGS_CHANGED,\n /** @deprecated Use BRAND_SETTINGS_CHANGED */\n BRAND_LOGO_CHANGED: BRAND_SETTINGS_CHANGED,\n applyWorkspaceBrand,\n getBrandLogo,\n getCustomBrandTitle,\n getBrandShowTitle,\n getDisplayBrandTitle,\n setBrandLogo,\n setCustomBrandTitle,\n setBrandShowTitle,\n resetBrandTitle,\n };\n}\n","export interface WorkspaceAppearanceConfig {\n /** Window event dispatched when the active workspace changes. */\n changedEvent: string;\n /** localStorage base key for the theme. */\n themeStorageBase: string;\n /** Reads the active workspace id from the app's API client. */\n getActiveWorkspaceId: () => string | null;\n}\n\nexport type WorkspaceAppearanceHelpers = ReturnType<\n typeof createWorkspaceAppearance\n>;\n\nexport function createWorkspaceAppearance(config: WorkspaceAppearanceConfig) {\n const WORKSPACE_CHANGED = config.changedEvent;\n const THEME_STORAGE_BASE = config.themeStorageBase;\n\n function notifyWorkspaceChanged(): void {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new Event(WORKSPACE_CHANGED));\n }\n }\n\n function scopedStorageKey(\n base: string,\n workspaceId?: string | null,\n ): string {\n const wsId = workspaceId ?? config.getActiveWorkspaceId();\n return wsId ? `${base}:${wsId}` : base;\n }\n\n function getScopedStorageItem(\n base: string,\n workspaceId?: string | null,\n ): string | null {\n if (typeof window === 'undefined') return null;\n try {\n const scoped = localStorage.getItem(scopedStorageKey(base, workspaceId));\n if (scoped !== null) return scoped;\n const wsId = workspaceId ?? config.getActiveWorkspaceId();\n if (wsId) {\n return localStorage.getItem(base);\n }\n return null;\n } catch {\n return null;\n }\n }\n\n function setScopedStorageItem(\n base: string,\n value: string | null,\n workspaceId?: string | null,\n ): void {\n if (typeof window === 'undefined') return;\n const key = scopedStorageKey(base, workspaceId);\n if (value === null) {\n localStorage.removeItem(key);\n } else {\n localStorage.setItem(key, value);\n }\n }\n\n return {\n WORKSPACE_CHANGED,\n THEME_STORAGE_BASE,\n notifyWorkspaceChanged,\n scopedStorageKey,\n getScopedStorageItem,\n setScopedStorageItem,\n };\n}\n","import type { Api } from '../api/create-api-client';\nimport type { WorkspaceBrandPayload } from './create-brand-settings';\n\nexport interface WorkspaceAppearanceApiConfig {\n api: Api;\n parseApiError: (res: Response) => Promise<string>;\n /** Endpoint base, e.g. `/api/turtletime/workspaces/current/appearance/`. */\n basePath: string;\n applyWorkspaceBrand: (payload: WorkspaceBrandPayload) => void;\n}\n\nexport type WorkspaceTheme = 'light' | 'dark';\n\nexport interface WorkspaceAppearance {\n theme: WorkspaceTheme;\n brand_logo: string | null;\n brand_title: string | null;\n brand_show_title: boolean;\n}\n\nexport type WorkspaceAppearancePatch = Partial<\n Pick<WorkspaceAppearance, 'theme' | 'brand_title' | 'brand_show_title'>\n> & {\n brand_logo_file?: File;\n clear_brand_logo?: boolean;\n};\n\nexport class WorkspaceAppearanceError extends Error {\n status: number;\n\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\n }\n}\n\nfunction normalizeTheme(value: unknown): WorkspaceTheme {\n return value === 'light' ? 'light' : 'dark';\n}\n\nfunction parseAppearancePayload(\n data: Record<string, unknown>,\n): WorkspaceAppearance {\n const raw = (data.appearance ?? data) as Record<string, unknown>;\n return {\n theme: normalizeTheme(raw.theme),\n brand_logo: (raw.brand_logo as string | null) ?? null,\n brand_title: (raw.brand_title as string | null) ?? null,\n brand_show_title: Boolean(raw.brand_show_title),\n };\n}\n\nexport function createWorkspaceAppearanceApi(\n config: WorkspaceAppearanceApiConfig,\n) {\n const { api, parseApiError, basePath, applyWorkspaceBrand } = config;\n\n async function fetchWorkspaceAppearance(): Promise<WorkspaceAppearance> {\n const res = await api.get(basePath);\n if (!res.ok) {\n throw new WorkspaceAppearanceError(await parseApiError(res), res.status);\n }\n const data = await res.json();\n return parseAppearancePayload(data);\n }\n\n async function updateWorkspaceAppearance(\n patch: WorkspaceAppearancePatch,\n ): Promise<WorkspaceAppearance> {\n const hasFileUpload = patch.brand_logo_file instanceof File;\n const shouldClearLogo = patch.clear_brand_logo === true;\n\n if (hasFileUpload || shouldClearLogo) {\n const formData = new FormData();\n if (patch.theme) formData.append('theme', patch.theme);\n if (patch.brand_title !== undefined) {\n formData.append('brand_title', patch.brand_title ?? '');\n }\n if (patch.brand_show_title !== undefined) {\n formData.append(\n 'brand_show_title',\n patch.brand_show_title ? 'true' : 'false',\n );\n }\n if (hasFileUpload && patch.brand_logo_file) {\n formData.append('brand_logo', patch.brand_logo_file);\n }\n if (shouldClearLogo) {\n formData.append('clear_brand_logo', 'true');\n }\n\n const res = await api.patch(basePath, formData);\n if (!res.ok) {\n throw new Error(await parseApiError(res));\n }\n const data = await res.json();\n return parseAppearancePayload(data);\n }\n\n const res = await api.patch(basePath, {\n theme: patch.theme,\n brand_title: patch.brand_title,\n brand_show_title: patch.brand_show_title,\n });\n if (!res.ok) {\n throw new Error(await parseApiError(res));\n }\n const data = await res.json();\n return parseAppearancePayload(data);\n }\n\n function cacheWorkspaceAppearanceLocally(\n appearance: WorkspaceAppearance,\n ): void {\n applyWorkspaceBrand({\n brand_logo: appearance.brand_logo,\n brand_title: appearance.brand_title,\n brand_show_title: appearance.brand_show_title,\n });\n }\n\n return {\n WorkspaceAppearanceError,\n fetchWorkspaceAppearance,\n updateWorkspaceAppearance,\n cacheWorkspaceAppearanceLocally,\n };\n}\n","import type { Api } from '../api/create-api-client';\n\nexport type CrossAppSyncTopic = 'time' | 'integration' | 'tasks';\n\nexport interface CrossAppSyncConfig {\n api: Api;\n /** Checkpoint endpoint, e.g. `/api/turtletime/sync-checkpoint/`. */\n checkpointPath: string;\n /** Poll interval while the tab is visible (ms). */\n pollMs?: number;\n}\n\ntype SyncCheckpoint = {\n tasks_synced_at: number;\n time_synced_at: number;\n integration_synced_at: number;\n};\n\nexport type CrossAppSync = ReturnType<typeof createCrossAppSync>;\n\n/**\n * Poll backend checkpoints so sibling apps refresh when data mutates\n * elsewhere. Same-tab instant updates stay the app's responsibility via its\n * own window events.\n */\nexport function createCrossAppSync(config: CrossAppSyncConfig) {\n const pollMs = config.pollMs ?? 5000;\n\n const lastKnown: Record<CrossAppSyncTopic, number> = {\n time: 0,\n integration: 0,\n tasks: 0,\n };\n\n const subscribers: Array<{\n topic?: CrossAppSyncTopic;\n callback: () => void;\n lastSync: number;\n }> = [];\n\n let pollInterval: number | null = null;\n let pollListeners = 0;\n\n async function fetchCheckpoint(): Promise<SyncCheckpoint | null> {\n try {\n const res = await config.api.get(config.checkpointPath);\n if (!res.ok) return null;\n return (await res.json()) as SyncCheckpoint;\n } catch {\n return null;\n }\n }\n\n function notify(topic: CrossAppSyncTopic): void {\n for (const sub of subscribers) {\n if (!sub.topic || sub.topic === topic) {\n if (lastKnown[topic] > sub.lastSync) {\n sub.lastSync = lastKnown[topic];\n sub.callback();\n }\n }\n }\n }\n\n function checkAndNotify(data: SyncCheckpoint): void {\n const tasks = data.tasks_synced_at || 0;\n const time = data.time_synced_at || 0;\n const integration = data.integration_synced_at || 0;\n\n if (tasks > lastKnown.tasks) {\n lastKnown.tasks = tasks;\n notify('tasks');\n }\n if (time > lastKnown.time) {\n lastKnown.time = time;\n notify('time');\n }\n if (integration > lastKnown.integration) {\n lastKnown.integration = integration;\n notify('integration');\n }\n }\n\n function startPolling(): void {\n if (pollInterval !== null || typeof window === 'undefined') return;\n\n void fetchCheckpoint().then((data) => data && checkAndNotify(data));\n pollInterval = window.setInterval(() => {\n if (document.visibilityState === 'visible') {\n void fetchCheckpoint().then((data) => data && checkAndNotify(data));\n }\n }, pollMs);\n }\n\n function stopPolling(): void {\n if (pollListeners > 0) return;\n if (pollInterval !== null) {\n window.clearInterval(pollInterval);\n pollInterval = null;\n }\n }\n\n function broadcastCrossAppSync(_topic: CrossAppSyncTopic): void {\n // Cross-subdomain sync is handled by backend checkpoints + polling.\n // Same-tab instant updates are dispatched via the app's own events.\n void _topic;\n }\n\n function subscribeCrossAppSync(\n onSync: () => void,\n topic?: CrossAppSyncTopic,\n ): () => void {\n if (typeof window === 'undefined') return () => {};\n\n const sub = {\n topic,\n callback: onSync,\n lastSync: lastKnown[topic || 'tasks'],\n };\n subscribers.push(sub);\n pollListeners += 1;\n startPolling();\n\n return () => {\n const index = subscribers.indexOf(sub);\n if (index >= 0) subscribers.splice(index, 1);\n pollListeners -= 1;\n if (pollListeners <= 0) stopPolling();\n };\n }\n\n function lastCrossAppSyncTime(topic?: CrossAppSyncTopic): number {\n return topic ? lastKnown[topic] : Math.max(...Object.values(lastKnown));\n }\n\n return {\n broadcastCrossAppSync,\n subscribeCrossAppSync,\n lastCrossAppSyncTime,\n };\n}\n","import type {\n CrossAppSync,\n CrossAppSyncTopic,\n} from './create-cross-app-sync';\n\nexport interface LiveRefreshOptions {\n /** Same-tab custom events (e.g. `tt-timer-mutated`). */\n localEvents?: string[];\n /** Gentle poll while the tab is visible (ms). */\n pollMs?: number;\n /** Limit cross-tab refreshes to one mutation topic. */\n crossAppTopic?: CrossAppSyncTopic;\n}\n\nexport type LiveRefresh = ReturnType<typeof createLiveRefresh>;\n\nexport function createLiveRefresh(sync: CrossAppSync) {\n /** True if a cross-tab sync for the given topic happened after `since`. */\n function hasPendingCrossAppSync(\n topic: CrossAppSyncTopic | undefined,\n since: number,\n ): boolean {\n return sync.lastCrossAppSyncTime(topic) > since;\n }\n\n /** Re-run `refresh` when sibling apps mutate data, on same-tab events, or on an interval. */\n function subscribeLiveRefresh(\n refresh: () => void,\n options: LiveRefreshOptions = {},\n ): () => void {\n if (typeof window === 'undefined') return () => {};\n\n const cleanups: Array<() => void> = [];\n let lastRefreshAt = Date.now();\n\n const trigger = () => {\n lastRefreshAt = Date.now();\n refresh();\n };\n\n for (const eventName of options.localEvents ?? []) {\n window.addEventListener(eventName, trigger);\n cleanups.push(() => window.removeEventListener(eventName, trigger));\n }\n\n cleanups.push(sync.subscribeCrossAppSync(trigger, options.crossAppTopic));\n\n const shouldRefreshOnFocus = () =>\n hasPendingCrossAppSync(options.crossAppTopic, lastRefreshAt);\n\n const onVisibility = () => {\n if (document.visibilityState === 'visible' && shouldRefreshOnFocus()) {\n trigger();\n }\n };\n document.addEventListener('visibilitychange', onVisibility);\n cleanups.push(() =>\n document.removeEventListener('visibilitychange', onVisibility),\n );\n\n const onFocus = () => {\n if (shouldRefreshOnFocus()) {\n trigger();\n }\n };\n window.addEventListener('focus', onFocus);\n cleanups.push(() => window.removeEventListener('focus', onFocus));\n\n if (options.pollMs && options.pollMs > 0) {\n const interval = window.setInterval(() => {\n if (document.visibilityState === 'visible') trigger();\n }, options.pollMs);\n cleanups.push(() => window.clearInterval(interval));\n }\n\n return () => {\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n return { subscribeLiveRefresh, hasPendingCrossAppSync };\n}\n","import type { Api } from '../api/create-api-client';\n\n/** Cross-app (suite) notification from `/api/notifications/` — shared across ShellStack apps. */\nexport interface SuiteNotification {\n id: string;\n title: string;\n body: string;\n href: string;\n kind: string;\n source_app: string;\n read_at: string | null;\n created_at: string;\n workspace: string | null;\n organisation: string | null;\n}\n\nexport interface SuiteNotificationsResponse {\n notifications: SuiteNotification[];\n unread_count: number;\n}\n\nexport interface NotificationsClientConfig {\n api: Api;\n parseApiError: (res: Response) => Promise<string>;\n cacheMs?: number;\n listPath?: string;\n}\n\nexport type NotificationsClient = ReturnType<typeof createNotificationsClient>;\n\nexport function createNotificationsClient(config: NotificationsClientConfig) {\n const { api, parseApiError } = config;\n const CACHE_MS = config.cacheMs ?? 15_000;\n const LIST_PATH = config.listPath ?? '/api/notifications/';\n\n let notificationsCache: {\n data: SuiteNotificationsResponse;\n at: number;\n } | null = null;\n let notificationsInflight: Promise<SuiteNotificationsResponse> | null = null;\n\n async function fetchSuiteNotifications(): Promise<SuiteNotificationsResponse> {\n if (notificationsCache && Date.now() - notificationsCache.at < CACHE_MS) {\n return notificationsCache.data;\n }\n if (notificationsInflight) return notificationsInflight;\n notificationsInflight = (async () => {\n try {\n const res = await api.get(LIST_PATH);\n if (!res.ok) throw new Error(await parseApiError(res));\n const data = (await res.json()) as Partial<SuiteNotificationsResponse>;\n const result: SuiteNotificationsResponse = {\n notifications: data.notifications ?? [],\n unread_count: data.unread_count ?? 0,\n };\n notificationsCache = { data: result, at: Date.now() };\n return result;\n } finally {\n notificationsInflight = null;\n }\n })();\n return notificationsInflight;\n }\n\n async function markSuiteNotificationRead(id: string): Promise<void> {\n const res = await api.post(`${LIST_PATH}${id}/read/`);\n if (!res.ok) throw new Error(await parseApiError(res));\n notificationsCache = null;\n }\n\n async function markAllSuiteNotificationsRead(): Promise<void> {\n const res = await api.post(`${LIST_PATH}read-all/`);\n if (!res.ok) throw new Error(await parseApiError(res));\n notificationsCache = null;\n }\n\n return {\n fetchSuiteNotifications,\n markSuiteNotificationRead,\n markAllSuiteNotificationsRead,\n };\n}\n","export type SuiteAiJobEvent = {\n sequence: number;\n event_type:\n | 'queued'\n | 'started'\n | 'progress'\n | 'tool'\n | 'completed'\n | 'failed'\n | 'cancelled';\n summary: string;\n payload: Record<string, unknown>;\n created_at: string;\n};\n\nexport type SuiteAiJob = {\n id: string;\n status: 'pending' | 'running' | 'completed' | 'failed';\n events: SuiteAiJobEvent[];\n};\n\nexport function activeSuiteAiJobs(jobs: SuiteAiJob[]) {\n return jobs.filter(\n (job) => job.status === 'pending' || job.status === 'running',\n );\n}\n\nexport function sortedSuiteAiEvents(events: SuiteAiJobEvent[]) {\n return [...events].sort((a, b) => a.sequence - b.sequence);\n}\n\nexport function suiteAiEventSummary(event?: SuiteAiJobEvent) {\n const summary = event?.summary\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u001f]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n if (summary) return summary;\n if (event?.event_type === 'queued') return 'Shelly request queued';\n if (event?.event_type === 'started')\n return 'Shelly started preparing a reply';\n if (event?.event_type === 'completed') return 'Shelly reply ready';\n if (event?.event_type === 'failed') return 'AI request failed';\n return 'Shelly is working';\n}\n","export const WEBHOOK_ACTIVITY_ACTIONS = [\n { id: 'created', label: 'Task created' },\n { id: 'updated', label: 'Task updated' },\n { id: 'status_changed', label: 'Task status changed' },\n { id: 'assigned', label: 'Task assigned' },\n { id: 'commented', label: 'Comment posted' },\n { id: 'project_renamed', label: 'Project renamed' },\n { id: 'project_status_changed', label: 'Project archived or restored' },\n] as const;\n\nconst ALL_WEBHOOK_ACTION_IDS = WEBHOOK_ACTIVITY_ACTIONS.map(\n (action) => action.id,\n);\n\nexport function webhookActionsToSelected(\n stored: string[] | null | undefined,\n): Set<string> {\n if (!stored || stored.length === 0) {\n return new Set(ALL_WEBHOOK_ACTION_IDS);\n }\n return new Set(stored);\n}\n\nexport function selectedToWebhookActions(selected: Set<string>): string[] {\n if (selected.size >= ALL_WEBHOOK_ACTION_IDS.length) {\n return [];\n }\n return ALL_WEBHOOK_ACTION_IDS.filter((id) => selected.has(id));\n}\n","export interface JsonLdConfig {\n name: string;\n url: string;\n description: string;\n applicationCategory?: string;\n applicationSubCategory?: string;\n operatingSystem?: string;\n softwareVersion?: string;\n price?: string;\n priceCurrency?: string;\n inLanguage?: string;\n publisherName?: string;\n publisherUrl?: string;\n}\n\n/** Build a schema.org SoftwareApplication JSON-LD string for a product page. */\nexport function createJsonLd(config: JsonLdConfig): string {\n const publisherName = config.publisherName ?? 'Xenide';\n const publisherUrl = config.publisherUrl ?? 'https://xenide.io';\n\n return JSON.stringify({\n '@context': 'https://schema.org',\n '@type': 'SoftwareApplication',\n name: config.name,\n url: config.url,\n description: config.description,\n applicationCategory: config.applicationCategory ?? 'BusinessApplication',\n ...(config.applicationSubCategory\n ? { applicationSubCategory: config.applicationSubCategory }\n : {}),\n operatingSystem: config.operatingSystem ?? 'Web',\n browserRequirements: 'Requires JavaScript. Requires HTML5.',\n softwareVersion: config.softwareVersion ?? '1.0',\n offers: {\n '@type': 'Offer',\n price: config.price ?? '0',\n priceCurrency: config.priceCurrency ?? 'AUD',\n },\n publisher: {\n '@type': 'Organization',\n name: publisherName,\n url: publisherUrl,\n },\n author: {\n '@type': 'Organization',\n name: publisherName,\n url: publisherUrl,\n },\n inLanguage: config.inLanguage ?? 'en-AU',\n });\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nconst NIGHT_START_HOUR = 18;\nconst NIGHT_END_HOUR = 6;\n\nexport function useIsNight(): boolean {\n const [isNight, setIsNight] = useState(false);\n\n useEffect(() => {\n const check = () => {\n const hour = new Date().getHours();\n return hour >= NIGHT_START_HOUR || hour < NIGHT_END_HOUR;\n };\n setIsNight(check());\n\n let timeout: ReturnType<typeof setTimeout>;\n const update = () => {\n const next = check();\n setIsNight((current) => (current !== next ? next : current));\n timeout = setTimeout(update, 60_000);\n };\n timeout = setTimeout(update, 60_000);\n return () => clearTimeout(timeout);\n }, []);\n\n return isNight;\n}\n"],"mappings":"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","names":["res","data"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors/codes.ts","../src/errors/normalize.ts","../src/api/create-api-client.ts","../src/auth/create-auth-return-path.ts","../src/workspace/access.ts","../src/workspace/subscription.ts","../src/workspace/create-brand-settings.ts","../src/workspace/create-workspace-appearance.ts","../src/workspace/create-workspace-appearance-api.ts","../src/sync/create-cross-app-sync.ts","../src/sync/create-live-refresh.ts","../src/notifications/create-notifications-client.ts","../src/notifications/create-web-push-client.ts","../src/ai/suite-ai-job.ts","../src/webhooks/webhook-activity-actions.ts","../src/seo/create-json-ld.ts","../src/hooks/use-is-night.ts"],"sourcesContent":["// AUTO-GENERATED from ERROR_CODES.md by scripts/error-codes/generate.py. Do not edit.\n\nexport const ERROR_CODES = {\n AUTH_001: 'E-AUTH-001',\n AUTH_002: 'E-AUTH-002',\n AUTH_003: 'E-AUTH-003',\n VALID_001: 'E-VALID-001',\n DATA_001: 'E-DATA-001',\n DATA_002: 'E-DATA-002',\n LIMIT_001: 'E-LIMIT-001',\n NET_001: 'E-NET-001',\n EXT_001: 'E-EXT-001',\n EXT_002: 'E-EXT-002',\n SYS_001: 'E-SYS-001',\n SYS_002: 'E-SYS-002',\n CLIENT_001: 'E-CLIENT-001',\n CLIENT_002: 'E-CLIENT-002',\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\n\nexport const DEFAULT_ERROR_CODE: ErrorCode = 'E-SYS-001';\n\nexport const ERROR_COPY: Record<ErrorCode, string> = {\n 'E-AUTH-001': 'Sign-in was not accepted. Check your details and try again.',\n 'E-AUTH-002': 'Your session has ended. Sign in again to continue.',\n 'E-AUTH-003': 'You do not have access to this. Ask an administrator for access.',\n 'E-VALID-001': 'Some details need correcting before this can be saved.',\n 'E-DATA-001': 'We could not find what you asked for. It may have been removed.',\n 'E-DATA-002': 'This conflicts with existing information. Review it and try again.',\n 'E-LIMIT-001': 'Too many requests right now. Wait a moment and try again.',\n 'E-NET-001': 'We could not reach the service. Try again in a moment.',\n 'E-EXT-001': 'The <capability> is unavailable right now. Try again shortly.',\n 'E-EXT-002': 'The <capability> could not complete that request. Try again, or adjust the input.',\n 'E-SYS-001': 'Something went wrong on our side. We have recorded it. Try again.',\n 'E-SYS-002': 'This feature is temporarily unavailable while we carry out maintenance.',\n 'E-CLIENT-001': 'You appear to be offline. Check your connection and try again.',\n 'E-CLIENT-002': 'We could not read the response. Reload the page and try again.',\n};\n\nexport const ERROR_RETRYABLE: Record<ErrorCode, boolean> = {\n 'E-AUTH-001': false,\n 'E-AUTH-002': true,\n 'E-AUTH-003': false,\n 'E-VALID-001': false,\n 'E-DATA-001': false,\n 'E-DATA-002': false,\n 'E-LIMIT-001': true,\n 'E-NET-001': true,\n 'E-EXT-001': true,\n 'E-EXT-002': true,\n 'E-SYS-001': true,\n 'E-SYS-002': false,\n 'E-CLIENT-001': true,\n 'E-CLIENT-002': true,\n};\n\nconst ERROR_CODE_SET = new Set<string>(Object.values(ERROR_CODES));\n\nexport function isErrorCode(value: unknown): value is ErrorCode {\n return typeof value === 'string' && ERROR_CODE_SET.has(value);\n}\n\nexport function copyFor(code: ErrorCode, capability?: string): string {\n const copy = ERROR_COPY[code] ?? ERROR_COPY[DEFAULT_ERROR_CODE];\n if (copy.includes('<capability>')) {\n return copy.replace('<capability>', capability ?? 'service');\n }\n return copy;\n}\n","import {\n DEFAULT_ERROR_CODE,\n ERROR_CODES,\n ERROR_RETRYABLE,\n copyFor,\n isErrorCode,\n type ErrorCode,\n} from './codes';\n\n/**\n * The single normalised shape every caught failure is reduced to before any\n * component sees it. `message` is always resolved from the local registry;\n * upstream text is never carried through.\n */\nexport type AppError = {\n code: ErrorCode;\n message: string;\n correlationId?: string;\n retryable: boolean;\n};\n\nconst STATUS_TO_CODE: Record<number, ErrorCode> = {\n 400: ERROR_CODES.VALID_001,\n 401: ERROR_CODES.AUTH_001,\n 403: ERROR_CODES.AUTH_003,\n 404: ERROR_CODES.DATA_001,\n 405: ERROR_CODES.SYS_001,\n 409: ERROR_CODES.DATA_002,\n 422: ERROR_CODES.VALID_001,\n 429: ERROR_CODES.LIMIT_001,\n 500: ERROR_CODES.SYS_001,\n 502: ERROR_CODES.EXT_002,\n 503: ERROR_CODES.NET_001,\n};\n\nexport function codeForStatus(status: number): ErrorCode {\n if (STATUS_TO_CODE[status]) return STATUS_TO_CODE[status];\n if (status >= 500) return ERROR_CODES.SYS_001;\n if (status >= 400) return ERROR_CODES.VALID_001;\n return ERROR_CODES.SYS_001;\n}\n\nfunction build(\n code: ErrorCode,\n correlationId?: string,\n retryable?: boolean,\n capability?: string,\n): AppError {\n return {\n code,\n message: copyFor(code, capability),\n ...(correlationId ? { correlationId } : {}),\n retryable: retryable ?? ERROR_RETRYABLE[code],\n };\n}\n\nfunction extractEnvelope(\n input: unknown,\n): { code: string; correlationId?: string; retryable?: boolean } | null {\n if (typeof input !== 'object' || input === null) return null;\n const error = (input as Record<string, unknown>).error;\n if (typeof error !== 'object' || error === null) return null;\n const record = error as Record<string, unknown>;\n return {\n code: typeof record.code === 'string' ? record.code : '',\n correlationId:\n typeof record.correlation_id === 'string'\n ? record.correlation_id\n : undefined,\n retryable:\n typeof record.retryable === 'boolean' ? record.retryable : undefined,\n };\n}\n\n/**\n * Reduce an unknown value to an `AppError`. Accepts only the envelope shape;\n * every other key on the input is discarded. Unknown or absent codes fall back.\n */\nexport function normalizeError(\n input: unknown,\n fallback: ErrorCode = DEFAULT_ERROR_CODE,\n capability?: string,\n): AppError {\n const envelope = extractEnvelope(input);\n if (!envelope) return build(fallback, undefined, undefined, capability);\n const resolved = isErrorCode(envelope.code) ? envelope.code : fallback;\n return build(\n resolved,\n envelope.correlationId,\n envelope.retryable,\n capability,\n );\n}\n\n/** Normalise a non-2xx `Response` without ever reading its raw message. */\nexport async function appErrorFromResponse(\n response: Response,\n capability?: string,\n): Promise<AppError> {\n const fallback = codeForStatus(response.status);\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n body = undefined;\n }\n return normalizeError(body, fallback, capability);\n}\n\n/** Normalise a thrown fetch failure: offline, aborted, or unreachable. */\nexport function normalizeThrown(input: unknown, capability?: string): AppError {\n if (typeof navigator !== 'undefined' && navigator.onLine === false) {\n return build(ERROR_CODES.CLIENT_001, undefined, undefined, capability);\n }\n if (input instanceof DOMException && input.name === 'AbortError') {\n return build(ERROR_CODES.NET_001, undefined, undefined, capability);\n }\n return build(ERROR_CODES.NET_001, undefined, undefined, capability);\n}\n\n/** Display string for a failure surface: capability copy plus the code. */\nexport function formatAppError(error: AppError): string {\n return `${error.message} (Reference: ${error.code})`;\n}\n","import { appErrorFromResponse, formatAppError } from '../errors/normalize';\n\nexport type ApiFetchInit = RequestInit & { skipAuth?: boolean };\n\nexport interface ApiClientConfig {\n /** localStorage key namespace, e.g. `tt` or `tides`. */\n storagePrefix: string;\n /** Backend refresh endpoint, e.g. `/api/turtletime/auth/refresh/`. */\n refreshPath: string;\n /** Overrides `NEXT_PUBLIC_API_URL` / `http://localhost:8000`. */\n baseUrl?: string;\n accessTokenKey?: string;\n refreshTokenKey?: string;\n workspaceKey?: string;\n /** Suite-wide workspace cookie name. */\n workspaceCookieName?: string;\n /** Overrides `NEXT_PUBLIC_WORKSPACE_COOKIE_DOMAIN`. */\n workspaceCookieDomain?: string;\n /** Fired when the active workspace changes in this tab. */\n onWorkspaceChange?: (id: string | null) => void;\n /** Fired during sign-out before storage is cleared. */\n onSignOut?: () => void;\n /** Clear the workspace key on sign-out (default `true`). */\n clearWorkspaceOnSignOut?: boolean;\n /** Extra localStorage keys removed on sign-out (e.g. portal app tokens). */\n extraStorageKeysOnClear?: string[];\n /** sessionStorage key prefixes to clear on sign-out. */\n sessionStorageClearPrefixes?: string[];\n /** Refuse to send the session to an unexpected absolute origin. */\n enforceSameOrigin?: boolean;\n}\n\n/**\n * Build a browser API client scoped to one ShellStack app. Every app shares the\n * same fetch/refresh/workspace behaviour; only the storage namespace and\n * refresh path differ.\n */\nexport function createApiClient(config: ApiClientConfig) {\n const API_URL =\n config.baseUrl ??\n process.env.NEXT_PUBLIC_API_URL ??\n 'http://localhost:8000';\n\n // Suite-wide workspace selection. Cookies (unlike localStorage) are shared\n // across ports/subdomains, so every product app reads the same value. Set\n // NEXT_PUBLIC_WORKSPACE_COOKIE_DOMAIN (e.g. \".xenide.io\") to share across\n // subdomains in production; leave unset for localhost.\n const SHARED_WORKSPACE_COOKIE =\n config.workspaceCookieName ?? 'xenide_active_workspace_id';\n const SHARED_WORKSPACE_COOKIE_DOMAIN =\n config.workspaceCookieDomain ??\n process.env.NEXT_PUBLIC_WORKSPACE_COOKIE_DOMAIN;\n\n const ACCESS_KEY = config.accessTokenKey ?? `${config.storagePrefix}_access_token`;\n const REFRESH_KEY =\n config.refreshTokenKey ?? `${config.storagePrefix}_refresh_token`;\n const WORKSPACE_KEY =\n config.workspaceKey ?? `${config.storagePrefix}_active_workspace_id`;\n const CLEAR_WORKSPACE = config.clearWorkspaceOnSignOut ?? true;\n\n let accessToken: string | null = null;\n let refreshToken: string | null = null;\n let activeWorkspaceId: string | null = null;\n\n let authFailureHandler: (() => void) | null = null;\n let authFailureFired = false;\n let refreshPromise: Promise<boolean> | null = null;\n\n function readSharedWorkspaceId(): string | null {\n if (typeof document === 'undefined') return null;\n const prefix = `${SHARED_WORKSPACE_COOKIE}=`;\n const entry = document.cookie\n .split('; ')\n .find((item) => item.startsWith(prefix));\n if (!entry) return null;\n const raw = entry.slice(prefix.length);\n if (!raw) return null;\n try {\n return decodeURIComponent(raw) || null;\n } catch {\n return raw;\n }\n }\n\n function writeSharedWorkspaceId(id: string | null): void {\n if (typeof document === 'undefined') return;\n const value = id ? encodeURIComponent(id) : '';\n const maxAge = id ? 60 * 60 * 24 * 365 : 0;\n const domain = SHARED_WORKSPACE_COOKIE_DOMAIN\n ? `; domain=${SHARED_WORKSPACE_COOKIE_DOMAIN}`\n : '';\n document.cookie = `${SHARED_WORKSPACE_COOKIE}=${value}; path=/; max-age=${maxAge}; SameSite=Lax${domain}`;\n }\n\n function onAuthFailure(handler: () => void): void {\n authFailureHandler = handler;\n authFailureFired = false;\n }\n\n function triggerAuthFailure(): void {\n if (authFailureFired) return;\n authFailureFired = true;\n authFailureHandler?.();\n }\n\n function setTokens(access: string, refresh?: string | null): void {\n accessToken = access;\n authFailureFired = false;\n if (refresh !== undefined) refreshToken = refresh;\n if (typeof window !== 'undefined') {\n window.localStorage.setItem(ACCESS_KEY, access);\n if (refresh) window.localStorage.setItem(REFRESH_KEY, refresh);\n else if (refresh !== undefined) window.localStorage.removeItem(REFRESH_KEY);\n }\n }\n\n function getAccessToken(): string | null {\n if (accessToken) return accessToken;\n if (typeof window !== 'undefined') {\n accessToken = window.localStorage.getItem(ACCESS_KEY);\n }\n return accessToken;\n }\n\n function getRefreshToken(): string | null {\n if (refreshToken) return refreshToken;\n if (typeof window !== 'undefined') {\n refreshToken = window.localStorage.getItem(REFRESH_KEY);\n }\n return refreshToken;\n }\n\n function hasAccessToken(): boolean {\n return Boolean(getAccessToken());\n }\n\n function setActiveWorkspaceId(\n id: string | null,\n options?: { share?: boolean },\n ): void {\n const changed = activeWorkspaceId !== id;\n activeWorkspaceId = id;\n if (typeof window !== 'undefined') {\n if (id) {\n window.localStorage.setItem(WORKSPACE_KEY, id);\n } else {\n window.localStorage.removeItem(WORKSPACE_KEY);\n }\n if (changed) config.onWorkspaceChange?.(id);\n }\n // Only an explicit choice should update the suite-wide selection; a fallback\n // default must not clobber the workspace another app is using.\n if (options?.share) writeSharedWorkspaceId(id);\n }\n\n function getActiveWorkspaceId(): string | null {\n if (activeWorkspaceId) return activeWorkspaceId;\n if (typeof window !== 'undefined') {\n activeWorkspaceId =\n readSharedWorkspaceId() ?? window.localStorage.getItem(WORKSPACE_KEY);\n }\n return activeWorkspaceId;\n }\n\n function restoreWorkspace(): void {\n if (typeof window !== 'undefined') {\n activeWorkspaceId =\n readSharedWorkspaceId() ?? window.localStorage.getItem(WORKSPACE_KEY);\n }\n }\n\n function clearAuth(): void {\n config.onSignOut?.();\n accessToken = null;\n refreshToken = null;\n activeWorkspaceId = null;\n if (typeof window !== 'undefined') {\n window.localStorage.removeItem(ACCESS_KEY);\n window.localStorage.removeItem(REFRESH_KEY);\n if (CLEAR_WORKSPACE) window.localStorage.removeItem(WORKSPACE_KEY);\n for (const key of config.extraStorageKeysOnClear ?? []) {\n window.localStorage.removeItem(key);\n }\n const prefixes = config.sessionStorageClearPrefixes ?? [];\n if (prefixes.length > 0) {\n try {\n for (let i = window.sessionStorage.length - 1; i >= 0; i -= 1) {\n const key = window.sessionStorage.key(i);\n if (key && prefixes.some((prefix) => key.startsWith(prefix))) {\n window.sessionStorage.removeItem(key);\n }\n }\n } catch {\n // ignore storage access errors\n }\n }\n }\n }\n\n function restoreTokens(): void {\n if (typeof window !== 'undefined') {\n accessToken = window.localStorage.getItem(ACCESS_KEY);\n refreshToken = window.localStorage.getItem(REFRESH_KEY);\n activeWorkspaceId =\n readSharedWorkspaceId() ?? window.localStorage.getItem(WORKSPACE_KEY);\n }\n }\n\n async function refreshAccessToken(): Promise<boolean> {\n if (refreshPromise) return refreshPromise;\n refreshPromise = refreshAccessTokenInternal().finally(() => {\n refreshPromise = null;\n });\n return refreshPromise;\n }\n\n async function refreshAccessTokenInternal(): Promise<boolean> {\n const refresh = getRefreshToken();\n if (!refresh) {\n triggerAuthFailure();\n return false;\n }\n\n try {\n const res = await fetch(`${API_URL}${config.refreshPath}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({ refresh }),\n });\n if (!res.ok) {\n clearAuth();\n triggerAuthFailure();\n return false;\n }\n const data = await res.json();\n setTokens(data.access, data.refresh);\n authFailureFired = false;\n return true;\n } catch {\n clearAuth();\n triggerAuthFailure();\n return false;\n }\n }\n\n async function apiFetch(\n path: string,\n init: ApiFetchInit = {},\n ): Promise<Response> {\n const { skipAuth, ...requestInit } = init;\n const token = getAccessToken();\n const headers: Record<string, string> = {\n Accept: 'application/json',\n ...(requestInit.headers as Record<string, string>),\n };\n\n const body = requestInit.body;\n const isFormData =\n typeof FormData !== 'undefined' && body instanceof FormData;\n\n if (!isFormData) {\n headers['Content-Type'] = 'application/json';\n }\n\n if (token && !skipAuth) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n const wsId = getActiveWorkspaceId();\n if (wsId && !skipAuth) {\n headers['X-Active-Workspace-Id'] = wsId;\n }\n\n const url = path.startsWith('http') ? path : `${API_URL}${path}`;\n if (\n config.enforceSameOrigin &&\n !skipAuth &&\n path.startsWith('http') &&\n !url.startsWith(API_URL)\n ) {\n throw new Error('Refusing to send the session to an unexpected origin.');\n }\n\n async function doFetch(): Promise<Response> {\n try {\n return await fetch(url, { ...requestInit, headers });\n } catch (error) {\n if (\n init.signal?.aborted ||\n (error instanceof DOMException && error.name === 'AbortError')\n ) {\n throw error;\n }\n throw new Error(\n `Could not reach the API at ${API_URL}. Is the Django backend running?`,\n );\n }\n }\n\n let res = await doFetch();\n\n if (res.status === 401 && token && !skipAuth) {\n const refreshed = await refreshAccessToken();\n if (refreshed) {\n const newToken = getAccessToken();\n if (newToken) {\n headers['Authorization'] = `Bearer ${newToken}`;\n }\n res = await doFetch();\n }\n if (res.status === 401) {\n clearAuth();\n triggerAuthFailure();\n }\n }\n\n return res;\n }\n\n async function parseApiError(res: Response): Promise<string> {\n // Never render upstream text or a status number: normalise, then show the code.\n return formatAppError(await appErrorFromResponse(res));\n }\n\n const api = {\n get: (path: string, init?: ApiFetchInit) => apiFetch(path, init),\n\n post: (path: string, body?: unknown, init: ApiFetchInit = {}) =>\n apiFetch(path, {\n ...init,\n method: 'POST',\n body:\n body instanceof FormData\n ? body\n : body\n ? JSON.stringify(body)\n : undefined,\n }),\n\n put: (path: string, body?: unknown, init: ApiFetchInit = {}) =>\n apiFetch(path, {\n ...init,\n method: 'PUT',\n body:\n body instanceof FormData\n ? body\n : body\n ? JSON.stringify(body)\n : undefined,\n }),\n\n patch: (path: string, body?: unknown, init: ApiFetchInit = {}) =>\n apiFetch(path, {\n ...init,\n method: 'PATCH',\n body:\n body instanceof FormData\n ? body\n : body\n ? JSON.stringify(body)\n : undefined,\n }),\n\n delete: (path: string, init: ApiFetchInit = {}) =>\n apiFetch(path, { ...init, method: 'DELETE' }),\n };\n\n return {\n API_URL,\n onAuthFailure,\n setTokens,\n getAccessToken,\n getRefreshToken,\n hasAccessToken,\n setActiveWorkspaceId,\n getActiveWorkspaceId,\n restoreWorkspace,\n clearAuth,\n restoreTokens,\n refreshAccessToken,\n parseApiError,\n api,\n // Aliases used by Kraken/Shelly.\n restoreSession: restoreTokens,\n clearSession: clearAuth,\n };\n}\n\nexport type ApiClient = ReturnType<typeof createApiClient>;\nexport type Api = ApiClient['api'];\n","export interface AuthReturnPathConfig {\n /** sessionStorage key holding the in-app return path, e.g. `tt_auth_return_to`. */\n returnKey: string;\n /** sessionStorage key caching the portal handoff payload. */\n handoffKey?: string;\n defaultNext?: string;\n portalCallbackPath?: string;\n redeemPath?: string;\n}\n\nexport interface PortalHandoffPayload {\n code?: string;\n token?: string;\n access?: string;\n refresh?: string;\n}\n\nexport interface PortalHandoffRead {\n code: string | null;\n token: string | null;\n refresh: string | null;\n next: string;\n}\n\nexport type AuthReturnPath = ReturnType<typeof createAuthReturnPath>;\n\nexport function createAuthReturnPath(config: AuthReturnPathConfig) {\n const RETURN_KEY = config.returnKey;\n const HANDOFF_KEY = config.handoffKey ?? 'ss_portal_handoff';\n const DEFAULT_NEXT = config.defaultNext ?? '/today';\n const PORTAL_CALLBACK_PATH =\n config.portalCallbackPath ?? '/auth/portal/callback';\n const REDEEM_PATH = config.redeemPath ?? '/api/portal/auth/switch/redeem';\n\n function isSafeAuthReturnPath(\n path: string | null | undefined,\n ): path is string {\n if (\n !path ||\n path[0] !== '/' ||\n path.startsWith('//') ||\n path.startsWith('/\\\\')\n ) {\n return false;\n }\n if (path.includes('\\\\') || path.includes('://')) return false;\n return true;\n }\n\n function stashAuthReturnPath(path: string | null | undefined): void {\n if (typeof window === 'undefined' || !isSafeAuthReturnPath(path)) return;\n sessionStorage.setItem(RETURN_KEY, path);\n }\n\n function portalHandoffUrl(\n base: string,\n data: PortalHandoffPayload,\n next?: string,\n fallbackNext = DEFAULT_NEXT,\n ): string {\n const dest = isSafeAuthReturnPath(next) ? next : fallbackNext;\n const hash = new URLSearchParams();\n if (data.code) {\n hash.set('code', data.code);\n } else {\n const token = data.token || data.access || '';\n if (token) hash.set('token', token);\n if (data.refresh) hash.set('refresh', data.refresh);\n }\n hash.set('next', dest);\n return `${base.replace(/\\/$/, '')}${PORTAL_CALLBACK_PATH}#${hash.toString()}`;\n }\n\n function cachedHandoff(fallbackNext: string): PortalHandoffRead | null {\n try {\n const raw = sessionStorage.getItem(HANDOFF_KEY);\n if (!raw) return null;\n const parsed = JSON.parse(raw) as PortalHandoffRead;\n if (!parsed.code && !parsed.token) return null;\n return {\n code: parsed.code || null,\n token: parsed.token || null,\n refresh: parsed.refresh || null,\n next: isSafeAuthReturnPath(parsed.next) ? parsed.next : fallbackNext,\n };\n } catch {\n sessionStorage.removeItem(HANDOFF_KEY);\n return null;\n }\n }\n\n function readPortalHandoff(fallbackNext = DEFAULT_NEXT): PortalHandoffRead {\n const cached = cachedHandoff(fallbackNext);\n if (cached) return cached;\n const hash = new URLSearchParams(window.location.hash.replace(/^#/, ''));\n const query = new URLSearchParams(window.location.search);\n const nextRaw = hash.get('next') || query.get('next');\n const result: PortalHandoffRead = {\n code: hash.get('code') || query.get('code'),\n token: hash.get('token') || query.get('token'),\n refresh: hash.get('refresh') || query.get('refresh'),\n next: isSafeAuthReturnPath(nextRaw) ? nextRaw : fallbackNext,\n };\n if (result.code || result.token) {\n sessionStorage.setItem(HANDOFF_KEY, JSON.stringify(result));\n }\n // The callback's hard location.replace removes the fragment from history.\n // Calling history.replaceState here makes Next.js remount the callback before\n // its one-time code redemption has completed.\n return result;\n }\n\n let redeemInflight: Promise<PortalHandoffRead> | null = null;\n\n async function redeemPortalHandoff(\n apiUrl: string,\n fallbackNext = DEFAULT_NEXT,\n ): Promise<PortalHandoffRead> {\n redeemInflight ??= (async () => {\n const { code, token, refresh, next } = readPortalHandoff(fallbackNext);\n if (!code) return { code: null, token, refresh, next };\n const response = await fetch(`${apiUrl.replace(/\\/$/, '')}${REDEEM_PATH}`, {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ code }),\n });\n if (!response.ok) return { code: null, token: null, refresh: null, next };\n const data = (await response.json()) as PortalHandoffPayload;\n sessionStorage.removeItem(HANDOFF_KEY);\n return {\n code: null,\n token: data.token || data.access || null,\n refresh: data.refresh || null,\n next,\n };\n })();\n return redeemInflight;\n }\n\n function readAuthReturnPath(): string | null {\n if (typeof window === 'undefined') return null;\n const path = sessionStorage.getItem(RETURN_KEY);\n return isSafeAuthReturnPath(path) ? path : null;\n }\n\n function signInHrefWithReturn(fallbackRedirect?: string | null): string {\n const path = isSafeAuthReturnPath(fallbackRedirect)\n ? fallbackRedirect\n : readAuthReturnPath();\n if (!path) return '/login';\n return `/login?redirect=${encodeURIComponent(path)}`;\n }\n\n function registerHrefWithReturn(returnPath: string): string {\n return `/register?redirect=${encodeURIComponent(returnPath)}`;\n }\n\n return {\n isSafeAuthReturnPath,\n stashAuthReturnPath,\n portalHandoffUrl,\n readPortalHandoff,\n redeemPortalHandoff,\n readAuthReturnPath,\n signInHrefWithReturn,\n registerHrefWithReturn,\n };\n}\n","/** Copy and helpers for workspace seats vs per-app access (shared core model). */\n\nexport const WORKSPACE_SEAT_HELP =\n 'Each member uses one workspace seat. Inviting someone reserves a seat until the invite is revoked or accepted.';\n\nexport const ORGANISATION_SEAT_HELP =\n 'Each organisation member uses one seat across all your workspaces. One invite gives them access to every workspace in your organisation.';\n\nexport const TIDES_ACCESS_HELP = 'Tides — project management.';\n\nexport const TURTLETIME_ACCESS_HELP =\n 'TurtleTime — time tracking add-on (Basic plan and above).';\n\nexport const FREE_PLAN_SEAT_HELP =\n 'Free personal workspaces include one product app only. Create an organisation on ShellStack for both apps and integration.';\n\nexport function hasAppAccess(\n canAccessTurtletime: boolean,\n canAccessTides: boolean,\n): boolean {\n return canAccessTurtletime || canAccessTides;\n}\n\nexport function canDisableAppAccess(\n app: 'turtletime' | 'tides',\n canAccessTurtletime: boolean,\n canAccessTides: boolean,\n): boolean {\n if (app === 'turtletime') return canAccessTides;\n return canAccessTurtletime;\n}\n","/** Per-workspace subscription tier helpers (mirrors core/subscriptions.py). */\n\nexport type SubscriptionTier =\n | 'free'\n | 'plus'\n | 'ai_plus'\n | 'basic'\n | 'pro'\n | 'enterprise';\n\nexport interface WorkspaceSubscription {\n tier: SubscriptionTier;\n tier_label: string;\n limits: {\n seats?: number | null;\n integrated_seats: number | null;\n tides_only_seats: number | null;\n };\n usage: {\n seats?: number;\n integrated_seats: number;\n tides_only_seats: number;\n };\n}\n\nexport type SeatType = 'tides' | 'integrated';\n\nconst TIER_LABELS: Record<SubscriptionTier, string> = {\n free: 'Free',\n plus: 'Plus',\n ai_plus: 'AI Plus',\n basic: 'Basic',\n pro: 'Pro',\n enterprise: 'Enterprise',\n};\n\nexport function tierLabel(tier: SubscriptionTier): string {\n if (tier === 'basic' || tier === 'pro') return 'Plus';\n return TIER_LABELS[tier] ?? 'Free';\n}\n\nexport function resolveSubscriptionTier(\n workspace:\n | {\n subscription?: { tier?: string } | null;\n subscription_tier?: string | null;\n }\n | null\n | undefined,\n): SubscriptionTier {\n const tier = workspace?.subscription?.tier ?? workspace?.subscription_tier;\n if (\n tier === 'plus' ||\n tier === 'ai_plus' ||\n tier === 'basic' ||\n tier === 'pro' ||\n tier === 'enterprise' ||\n tier === 'free'\n ) {\n return tier === 'basic' || tier === 'pro' ? 'plus' : tier;\n }\n return 'free';\n}\n\nexport function tierAllowsTurtletime(tier: SubscriptionTier): boolean {\n return tier !== 'free';\n}\n\nexport function defaultInviteSeatType(tier: SubscriptionTier): SeatType {\n return tierAllowsTurtletime(tier) ? 'integrated' : 'tides';\n}\n\nexport function tierAllowsTidesOnlySeats(): boolean {\n return false;\n}\n\nexport function seatTypeFromFlags(\n canAccessTurtletime: boolean,\n canAccessTides: boolean,\n): SeatType {\n if (canAccessTurtletime && canAccessTides) return 'integrated';\n return 'tides';\n}\n\nexport function flagsFromSeatType(seatType: SeatType): {\n can_access_turtletime: boolean;\n can_access_tides: boolean;\n} {\n if (seatType === 'integrated') {\n return { can_access_turtletime: true, can_access_tides: true };\n }\n return { can_access_turtletime: false, can_access_tides: true };\n}\n\nexport function seatTypeLabel(seatType: SeatType): string {\n return seatType === 'integrated' ? 'Tides + TurtleTime' : 'Tides';\n}\n\nexport function seatTypeDescription(\n tier: SubscriptionTier,\n seatType: SeatType,\n): string {\n if (seatType === 'integrated') {\n return 'Project management and time tracking with TT↔Tides integration.';\n }\n if (!tierAllowsTidesOnlySeats()) {\n return 'Project management included on the Free plan.';\n }\n return 'Project management only — no TurtleTime time tracking.';\n}\n\nexport function planSeatSummary(\n subscription: WorkspaceSubscription | null | undefined,\n): string {\n if (!subscription) return '';\n const { tier, limits, usage } = subscription;\n const seatCap = limits.seats ?? limits.integrated_seats;\n const seatCount = usage.seats ?? usage.integrated_seats;\n if (tier === 'enterprise') {\n return 'Unlimited seats';\n }\n if (seatCap != null) {\n return `${seatCount}/${seatCap} seats`;\n }\n return '';\n}\n","export interface BrandSettingsConfig {\n /** Fallback title when the workspace has not customised one. */\n defaultTitle: string;\n /** Window event dispatched when the brand changes. */\n changedEvent: string;\n}\n\nexport interface WorkspaceBrandPayload {\n brand_logo?: string | null;\n brand_title?: string | null;\n brand_show_title?: boolean;\n}\n\nexport type BrandSettings = ReturnType<typeof createBrandSettings>;\n\nexport function createBrandSettings(config: BrandSettingsConfig) {\n const DEFAULT_BRAND_TITLE = config.defaultTitle;\n const BRAND_SETTINGS_CHANGED = config.changedEvent;\n\n interface WorkspaceBrandState {\n logo: string | null;\n title: string | null;\n showTitle: boolean;\n }\n\n const brandState: WorkspaceBrandState = {\n logo: null,\n title: null,\n showTitle: true,\n };\n\n function notifyBrandChange(): void {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new Event(BRAND_SETTINGS_CHANGED));\n }\n }\n\n function applyWorkspaceBrand(payload: WorkspaceBrandPayload): void {\n if ('brand_logo' in payload) {\n brandState.logo = payload.brand_logo ?? null;\n }\n if ('brand_title' in payload) {\n const title = payload.brand_title;\n brandState.title = title?.trim() ? title.trim() : null;\n }\n if ('brand_show_title' in payload && payload.brand_show_title !== undefined) {\n brandState.showTitle = payload.brand_show_title;\n }\n notifyBrandChange();\n }\n\n function getBrandLogo(): string | null {\n return brandState.logo;\n }\n\n function getCustomBrandTitle(): string | null {\n return brandState.title;\n }\n\n function getBrandShowTitle(): boolean {\n return brandState.showTitle;\n }\n\n /** Title shown in navigation — null when hidden. */\n function getDisplayBrandTitle(): string | null {\n if (!getBrandShowTitle()) return null;\n const custom = getCustomBrandTitle();\n if (custom === null) return DEFAULT_BRAND_TITLE;\n return custom || DEFAULT_BRAND_TITLE;\n }\n\n /** @deprecated Brand is persisted via workspace appearance API. */\n function setBrandLogo(dataUrl: string | null): void {\n brandState.logo = dataUrl;\n notifyBrandChange();\n }\n\n /** @deprecated Brand is persisted via workspace appearance API. */\n function setCustomBrandTitle(title: string | null): void {\n brandState.title = title?.trim() ? title.trim() : null;\n notifyBrandChange();\n }\n\n /** @deprecated Brand is persisted via workspace appearance API. */\n function setBrandShowTitle(show: boolean): void {\n brandState.showTitle = show;\n notifyBrandChange();\n }\n\n /** @deprecated Brand is persisted via workspace appearance API. */\n function resetBrandTitle(): void {\n brandState.title = null;\n brandState.showTitle = true;\n notifyBrandChange();\n }\n\n return {\n DEFAULT_BRAND_TITLE,\n BRAND_SETTINGS_CHANGED,\n /** @deprecated Use BRAND_SETTINGS_CHANGED */\n BRAND_LOGO_CHANGED: BRAND_SETTINGS_CHANGED,\n applyWorkspaceBrand,\n getBrandLogo,\n getCustomBrandTitle,\n getBrandShowTitle,\n getDisplayBrandTitle,\n setBrandLogo,\n setCustomBrandTitle,\n setBrandShowTitle,\n resetBrandTitle,\n };\n}\n","export interface WorkspaceAppearanceConfig {\n /** Window event dispatched when the active workspace changes. */\n changedEvent: string;\n /** localStorage base key for the theme. */\n themeStorageBase: string;\n /** Reads the active workspace id from the app's API client. */\n getActiveWorkspaceId: () => string | null;\n}\n\nexport type WorkspaceAppearanceHelpers = ReturnType<\n typeof createWorkspaceAppearance\n>;\n\nexport function createWorkspaceAppearance(config: WorkspaceAppearanceConfig) {\n const WORKSPACE_CHANGED = config.changedEvent;\n const THEME_STORAGE_BASE = config.themeStorageBase;\n\n function notifyWorkspaceChanged(): void {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new Event(WORKSPACE_CHANGED));\n }\n }\n\n function scopedStorageKey(\n base: string,\n workspaceId?: string | null,\n ): string {\n const wsId = workspaceId ?? config.getActiveWorkspaceId();\n return wsId ? `${base}:${wsId}` : base;\n }\n\n function getScopedStorageItem(\n base: string,\n workspaceId?: string | null,\n ): string | null {\n if (typeof window === 'undefined') return null;\n try {\n const scoped = localStorage.getItem(scopedStorageKey(base, workspaceId));\n if (scoped !== null) return scoped;\n const wsId = workspaceId ?? config.getActiveWorkspaceId();\n if (wsId) {\n return localStorage.getItem(base);\n }\n return null;\n } catch {\n return null;\n }\n }\n\n function setScopedStorageItem(\n base: string,\n value: string | null,\n workspaceId?: string | null,\n ): void {\n if (typeof window === 'undefined') return;\n const key = scopedStorageKey(base, workspaceId);\n if (value === null) {\n localStorage.removeItem(key);\n } else {\n localStorage.setItem(key, value);\n }\n }\n\n return {\n WORKSPACE_CHANGED,\n THEME_STORAGE_BASE,\n notifyWorkspaceChanged,\n scopedStorageKey,\n getScopedStorageItem,\n setScopedStorageItem,\n };\n}\n","import type { Api } from '../api/create-api-client';\nimport type { WorkspaceBrandPayload } from './create-brand-settings';\n\nexport interface WorkspaceAppearanceApiConfig {\n api: Api;\n parseApiError: (res: Response) => Promise<string>;\n /** Endpoint base, e.g. `/api/turtletime/workspaces/current/appearance/`. */\n basePath: string;\n applyWorkspaceBrand: (payload: WorkspaceBrandPayload) => void;\n}\n\nexport type WorkspaceTheme = 'light' | 'dark';\n\nexport interface WorkspaceAppearance {\n theme: WorkspaceTheme;\n brand_logo: string | null;\n brand_title: string | null;\n brand_show_title: boolean;\n}\n\nexport type WorkspaceAppearancePatch = Partial<\n Pick<WorkspaceAppearance, 'theme' | 'brand_title' | 'brand_show_title'>\n> & {\n brand_logo_file?: File;\n clear_brand_logo?: boolean;\n};\n\nexport class WorkspaceAppearanceError extends Error {\n status: number;\n\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\n }\n}\n\nfunction normalizeTheme(value: unknown): WorkspaceTheme {\n return value === 'light' ? 'light' : 'dark';\n}\n\nfunction parseAppearancePayload(\n data: Record<string, unknown>,\n): WorkspaceAppearance {\n const raw = (data.appearance ?? data) as Record<string, unknown>;\n return {\n theme: normalizeTheme(raw.theme),\n brand_logo: (raw.brand_logo as string | null) ?? null,\n brand_title: (raw.brand_title as string | null) ?? null,\n brand_show_title: Boolean(raw.brand_show_title),\n };\n}\n\nexport function createWorkspaceAppearanceApi(\n config: WorkspaceAppearanceApiConfig,\n) {\n const { api, parseApiError, basePath, applyWorkspaceBrand } = config;\n\n async function fetchWorkspaceAppearance(): Promise<WorkspaceAppearance> {\n const res = await api.get(basePath);\n if (!res.ok) {\n throw new WorkspaceAppearanceError(await parseApiError(res), res.status);\n }\n const data = await res.json();\n return parseAppearancePayload(data);\n }\n\n async function updateWorkspaceAppearance(\n patch: WorkspaceAppearancePatch,\n ): Promise<WorkspaceAppearance> {\n const hasFileUpload = patch.brand_logo_file instanceof File;\n const shouldClearLogo = patch.clear_brand_logo === true;\n\n if (hasFileUpload || shouldClearLogo) {\n const formData = new FormData();\n if (patch.theme) formData.append('theme', patch.theme);\n if (patch.brand_title !== undefined) {\n formData.append('brand_title', patch.brand_title ?? '');\n }\n if (patch.brand_show_title !== undefined) {\n formData.append(\n 'brand_show_title',\n patch.brand_show_title ? 'true' : 'false',\n );\n }\n if (hasFileUpload && patch.brand_logo_file) {\n formData.append('brand_logo', patch.brand_logo_file);\n }\n if (shouldClearLogo) {\n formData.append('clear_brand_logo', 'true');\n }\n\n const res = await api.patch(basePath, formData);\n if (!res.ok) {\n throw new Error(await parseApiError(res));\n }\n const data = await res.json();\n return parseAppearancePayload(data);\n }\n\n const res = await api.patch(basePath, {\n theme: patch.theme,\n brand_title: patch.brand_title,\n brand_show_title: patch.brand_show_title,\n });\n if (!res.ok) {\n throw new Error(await parseApiError(res));\n }\n const data = await res.json();\n return parseAppearancePayload(data);\n }\n\n function cacheWorkspaceAppearanceLocally(\n appearance: WorkspaceAppearance,\n ): void {\n applyWorkspaceBrand({\n brand_logo: appearance.brand_logo,\n brand_title: appearance.brand_title,\n brand_show_title: appearance.brand_show_title,\n });\n }\n\n return {\n WorkspaceAppearanceError,\n fetchWorkspaceAppearance,\n updateWorkspaceAppearance,\n cacheWorkspaceAppearanceLocally,\n };\n}\n","import type { Api } from '../api/create-api-client';\n\nexport type CrossAppSyncTopic = 'time' | 'integration' | 'tasks';\n\nexport interface CrossAppSyncConfig {\n api: Api;\n /** Checkpoint endpoint, e.g. `/api/turtletime/sync-checkpoint/`. */\n checkpointPath: string;\n /** Poll interval while the tab is visible (ms). */\n pollMs?: number;\n}\n\ntype SyncCheckpoint = {\n tasks_synced_at: number;\n time_synced_at: number;\n integration_synced_at: number;\n};\n\nexport type CrossAppSync = ReturnType<typeof createCrossAppSync>;\n\n/**\n * Poll backend checkpoints so sibling apps refresh when data mutates\n * elsewhere. Same-tab instant updates stay the app's responsibility via its\n * own window events.\n */\nexport function createCrossAppSync(config: CrossAppSyncConfig) {\n const pollMs = config.pollMs ?? 5000;\n\n const lastKnown: Record<CrossAppSyncTopic, number> = {\n time: 0,\n integration: 0,\n tasks: 0,\n };\n\n const subscribers: Array<{\n topic?: CrossAppSyncTopic;\n callback: () => void;\n lastSync: number;\n }> = [];\n\n let pollInterval: number | null = null;\n let pollListeners = 0;\n\n async function fetchCheckpoint(): Promise<SyncCheckpoint | null> {\n try {\n const res = await config.api.get(config.checkpointPath);\n if (!res.ok) return null;\n return (await res.json()) as SyncCheckpoint;\n } catch {\n return null;\n }\n }\n\n function notify(topic: CrossAppSyncTopic): void {\n for (const sub of subscribers) {\n if (!sub.topic || sub.topic === topic) {\n if (lastKnown[topic] > sub.lastSync) {\n sub.lastSync = lastKnown[topic];\n sub.callback();\n }\n }\n }\n }\n\n function checkAndNotify(data: SyncCheckpoint): void {\n const tasks = data.tasks_synced_at || 0;\n const time = data.time_synced_at || 0;\n const integration = data.integration_synced_at || 0;\n\n if (tasks > lastKnown.tasks) {\n lastKnown.tasks = tasks;\n notify('tasks');\n }\n if (time > lastKnown.time) {\n lastKnown.time = time;\n notify('time');\n }\n if (integration > lastKnown.integration) {\n lastKnown.integration = integration;\n notify('integration');\n }\n }\n\n function startPolling(): void {\n if (pollInterval !== null || typeof window === 'undefined') return;\n\n void fetchCheckpoint().then((data) => data && checkAndNotify(data));\n pollInterval = window.setInterval(() => {\n if (document.visibilityState === 'visible') {\n void fetchCheckpoint().then((data) => data && checkAndNotify(data));\n }\n }, pollMs);\n }\n\n function stopPolling(): void {\n if (pollListeners > 0) return;\n if (pollInterval !== null) {\n window.clearInterval(pollInterval);\n pollInterval = null;\n }\n }\n\n function broadcastCrossAppSync(_topic: CrossAppSyncTopic): void {\n // Cross-subdomain sync is handled by backend checkpoints + polling.\n // Same-tab instant updates are dispatched via the app's own events.\n void _topic;\n }\n\n function subscribeCrossAppSync(\n onSync: () => void,\n topic?: CrossAppSyncTopic,\n ): () => void {\n if (typeof window === 'undefined') return () => {};\n\n const sub = {\n topic,\n callback: onSync,\n lastSync: lastKnown[topic || 'tasks'],\n };\n subscribers.push(sub);\n pollListeners += 1;\n startPolling();\n\n return () => {\n const index = subscribers.indexOf(sub);\n if (index >= 0) subscribers.splice(index, 1);\n pollListeners -= 1;\n if (pollListeners <= 0) stopPolling();\n };\n }\n\n function lastCrossAppSyncTime(topic?: CrossAppSyncTopic): number {\n return topic ? lastKnown[topic] : Math.max(...Object.values(lastKnown));\n }\n\n return {\n broadcastCrossAppSync,\n subscribeCrossAppSync,\n lastCrossAppSyncTime,\n };\n}\n","import type {\n CrossAppSync,\n CrossAppSyncTopic,\n} from './create-cross-app-sync';\n\nexport interface LiveRefreshOptions {\n /** Same-tab custom events (e.g. `tt-timer-mutated`). */\n localEvents?: string[];\n /** Gentle poll while the tab is visible (ms). */\n pollMs?: number;\n /** Limit cross-tab refreshes to one mutation topic. */\n crossAppTopic?: CrossAppSyncTopic;\n}\n\nexport type LiveRefresh = ReturnType<typeof createLiveRefresh>;\n\nexport function createLiveRefresh(sync: CrossAppSync) {\n /** True if a cross-tab sync for the given topic happened after `since`. */\n function hasPendingCrossAppSync(\n topic: CrossAppSyncTopic | undefined,\n since: number,\n ): boolean {\n return sync.lastCrossAppSyncTime(topic) > since;\n }\n\n /** Re-run `refresh` when sibling apps mutate data, on same-tab events, or on an interval. */\n function subscribeLiveRefresh(\n refresh: () => void,\n options: LiveRefreshOptions = {},\n ): () => void {\n if (typeof window === 'undefined') return () => {};\n\n const cleanups: Array<() => void> = [];\n let lastRefreshAt = Date.now();\n\n const trigger = () => {\n lastRefreshAt = Date.now();\n refresh();\n };\n\n for (const eventName of options.localEvents ?? []) {\n window.addEventListener(eventName, trigger);\n cleanups.push(() => window.removeEventListener(eventName, trigger));\n }\n\n cleanups.push(sync.subscribeCrossAppSync(trigger, options.crossAppTopic));\n\n const shouldRefreshOnFocus = () =>\n hasPendingCrossAppSync(options.crossAppTopic, lastRefreshAt);\n\n const onVisibility = () => {\n if (document.visibilityState === 'visible' && shouldRefreshOnFocus()) {\n trigger();\n }\n };\n document.addEventListener('visibilitychange', onVisibility);\n cleanups.push(() =>\n document.removeEventListener('visibilitychange', onVisibility),\n );\n\n const onFocus = () => {\n if (shouldRefreshOnFocus()) {\n trigger();\n }\n };\n window.addEventListener('focus', onFocus);\n cleanups.push(() => window.removeEventListener('focus', onFocus));\n\n if (options.pollMs && options.pollMs > 0) {\n const interval = window.setInterval(() => {\n if (document.visibilityState === 'visible') trigger();\n }, options.pollMs);\n cleanups.push(() => window.clearInterval(interval));\n }\n\n return () => {\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n return { subscribeLiveRefresh, hasPendingCrossAppSync };\n}\n","import type { Api } from '../api/create-api-client';\n\n/** Cross-app (suite) notification from `/api/notifications/` — shared across ShellStack apps. */\nexport interface SuiteNotification {\n id: string;\n title: string;\n body: string;\n href: string;\n kind: string;\n source_app: string;\n read_at: string | null;\n created_at: string;\n workspace: string | null;\n organisation: string | null;\n}\n\nexport interface SuiteNotificationsResponse {\n notifications: SuiteNotification[];\n unread_count: number;\n}\n\nexport interface NotificationsClientConfig {\n api: Api;\n parseApiError: (res: Response) => Promise<string>;\n cacheMs?: number;\n listPath?: string;\n}\n\nexport type NotificationsClient = ReturnType<typeof createNotificationsClient>;\n\nexport function createNotificationsClient(config: NotificationsClientConfig) {\n const { api, parseApiError } = config;\n const CACHE_MS = config.cacheMs ?? 15_000;\n const LIST_PATH = config.listPath ?? '/api/notifications/';\n\n let notificationsCache: {\n data: SuiteNotificationsResponse;\n at: number;\n } | null = null;\n let notificationsInflight: Promise<SuiteNotificationsResponse> | null = null;\n\n async function fetchSuiteNotifications(): Promise<SuiteNotificationsResponse> {\n if (notificationsCache && Date.now() - notificationsCache.at < CACHE_MS) {\n return notificationsCache.data;\n }\n if (notificationsInflight) return notificationsInflight;\n notificationsInflight = (async () => {\n try {\n const res = await api.get(LIST_PATH);\n if (!res.ok) throw new Error(await parseApiError(res));\n const data = (await res.json()) as Partial<SuiteNotificationsResponse>;\n const result: SuiteNotificationsResponse = {\n notifications: data.notifications ?? [],\n unread_count: data.unread_count ?? 0,\n };\n notificationsCache = { data: result, at: Date.now() };\n return result;\n } finally {\n notificationsInflight = null;\n }\n })();\n return notificationsInflight;\n }\n\n async function markSuiteNotificationRead(id: string): Promise<void> {\n const res = await api.post(`${LIST_PATH}${id}/read/`);\n if (!res.ok) throw new Error(await parseApiError(res));\n notificationsCache = null;\n }\n\n async function markAllSuiteNotificationsRead(): Promise<void> {\n const res = await api.post(`${LIST_PATH}read-all/`);\n if (!res.ok) throw new Error(await parseApiError(res));\n notificationsCache = null;\n }\n\n return {\n fetchSuiteNotifications,\n markSuiteNotificationRead,\n markAllSuiteNotificationsRead,\n };\n}\n","import type { Api } from '../api/create-api-client';\n\n/** Browser push state surfaced to settings UI. */\nexport type WebPushState =\n | 'unsupported'\n | 'unconfigured'\n | 'default'\n | 'granted'\n | 'denied'\n | 'subscribed';\n\nexport interface WebPushConfig {\n enabled: boolean;\n public_key: string;\n}\n\nexport interface WebPushClientConfig {\n api: Api;\n parseApiError: (res: Response) => Promise<string>;\n publicKeyPath?: string;\n subscribePath?: string;\n unsubscribePath?: string;\n serviceWorkerPath?: string;\n serviceWorkerScope?: string;\n}\n\nexport type WebPushClient = ReturnType<typeof createWebPushClient>;\n\n/**\n * Browser Web Push client shared across ShellStack apps.\n *\n * Registration is per origin: each app ships its own `/sw.js` and manifest,\n * but all talk to the same suite endpoints, so a subscription made in one app\n * receives notifications for the whole suite.\n */\nexport function createWebPushClient(config: WebPushClientConfig) {\n const { api, parseApiError } = config;\n const PUBLIC_KEY_PATH =\n config.publicKeyPath ?? '/api/notifications/push/public-key/';\n const SUBSCRIBE_PATH =\n config.subscribePath ?? '/api/notifications/push/subscribe/';\n const UNSUBSCRIBE_PATH =\n config.unsubscribePath ?? '/api/notifications/push/unsubscribe/';\n const SW_PATH = config.serviceWorkerPath ?? '/sw.js';\n const SW_SCOPE = config.serviceWorkerScope ?? '/';\n\n function supported(): boolean {\n return (\n typeof window !== 'undefined' &&\n 'serviceWorker' in navigator &&\n 'PushManager' in window &&\n 'Notification' in window\n );\n }\n\n async function fetchConfig(): Promise<WebPushConfig> {\n const res = await api.get(PUBLIC_KEY_PATH);\n if (!res.ok) throw new Error(await parseApiError(res));\n return (await res.json()) as WebPushConfig;\n }\n\n async function getRegistration(): Promise<ServiceWorkerRegistration | null> {\n const existing = await navigator.serviceWorker.getRegistration(SW_SCOPE);\n return existing ?? null;\n }\n\n function urlBase64ToUint8Array(base64: string): Uint8Array<ArrayBuffer> {\n const padding = '='.repeat((4 - (base64.length % 4)) % 4);\n const normalized = (base64 + padding).replace(/-/g, '+').replace(/_/g, '/');\n const raw = atob(normalized);\n const output = new Uint8Array(raw.length);\n for (let i = 0; i < raw.length; i += 1) {\n output[i] = raw.charCodeAt(i);\n }\n return output;\n }\n\n async function getState(): Promise<WebPushState> {\n if (!supported()) return 'unsupported';\n let cfg: WebPushConfig;\n try {\n cfg = await fetchConfig();\n } catch {\n return 'unconfigured';\n }\n if (!cfg.enabled || !cfg.public_key) return 'unconfigured';\n if (Notification.permission === 'denied') return 'denied';\n const registration = await getRegistration();\n const subscription = await registration?.pushManager.getSubscription();\n if (subscription && Notification.permission === 'granted') {\n return 'subscribed';\n }\n if (Notification.permission === 'granted') return 'granted';\n return 'default';\n }\n\n async function enable(): Promise<void> {\n if (!supported()) {\n throw new Error('This browser does not support notifications.');\n }\n\n // Ask for permission first: browsers require it in response to the user\n // gesture, and an awaited request can expire that activation.\n const permission = await Notification.requestPermission();\n if (permission !== 'granted') {\n throw new Error('Notification permission was not granted.');\n }\n\n const cfg = await fetchConfig();\n if (!cfg.enabled || !cfg.public_key) {\n throw new Error('Push notifications are not configured on the server.');\n }\n\n let registration = await getRegistration();\n if (!registration) {\n registration = await navigator.serviceWorker.register(SW_PATH, {\n scope: SW_SCOPE,\n });\n }\n await navigator.serviceWorker.ready;\n\n const existing = await registration.pushManager.getSubscription();\n const subscription =\n existing ??\n (await registration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: urlBase64ToUint8Array(cfg.public_key),\n }));\n\n const json = subscription.toJSON() as {\n endpoint?: string;\n keys?: { p256dh?: string; auth?: string };\n };\n if (!json.endpoint || !json.keys?.p256dh || !json.keys?.auth) {\n throw new Error('The browser returned an incomplete push subscription.');\n }\n\n const res = await api.post(SUBSCRIBE_PATH, {\n endpoint: json.endpoint,\n keys: { p256dh: json.keys.p256dh, auth: json.keys.auth },\n user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n });\n if (!res.ok) throw new Error(await parseApiError(res));\n }\n\n async function disable(): Promise<void> {\n if (!supported()) return;\n const registration = await getRegistration();\n const subscription = await registration?.pushManager.getSubscription();\n if (!subscription) return;\n\n const endpoint = subscription.endpoint;\n try {\n await subscription.unsubscribe();\n } catch {\n // Already gone — still tell the server to stop sending.\n }\n\n const res = await api.post(UNSUBSCRIBE_PATH, { endpoint });\n if (!res.ok) throw new Error(await parseApiError(res));\n }\n\n return { supported, getState, enable, disable };\n}\n","export type SuiteAiJobEvent = {\n sequence: number;\n event_type:\n | 'queued'\n | 'started'\n | 'progress'\n | 'tool'\n | 'completed'\n | 'failed'\n | 'cancelled';\n summary: string;\n payload: Record<string, unknown>;\n created_at: string;\n};\n\nexport type SuiteAiJob = {\n id: string;\n status: 'pending' | 'running' | 'completed' | 'failed';\n events: SuiteAiJobEvent[];\n};\n\nexport function activeSuiteAiJobs(jobs: SuiteAiJob[]) {\n return jobs.filter(\n (job) => job.status === 'pending' || job.status === 'running',\n );\n}\n\nexport function sortedSuiteAiEvents(events: SuiteAiJobEvent[]) {\n return [...events].sort((a, b) => a.sequence - b.sequence);\n}\n\nexport function suiteAiEventSummary(event?: SuiteAiJobEvent) {\n const summary = event?.summary\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u001f]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n if (summary) return summary;\n if (event?.event_type === 'queued') return 'Shelly request queued';\n if (event?.event_type === 'started')\n return 'Shelly started preparing a reply';\n if (event?.event_type === 'completed') return 'Shelly reply ready';\n if (event?.event_type === 'failed') return 'AI request failed';\n return 'Shelly is working';\n}\n","export const WEBHOOK_ACTIVITY_ACTIONS = [\n { id: 'created', label: 'Task created' },\n { id: 'updated', label: 'Task updated' },\n { id: 'status_changed', label: 'Task status changed' },\n { id: 'assigned', label: 'Task assigned' },\n { id: 'commented', label: 'Comment posted' },\n { id: 'project_renamed', label: 'Project renamed' },\n { id: 'project_status_changed', label: 'Project archived or restored' },\n] as const;\n\nconst ALL_WEBHOOK_ACTION_IDS = WEBHOOK_ACTIVITY_ACTIONS.map(\n (action) => action.id,\n);\n\nexport function webhookActionsToSelected(\n stored: string[] | null | undefined,\n): Set<string> {\n if (!stored || stored.length === 0) {\n return new Set(ALL_WEBHOOK_ACTION_IDS);\n }\n return new Set(stored);\n}\n\nexport function selectedToWebhookActions(selected: Set<string>): string[] {\n if (selected.size >= ALL_WEBHOOK_ACTION_IDS.length) {\n return [];\n }\n return ALL_WEBHOOK_ACTION_IDS.filter((id) => selected.has(id));\n}\n","export interface JsonLdConfig {\n name: string;\n url: string;\n description: string;\n applicationCategory?: string;\n applicationSubCategory?: string;\n operatingSystem?: string;\n softwareVersion?: string;\n price?: string;\n priceCurrency?: string;\n inLanguage?: string;\n publisherName?: string;\n publisherUrl?: string;\n}\n\n/** Build a schema.org SoftwareApplication JSON-LD string for a product page. */\nexport function createJsonLd(config: JsonLdConfig): string {\n const publisherName = config.publisherName ?? 'Xenide';\n const publisherUrl = config.publisherUrl ?? 'https://xenide.io';\n\n return JSON.stringify({\n '@context': 'https://schema.org',\n '@type': 'SoftwareApplication',\n name: config.name,\n url: config.url,\n description: config.description,\n applicationCategory: config.applicationCategory ?? 'BusinessApplication',\n ...(config.applicationSubCategory\n ? { applicationSubCategory: config.applicationSubCategory }\n : {}),\n operatingSystem: config.operatingSystem ?? 'Web',\n browserRequirements: 'Requires JavaScript. Requires HTML5.',\n softwareVersion: config.softwareVersion ?? '1.0',\n offers: {\n '@type': 'Offer',\n price: config.price ?? '0',\n priceCurrency: config.priceCurrency ?? 'AUD',\n },\n publisher: {\n '@type': 'Organization',\n name: publisherName,\n url: publisherUrl,\n },\n author: {\n '@type': 'Organization',\n name: publisherName,\n url: publisherUrl,\n },\n inLanguage: config.inLanguage ?? 'en-AU',\n });\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nconst NIGHT_START_HOUR = 18;\nconst NIGHT_END_HOUR = 6;\n\nexport function useIsNight(): boolean {\n const [isNight, setIsNight] = useState(false);\n\n useEffect(() => {\n const check = () => {\n const hour = new Date().getHours();\n return hour >= NIGHT_START_HOUR || hour < NIGHT_END_HOUR;\n };\n setIsNight(check());\n\n let timeout: ReturnType<typeof setTimeout>;\n const update = () => {\n const next = check();\n setIsNight((current) => (current !== next ? next : current));\n timeout = setTimeout(update, 60_000);\n };\n timeout = setTimeout(update, 60_000);\n return () => clearTimeout(timeout);\n }, []);\n\n return isNight;\n}\n"],"mappings":";AAEO,IAAM,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,YAAY;AACd;AAIO,IAAM,qBAAgC;AAEtC,IAAM,aAAwC;AAAA,EACnD,cAAc;AAAA,EACd,cAAc;AAAA,EACd,cAAc;AAAA,EACd,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc;AAAA,EACd,eAAe;AAAA,EACf,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAClB;AAEO,IAAM,kBAA8C;AAAA,EACzD,cAAc;AAAA,EACd,cAAc;AAAA,EACd,cAAc;AAAA,EACd,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc;AAAA,EACd,eAAe;AAAA,EACf,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAClB;AAEA,IAAM,iBAAiB,IAAI,IAAY,OAAO,OAAO,WAAW,CAAC;AAE1D,SAAS,YAAY,OAAoC;AAC9D,SAAO,OAAO,UAAU,YAAY,eAAe,IAAI,KAAK;AAC9D;AAEO,SAAS,QAAQ,MAAiB,YAA6B;AACpE,QAAM,OAAO,WAAW,IAAI,KAAK,WAAW,kBAAkB;AAC9D,MAAI,KAAK,SAAS,cAAc,GAAG;AACjC,WAAO,KAAK,QAAQ,gBAAgB,cAAc,SAAS;AAAA,EAC7D;AACA,SAAO;AACT;;;AChDA,IAAM,iBAA4C;AAAA,EAChD,KAAK,YAAY;AAAA,EACjB,KAAK,YAAY;AAAA,EACjB,KAAK,YAAY;AAAA,EACjB,KAAK,YAAY;AAAA,EACjB,KAAK,YAAY;AAAA,EACjB,KAAK,YAAY;AAAA,EACjB,KAAK,YAAY;AAAA,EACjB,KAAK,YAAY;AAAA,EACjB,KAAK,YAAY;AAAA,EACjB,KAAK,YAAY;AAAA,EACjB,KAAK,YAAY;AACnB;AAEO,SAAS,cAAc,QAA2B;AACvD,MAAI,eAAe,MAAM,EAAG,QAAO,eAAe,MAAM;AACxD,MAAI,UAAU,IAAK,QAAO,YAAY;AACtC,MAAI,UAAU,IAAK,QAAO,YAAY;AACtC,SAAO,YAAY;AACrB;AAEA,SAAS,MACP,MACA,eACA,WACA,YACU;AACV,SAAO;AAAA,IACL;AAAA,IACA,SAAS,QAAQ,MAAM,UAAU;AAAA,IACjC,GAAI,gBAAgB,EAAE,cAAc,IAAI,CAAC;AAAA,IACzC,WAAW,aAAa,gBAAgB,IAAI;AAAA,EAC9C;AACF;AAEA,SAAS,gBACP,OACsE;AACtE,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,QAAS,MAAkC;AACjD,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,SAAS;AACf,SAAO;AAAA,IACL,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;AAAA,IACtD,eACE,OAAO,OAAO,mBAAmB,WAC7B,OAAO,iBACP;AAAA,IACN,WACE,OAAO,OAAO,cAAc,YAAY,OAAO,YAAY;AAAA,EAC/D;AACF;AAMO,SAAS,eACd,OACA,WAAsB,oBACtB,YACU;AACV,QAAM,WAAW,gBAAgB,KAAK;AACtC,MAAI,CAAC,SAAU,QAAO,MAAM,UAAU,QAAW,QAAW,UAAU;AACtE,QAAM,WAAW,YAAY,SAAS,IAAI,IAAI,SAAS,OAAO;AAC9D,SAAO;AAAA,IACL;AAAA,IACA,SAAS;AAAA,IACT,SAAS;AAAA,IACT;AAAA,EACF;AACF;AAGA,eAAsB,qBACpB,UACA,YACmB;AACnB,QAAM,WAAW,cAAc,SAAS,MAAM;AAC9C,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,eAAe,MAAM,UAAU,UAAU;AAClD;AAGO,SAAS,gBAAgB,OAAgB,YAA+B;AAC7E,MAAI,OAAO,cAAc,eAAe,UAAU,WAAW,OAAO;AAClE,WAAO,MAAM,YAAY,YAAY,QAAW,QAAW,UAAU;AAAA,EACvE;AACA,MAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AAChE,WAAO,MAAM,YAAY,SAAS,QAAW,QAAW,UAAU;AAAA,EACpE;AACA,SAAO,MAAM,YAAY,SAAS,QAAW,QAAW,UAAU;AACpE;AAGO,SAAS,eAAe,OAAyB;AACtD,SAAO,GAAG,MAAM,OAAO,gBAAgB,MAAM,IAAI;AACnD;;;ACtFO,SAAS,gBAAgB,QAAyB;AACvD,QAAM,UACJ,OAAO,WACP,QAAQ,IAAI,uBACZ;AAMF,QAAM,0BACJ,OAAO,uBAAuB;AAChC,QAAM,iCACJ,OAAO,yBACP,QAAQ,IAAI;AAEd,QAAM,aAAa,OAAO,kBAAkB,GAAG,OAAO,aAAa;AACnE,QAAM,cACJ,OAAO,mBAAmB,GAAG,OAAO,aAAa;AACnD,QAAM,gBACJ,OAAO,gBAAgB,GAAG,OAAO,aAAa;AAChD,QAAM,kBAAkB,OAAO,2BAA2B;AAE1D,MAAI,cAA6B;AACjC,MAAI,eAA8B;AAClC,MAAI,oBAAmC;AAEvC,MAAI,qBAA0C;AAC9C,MAAI,mBAAmB;AACvB,MAAI,iBAA0C;AAE9C,WAAS,wBAAuC;AAC9C,QAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,UAAM,SAAS,GAAG,uBAAuB;AACzC,UAAM,QAAQ,SAAS,OACpB,MAAM,IAAI,EACV,KAAK,CAAC,SAAS,KAAK,WAAW,MAAM,CAAC;AACzC,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,MAAM,MAAM,MAAM,OAAO,MAAM;AACrC,QAAI,CAAC,IAAK,QAAO;AACjB,QAAI;AACF,aAAO,mBAAmB,GAAG,KAAK;AAAA,IACpC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,WAAS,uBAAuB,IAAyB;AACvD,QAAI,OAAO,aAAa,YAAa;AACrC,UAAM,QAAQ,KAAK,mBAAmB,EAAE,IAAI;AAC5C,UAAM,SAAS,KAAK,KAAK,KAAK,KAAK,MAAM;AACzC,UAAM,SAAS,iCACX,YAAY,8BAA8B,KAC1C;AACJ,aAAS,SAAS,GAAG,uBAAuB,IAAI,KAAK,qBAAqB,MAAM,iBAAiB,MAAM;AAAA,EACzG;AAEA,WAAS,cAAc,SAA2B;AAChD,yBAAqB;AACrB,uBAAmB;AAAA,EACrB;AAEA,WAAS,qBAA2B;AAClC,QAAI,iBAAkB;AACtB,uBAAmB;AACnB,yBAAqB;AAAA,EACvB;AAEA,WAAS,UAAU,QAAgB,SAA+B;AAChE,kBAAc;AACd,uBAAmB;AACnB,QAAI,YAAY,OAAW,gBAAe;AAC1C,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,aAAa,QAAQ,YAAY,MAAM;AAC9C,UAAI,QAAS,QAAO,aAAa,QAAQ,aAAa,OAAO;AAAA,eACpD,YAAY,OAAW,QAAO,aAAa,WAAW,WAAW;AAAA,IAC5E;AAAA,EACF;AAEA,WAAS,iBAAgC;AACvC,QAAI,YAAa,QAAO;AACxB,QAAI,OAAO,WAAW,aAAa;AACjC,oBAAc,OAAO,aAAa,QAAQ,UAAU;AAAA,IACtD;AACA,WAAO;AAAA,EACT;AAEA,WAAS,kBAAiC;AACxC,QAAI,aAAc,QAAO;AACzB,QAAI,OAAO,WAAW,aAAa;AACjC,qBAAe,OAAO,aAAa,QAAQ,WAAW;AAAA,IACxD;AACA,WAAO;AAAA,EACT;AAEA,WAAS,iBAA0B;AACjC,WAAO,QAAQ,eAAe,CAAC;AAAA,EACjC;AAEA,WAAS,qBACP,IACA,SACM;AACN,UAAM,UAAU,sBAAsB;AACtC,wBAAoB;AACpB,QAAI,OAAO,WAAW,aAAa;AACjC,UAAI,IAAI;AACN,eAAO,aAAa,QAAQ,eAAe,EAAE;AAAA,MAC/C,OAAO;AACL,eAAO,aAAa,WAAW,aAAa;AAAA,MAC9C;AACA,UAAI,QAAS,QAAO,oBAAoB,EAAE;AAAA,IAC5C;AAGA,QAAI,SAAS,MAAO,wBAAuB,EAAE;AAAA,EAC/C;AAEA,WAAS,uBAAsC;AAC7C,QAAI,kBAAmB,QAAO;AAC9B,QAAI,OAAO,WAAW,aAAa;AACjC,0BACE,sBAAsB,KAAK,OAAO,aAAa,QAAQ,aAAa;AAAA,IACxE;AACA,WAAO;AAAA,EACT;AAEA,WAAS,mBAAyB;AAChC,QAAI,OAAO,WAAW,aAAa;AACjC,0BACE,sBAAsB,KAAK,OAAO,aAAa,QAAQ,aAAa;AAAA,IACxE;AAAA,EACF;AAEA,WAAS,YAAkB;AACzB,WAAO,YAAY;AACnB,kBAAc;AACd,mBAAe;AACf,wBAAoB;AACpB,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,aAAa,WAAW,UAAU;AACzC,aAAO,aAAa,WAAW,WAAW;AAC1C,UAAI,gBAAiB,QAAO,aAAa,WAAW,aAAa;AACjE,iBAAW,OAAO,OAAO,2BAA2B,CAAC,GAAG;AACtD,eAAO,aAAa,WAAW,GAAG;AAAA,MACpC;AACA,YAAM,WAAW,OAAO,+BAA+B,CAAC;AACxD,UAAI,SAAS,SAAS,GAAG;AACvB,YAAI;AACF,mBAAS,IAAI,OAAO,eAAe,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;AAC7D,kBAAM,MAAM,OAAO,eAAe,IAAI,CAAC;AACvC,gBAAI,OAAO,SAAS,KAAK,CAAC,WAAW,IAAI,WAAW,MAAM,CAAC,GAAG;AAC5D,qBAAO,eAAe,WAAW,GAAG;AAAA,YACtC;AAAA,UACF;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,WAAS,gBAAsB;AAC7B,QAAI,OAAO,WAAW,aAAa;AACjC,oBAAc,OAAO,aAAa,QAAQ,UAAU;AACpD,qBAAe,OAAO,aAAa,QAAQ,WAAW;AACtD,0BACE,sBAAsB,KAAK,OAAO,aAAa,QAAQ,aAAa;AAAA,IACxE;AAAA,EACF;AAEA,iBAAe,qBAAuC;AACpD,QAAI,eAAgB,QAAO;AAC3B,qBAAiB,2BAA2B,EAAE,QAAQ,MAAM;AAC1D,uBAAiB;AAAA,IACnB,CAAC;AACD,WAAO;AAAA,EACT;AAEA,iBAAe,6BAA+C;AAC5D,UAAM,UAAU,gBAAgB;AAChC,QAAI,CAAC,SAAS;AACZ,yBAAmB;AACnB,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,OAAO,WAAW,IAAI;AAAA,QACzD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,QACV;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC;AAAA,MAClC,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,kBAAU;AACV,2BAAmB;AACnB,eAAO;AAAA,MACT;AACA,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,gBAAU,KAAK,QAAQ,KAAK,OAAO;AACnC,yBAAmB;AACnB,aAAO;AAAA,IACT,QAAQ;AACN,gBAAU;AACV,yBAAmB;AACnB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,iBAAe,SACb,MACA,OAAqB,CAAC,GACH;AACnB,UAAM,EAAE,UAAU,GAAG,YAAY,IAAI;AACrC,UAAM,QAAQ,eAAe;AAC7B,UAAM,UAAkC;AAAA,MACtC,QAAQ;AAAA,MACR,GAAI,YAAY;AAAA,IAClB;AAEA,UAAM,OAAO,YAAY;AACzB,UAAM,aACJ,OAAO,aAAa,eAAe,gBAAgB;AAErD,QAAI,CAAC,YAAY;AACf,cAAQ,cAAc,IAAI;AAAA,IAC5B;AAEA,QAAI,SAAS,CAAC,UAAU;AACtB,cAAQ,eAAe,IAAI,UAAU,KAAK;AAAA,IAC5C;AAEA,UAAM,OAAO,qBAAqB;AAClC,QAAI,QAAQ,CAAC,UAAU;AACrB,cAAQ,uBAAuB,IAAI;AAAA,IACrC;AAEA,UAAM,MAAM,KAAK,WAAW,MAAM,IAAI,OAAO,GAAG,OAAO,GAAG,IAAI;AAC9D,QACE,OAAO,qBACP,CAAC,YACD,KAAK,WAAW,MAAM,KACtB,CAAC,IAAI,WAAW,OAAO,GACvB;AACA,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACzE;AAEA,mBAAe,UAA6B;AAC1C,UAAI;AACF,eAAO,MAAM,MAAM,KAAK,EAAE,GAAG,aAAa,QAAQ,CAAC;AAAA,MACrD,SAAS,OAAO;AACd,YACE,KAAK,QAAQ,WACZ,iBAAiB,gBAAgB,MAAM,SAAS,cACjD;AACA,gBAAM;AAAA,QACR;AACA,cAAM,IAAI;AAAA,UACR,8BAA8B,OAAO;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,MAAM,MAAM,QAAQ;AAExB,QAAI,IAAI,WAAW,OAAO,SAAS,CAAC,UAAU;AAC5C,YAAM,YAAY,MAAM,mBAAmB;AAC3C,UAAI,WAAW;AACb,cAAM,WAAW,eAAe;AAChC,YAAI,UAAU;AACZ,kBAAQ,eAAe,IAAI,UAAU,QAAQ;AAAA,QAC/C;AACA,cAAM,MAAM,QAAQ;AAAA,MACtB;AACA,UAAI,IAAI,WAAW,KAAK;AACtB,kBAAU;AACV,2BAAmB;AAAA,MACrB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,iBAAe,cAAc,KAAgC;AAE3D,WAAO,eAAe,MAAM,qBAAqB,GAAG,CAAC;AAAA,EACvD;AAEA,QAAM,MAAM;AAAA,IACV,KAAK,CAAC,MAAc,SAAwB,SAAS,MAAM,IAAI;AAAA,IAE/D,MAAM,CAAC,MAAc,MAAgB,OAAqB,CAAC,MACzD,SAAS,MAAM;AAAA,MACb,GAAG;AAAA,MACH,QAAQ;AAAA,MACR,MACE,gBAAgB,WACZ,OACA,OACE,KAAK,UAAU,IAAI,IACnB;AAAA,IACV,CAAC;AAAA,IAEH,KAAK,CAAC,MAAc,MAAgB,OAAqB,CAAC,MACxD,SAAS,MAAM;AAAA,MACb,GAAG;AAAA,MACH,QAAQ;AAAA,MACR,MACE,gBAAgB,WACZ,OACA,OACE,KAAK,UAAU,IAAI,IACnB;AAAA,IACV,CAAC;AAAA,IAEH,OAAO,CAAC,MAAc,MAAgB,OAAqB,CAAC,MAC1D,SAAS,MAAM;AAAA,MACb,GAAG;AAAA,MACH,QAAQ;AAAA,MACR,MACE,gBAAgB,WACZ,OACA,OACE,KAAK,UAAU,IAAI,IACnB;AAAA,IACV,CAAC;AAAA,IAEH,QAAQ,CAAC,MAAc,OAAqB,CAAC,MAC3C,SAAS,MAAM,EAAE,GAAG,MAAM,QAAQ,SAAS,CAAC;AAAA,EAChD;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AACF;;;AC3WO,SAAS,qBAAqB,QAA8B;AACjE,QAAM,aAAa,OAAO;AAC1B,QAAM,cAAc,OAAO,cAAc;AACzC,QAAM,eAAe,OAAO,eAAe;AAC3C,QAAM,uBACJ,OAAO,sBAAsB;AAC/B,QAAM,cAAc,OAAO,cAAc;AAEzC,WAAS,qBACP,MACgB;AAChB,QACE,CAAC,QACD,KAAK,CAAC,MAAM,OACZ,KAAK,WAAW,IAAI,KACpB,KAAK,WAAW,KAAK,GACrB;AACA,aAAO;AAAA,IACT;AACA,QAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,KAAK,EAAG,QAAO;AACxD,WAAO;AAAA,EACT;AAEA,WAAS,oBAAoB,MAAuC;AAClE,QAAI,OAAO,WAAW,eAAe,CAAC,qBAAqB,IAAI,EAAG;AAClE,mBAAe,QAAQ,YAAY,IAAI;AAAA,EACzC;AAEA,WAAS,iBACP,MACA,MACA,MACA,eAAe,cACP;AACR,UAAM,OAAO,qBAAqB,IAAI,IAAI,OAAO;AACjD,UAAM,OAAO,IAAI,gBAAgB;AACjC,QAAI,KAAK,MAAM;AACb,WAAK,IAAI,QAAQ,KAAK,IAAI;AAAA,IAC5B,OAAO;AACL,YAAM,QAAQ,KAAK,SAAS,KAAK,UAAU;AAC3C,UAAI,MAAO,MAAK,IAAI,SAAS,KAAK;AAClC,UAAI,KAAK,QAAS,MAAK,IAAI,WAAW,KAAK,OAAO;AAAA,IACpD;AACA,SAAK,IAAI,QAAQ,IAAI;AACrB,WAAO,GAAG,KAAK,QAAQ,OAAO,EAAE,CAAC,GAAG,oBAAoB,IAAI,KAAK,SAAS,CAAC;AAAA,EAC7E;AAEA,WAAS,cAAc,cAAgD;AACrE,QAAI;AACF,YAAM,MAAM,eAAe,QAAQ,WAAW;AAC9C,UAAI,CAAC,IAAK,QAAO;AACjB,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAI,CAAC,OAAO,QAAQ,CAAC,OAAO,MAAO,QAAO;AAC1C,aAAO;AAAA,QACL,MAAM,OAAO,QAAQ;AAAA,QACrB,OAAO,OAAO,SAAS;AAAA,QACvB,SAAS,OAAO,WAAW;AAAA,QAC3B,MAAM,qBAAqB,OAAO,IAAI,IAAI,OAAO,OAAO;AAAA,MAC1D;AAAA,IACF,QAAQ;AACN,qBAAe,WAAW,WAAW;AACrC,aAAO;AAAA,IACT;AAAA,EACF;AAEA,WAAS,kBAAkB,eAAe,cAAiC;AACzE,UAAM,SAAS,cAAc,YAAY;AACzC,QAAI,OAAQ,QAAO;AACnB,UAAM,OAAO,IAAI,gBAAgB,OAAO,SAAS,KAAK,QAAQ,MAAM,EAAE,CAAC;AACvE,UAAM,QAAQ,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACxD,UAAM,UAAU,KAAK,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM;AACpD,UAAM,SAA4B;AAAA,MAChC,MAAM,KAAK,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM;AAAA,MAC1C,OAAO,KAAK,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO;AAAA,MAC7C,SAAS,KAAK,IAAI,SAAS,KAAK,MAAM,IAAI,SAAS;AAAA,MACnD,MAAM,qBAAqB,OAAO,IAAI,UAAU;AAAA,IAClD;AACA,QAAI,OAAO,QAAQ,OAAO,OAAO;AAC/B,qBAAe,QAAQ,aAAa,KAAK,UAAU,MAAM,CAAC;AAAA,IAC5D;AAIA,WAAO;AAAA,EACT;AAEA,MAAI,iBAAoD;AAExD,iBAAe,oBACb,QACA,eAAe,cACa;AAC5B,yCAAoB,YAAY;AAC9B,YAAM,EAAE,MAAM,OAAO,SAAS,KAAK,IAAI,kBAAkB,YAAY;AACrE,UAAI,CAAC,KAAM,QAAO,EAAE,MAAM,MAAM,OAAO,SAAS,KAAK;AACrD,YAAM,WAAW,MAAM,MAAM,GAAG,OAAO,QAAQ,OAAO,EAAE,CAAC,GAAG,WAAW,IAAI;AAAA,QACzE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,MAC/B,CAAC;AACD,UAAI,CAAC,SAAS,GAAI,QAAO,EAAE,MAAM,MAAM,OAAO,MAAM,SAAS,MAAM,KAAK;AACxE,YAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,qBAAe,WAAW,WAAW;AACrC,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK,SAAS,KAAK,UAAU;AAAA,QACpC,SAAS,KAAK,WAAW;AAAA,QACzB;AAAA,MACF;AAAA,IACF,GAAG;AACH,WAAO;AAAA,EACT;AAEA,WAAS,qBAAoC;AAC3C,QAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,UAAM,OAAO,eAAe,QAAQ,UAAU;AAC9C,WAAO,qBAAqB,IAAI,IAAI,OAAO;AAAA,EAC7C;AAEA,WAAS,qBAAqB,kBAA0C;AACtE,UAAM,OAAO,qBAAqB,gBAAgB,IAC9C,mBACA,mBAAmB;AACvB,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,mBAAmB,mBAAmB,IAAI,CAAC;AAAA,EACpD;AAEA,WAAS,uBAAuB,YAA4B;AAC1D,WAAO,sBAAsB,mBAAmB,UAAU,CAAC;AAAA,EAC7D;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACxKO,IAAM,sBACX;AAEK,IAAM,yBACX;AAEK,IAAM,oBAAoB;AAE1B,IAAM,yBACX;AAEK,IAAM,sBACX;AAEK,SAAS,aACd,qBACA,gBACS;AACT,SAAO,uBAAuB;AAChC;AAEO,SAAS,oBACd,KACA,qBACA,gBACS;AACT,MAAI,QAAQ,aAAc,QAAO;AACjC,SAAO;AACT;;;ACHA,IAAM,cAAgD;AAAA,EACpD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,KAAK;AAAA,EACL,YAAY;AACd;AAEO,SAAS,UAAU,MAAgC;AACxD,MAAI,SAAS,WAAW,SAAS,MAAO,QAAO;AAC/C,SAAO,YAAY,IAAI,KAAK;AAC9B;AAEO,SAAS,wBACd,WAOkB;AAClB,QAAM,OAAO,WAAW,cAAc,QAAQ,WAAW;AACzD,MACE,SAAS,UACT,SAAS,aACT,SAAS,WACT,SAAS,SACT,SAAS,gBACT,SAAS,QACT;AACA,WAAO,SAAS,WAAW,SAAS,QAAQ,SAAS;AAAA,EACvD;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,MAAiC;AACpE,SAAO,SAAS;AAClB;AAEO,SAAS,sBAAsB,MAAkC;AACtE,SAAO,qBAAqB,IAAI,IAAI,eAAe;AACrD;AAEO,SAAS,2BAAoC;AAClD,SAAO;AACT;AAEO,SAAS,kBACd,qBACA,gBACU;AACV,MAAI,uBAAuB,eAAgB,QAAO;AAClD,SAAO;AACT;AAEO,SAAS,kBAAkB,UAGhC;AACA,MAAI,aAAa,cAAc;AAC7B,WAAO,EAAE,uBAAuB,MAAM,kBAAkB,KAAK;AAAA,EAC/D;AACA,SAAO,EAAE,uBAAuB,OAAO,kBAAkB,KAAK;AAChE;AAEO,SAAS,cAAc,UAA4B;AACxD,SAAO,aAAa,eAAe,uBAAuB;AAC5D;AAEO,SAAS,oBACd,MACA,UACQ;AACR,MAAI,aAAa,cAAc;AAC7B,WAAO;AAAA,EACT;AACA,MAAI,CAAC,yBAAyB,GAAG;AAC/B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,gBACd,cACQ;AACR,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,EAAE,MAAM,QAAQ,MAAM,IAAI;AAChC,QAAM,UAAU,OAAO,SAAS,OAAO;AACvC,QAAM,YAAY,MAAM,SAAS,MAAM;AACvC,MAAI,SAAS,cAAc;AACzB,WAAO;AAAA,EACT;AACA,MAAI,WAAW,MAAM;AACnB,WAAO,GAAG,SAAS,IAAI,OAAO;AAAA,EAChC;AACA,SAAO;AACT;;;AC9GO,SAAS,oBAAoB,QAA6B;AAC/D,QAAM,sBAAsB,OAAO;AACnC,QAAM,yBAAyB,OAAO;AAQtC,QAAM,aAAkC;AAAA,IACtC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAEA,WAAS,oBAA0B;AACjC,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,cAAc,IAAI,MAAM,sBAAsB,CAAC;AAAA,IACxD;AAAA,EACF;AAEA,WAAS,oBAAoB,SAAsC;AACjE,QAAI,gBAAgB,SAAS;AAC3B,iBAAW,OAAO,QAAQ,cAAc;AAAA,IAC1C;AACA,QAAI,iBAAiB,SAAS;AAC5B,YAAM,QAAQ,QAAQ;AACtB,iBAAW,QAAQ,OAAO,KAAK,IAAI,MAAM,KAAK,IAAI;AAAA,IACpD;AACA,QAAI,sBAAsB,WAAW,QAAQ,qBAAqB,QAAW;AAC3E,iBAAW,YAAY,QAAQ;AAAA,IACjC;AACA,sBAAkB;AAAA,EACpB;AAEA,WAAS,eAA8B;AACrC,WAAO,WAAW;AAAA,EACpB;AAEA,WAAS,sBAAqC;AAC5C,WAAO,WAAW;AAAA,EACpB;AAEA,WAAS,oBAA6B;AACpC,WAAO,WAAW;AAAA,EACpB;AAGA,WAAS,uBAAsC;AAC7C,QAAI,CAAC,kBAAkB,EAAG,QAAO;AACjC,UAAM,SAAS,oBAAoB;AACnC,QAAI,WAAW,KAAM,QAAO;AAC5B,WAAO,UAAU;AAAA,EACnB;AAGA,WAAS,aAAa,SAA8B;AAClD,eAAW,OAAO;AAClB,sBAAkB;AAAA,EACpB;AAGA,WAAS,oBAAoB,OAA4B;AACvD,eAAW,QAAQ,OAAO,KAAK,IAAI,MAAM,KAAK,IAAI;AAClD,sBAAkB;AAAA,EACpB;AAGA,WAAS,kBAAkB,MAAqB;AAC9C,eAAW,YAAY;AACvB,sBAAkB;AAAA,EACpB;AAGA,WAAS,kBAAwB;AAC/B,eAAW,QAAQ;AACnB,eAAW,YAAY;AACvB,sBAAkB;AAAA,EACpB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA,IAEA,oBAAoB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AClGO,SAAS,0BAA0B,QAAmC;AAC3E,QAAM,oBAAoB,OAAO;AACjC,QAAM,qBAAqB,OAAO;AAElC,WAAS,yBAA+B;AACtC,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,cAAc,IAAI,MAAM,iBAAiB,CAAC;AAAA,IACnD;AAAA,EACF;AAEA,WAAS,iBACP,MACA,aACQ;AACR,UAAM,OAAO,eAAe,OAAO,qBAAqB;AACxD,WAAO,OAAO,GAAG,IAAI,IAAI,IAAI,KAAK;AAAA,EACpC;AAEA,WAAS,qBACP,MACA,aACe;AACf,QAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAI;AACF,YAAM,SAAS,aAAa,QAAQ,iBAAiB,MAAM,WAAW,CAAC;AACvE,UAAI,WAAW,KAAM,QAAO;AAC5B,YAAM,OAAO,eAAe,OAAO,qBAAqB;AACxD,UAAI,MAAM;AACR,eAAO,aAAa,QAAQ,IAAI;AAAA,MAClC;AACA,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,WAAS,qBACP,MACA,OACA,aACM;AACN,QAAI,OAAO,WAAW,YAAa;AACnC,UAAM,MAAM,iBAAiB,MAAM,WAAW;AAC9C,QAAI,UAAU,MAAM;AAClB,mBAAa,WAAW,GAAG;AAAA,IAC7B,OAAO;AACL,mBAAa,QAAQ,KAAK,KAAK;AAAA,IACjC;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC5CO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAGlD,YAAY,SAAiB,QAAgB;AAC3C,UAAM,OAAO;AACb,SAAK,SAAS;AAAA,EAChB;AACF;AAEA,SAAS,eAAe,OAAgC;AACtD,SAAO,UAAU,UAAU,UAAU;AACvC;AAEA,SAAS,uBACP,MACqB;AACrB,QAAM,MAAO,KAAK,cAAc;AAChC,SAAO;AAAA,IACL,OAAO,eAAe,IAAI,KAAK;AAAA,IAC/B,YAAa,IAAI,cAAgC;AAAA,IACjD,aAAc,IAAI,eAAiC;AAAA,IACnD,kBAAkB,QAAQ,IAAI,gBAAgB;AAAA,EAChD;AACF;AAEO,SAAS,6BACd,QACA;AACA,QAAM,EAAE,KAAK,eAAe,UAAU,oBAAoB,IAAI;AAE9D,iBAAe,2BAAyD;AACtE,UAAM,MAAM,MAAM,IAAI,IAAI,QAAQ;AAClC,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,yBAAyB,MAAM,cAAc,GAAG,GAAG,IAAI,MAAM;AAAA,IACzE;AACA,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,WAAO,uBAAuB,IAAI;AAAA,EACpC;AAEA,iBAAe,0BACb,OAC8B;AAC9B,UAAM,gBAAgB,MAAM,2BAA2B;AACvD,UAAM,kBAAkB,MAAM,qBAAqB;AAEnD,QAAI,iBAAiB,iBAAiB;AACpC,YAAM,WAAW,IAAI,SAAS;AAC9B,UAAI,MAAM,MAAO,UAAS,OAAO,SAAS,MAAM,KAAK;AACrD,UAAI,MAAM,gBAAgB,QAAW;AACnC,iBAAS,OAAO,eAAe,MAAM,eAAe,EAAE;AAAA,MACxD;AACA,UAAI,MAAM,qBAAqB,QAAW;AACxC,iBAAS;AAAA,UACP;AAAA,UACA,MAAM,mBAAmB,SAAS;AAAA,QACpC;AAAA,MACF;AACA,UAAI,iBAAiB,MAAM,iBAAiB;AAC1C,iBAAS,OAAO,cAAc,MAAM,eAAe;AAAA,MACrD;AACA,UAAI,iBAAiB;AACnB,iBAAS,OAAO,oBAAoB,MAAM;AAAA,MAC5C;AAEA,YAAMA,OAAM,MAAM,IAAI,MAAM,UAAU,QAAQ;AAC9C,UAAI,CAACA,KAAI,IAAI;AACX,cAAM,IAAI,MAAM,MAAM,cAAcA,IAAG,CAAC;AAAA,MAC1C;AACA,YAAMC,QAAO,MAAMD,KAAI,KAAK;AAC5B,aAAO,uBAAuBC,KAAI;AAAA,IACpC;AAEA,UAAM,MAAM,MAAM,IAAI,MAAM,UAAU;AAAA,MACpC,OAAO,MAAM;AAAA,MACb,aAAa,MAAM;AAAA,MACnB,kBAAkB,MAAM;AAAA,IAC1B,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,MAAM,cAAc,GAAG,CAAC;AAAA,IAC1C;AACA,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,WAAO,uBAAuB,IAAI;AAAA,EACpC;AAEA,WAAS,gCACP,YACM;AACN,wBAAoB;AAAA,MAClB,YAAY,WAAW;AAAA,MACvB,aAAa,WAAW;AAAA,MACxB,kBAAkB,WAAW;AAAA,IAC/B,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACtGO,SAAS,mBAAmB,QAA4B;AAC7D,QAAM,SAAS,OAAO,UAAU;AAEhC,QAAM,YAA+C;AAAA,IACnD,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,EACT;AAEA,QAAM,cAID,CAAC;AAEN,MAAI,eAA8B;AAClC,MAAI,gBAAgB;AAEpB,iBAAe,kBAAkD;AAC/D,QAAI;AACF,YAAM,MAAM,MAAM,OAAO,IAAI,IAAI,OAAO,cAAc;AACtD,UAAI,CAAC,IAAI,GAAI,QAAO;AACpB,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,WAAS,OAAO,OAAgC;AAC9C,eAAW,OAAO,aAAa;AAC7B,UAAI,CAAC,IAAI,SAAS,IAAI,UAAU,OAAO;AACrC,YAAI,UAAU,KAAK,IAAI,IAAI,UAAU;AACnC,cAAI,WAAW,UAAU,KAAK;AAC9B,cAAI,SAAS;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,WAAS,eAAe,MAA4B;AAClD,UAAM,QAAQ,KAAK,mBAAmB;AACtC,UAAM,OAAO,KAAK,kBAAkB;AACpC,UAAM,cAAc,KAAK,yBAAyB;AAElD,QAAI,QAAQ,UAAU,OAAO;AAC3B,gBAAU,QAAQ;AAClB,aAAO,OAAO;AAAA,IAChB;AACA,QAAI,OAAO,UAAU,MAAM;AACzB,gBAAU,OAAO;AACjB,aAAO,MAAM;AAAA,IACf;AACA,QAAI,cAAc,UAAU,aAAa;AACvC,gBAAU,cAAc;AACxB,aAAO,aAAa;AAAA,IACtB;AAAA,EACF;AAEA,WAAS,eAAqB;AAC5B,QAAI,iBAAiB,QAAQ,OAAO,WAAW,YAAa;AAE5D,SAAK,gBAAgB,EAAE,KAAK,CAAC,SAAS,QAAQ,eAAe,IAAI,CAAC;AAClE,mBAAe,OAAO,YAAY,MAAM;AACtC,UAAI,SAAS,oBAAoB,WAAW;AAC1C,aAAK,gBAAgB,EAAE,KAAK,CAAC,SAAS,QAAQ,eAAe,IAAI,CAAC;AAAA,MACpE;AAAA,IACF,GAAG,MAAM;AAAA,EACX;AAEA,WAAS,cAAoB;AAC3B,QAAI,gBAAgB,EAAG;AACvB,QAAI,iBAAiB,MAAM;AACzB,aAAO,cAAc,YAAY;AACjC,qBAAe;AAAA,IACjB;AAAA,EACF;AAEA,WAAS,sBAAsB,QAAiC;AAG9D,SAAK;AAAA,EACP;AAEA,WAAS,sBACP,QACA,OACY;AACZ,QAAI,OAAO,WAAW,YAAa,QAAO,MAAM;AAAA,IAAC;AAEjD,UAAM,MAAM;AAAA,MACV;AAAA,MACA,UAAU;AAAA,MACV,UAAU,UAAU,SAAS,OAAO;AAAA,IACtC;AACA,gBAAY,KAAK,GAAG;AACpB,qBAAiB;AACjB,iBAAa;AAEb,WAAO,MAAM;AACX,YAAM,QAAQ,YAAY,QAAQ,GAAG;AACrC,UAAI,SAAS,EAAG,aAAY,OAAO,OAAO,CAAC;AAC3C,uBAAiB;AACjB,UAAI,iBAAiB,EAAG,aAAY;AAAA,IACtC;AAAA,EACF;AAEA,WAAS,qBAAqB,OAAmC;AAC/D,WAAO,QAAQ,UAAU,KAAK,IAAI,KAAK,IAAI,GAAG,OAAO,OAAO,SAAS,CAAC;AAAA,EACxE;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC5HO,SAAS,kBAAkB,MAAoB;AAEpD,WAAS,uBACP,OACA,OACS;AACT,WAAO,KAAK,qBAAqB,KAAK,IAAI;AAAA,EAC5C;AAGA,WAAS,qBACP,SACA,UAA8B,CAAC,GACnB;AACZ,QAAI,OAAO,WAAW,YAAa,QAAO,MAAM;AAAA,IAAC;AAEjD,UAAM,WAA8B,CAAC;AACrC,QAAI,gBAAgB,KAAK,IAAI;AAE7B,UAAM,UAAU,MAAM;AACpB,sBAAgB,KAAK,IAAI;AACzB,cAAQ;AAAA,IACV;AAEA,eAAW,aAAa,QAAQ,eAAe,CAAC,GAAG;AACjD,aAAO,iBAAiB,WAAW,OAAO;AAC1C,eAAS,KAAK,MAAM,OAAO,oBAAoB,WAAW,OAAO,CAAC;AAAA,IACpE;AAEA,aAAS,KAAK,KAAK,sBAAsB,SAAS,QAAQ,aAAa,CAAC;AAExE,UAAM,uBAAuB,MAC3B,uBAAuB,QAAQ,eAAe,aAAa;AAE7D,UAAM,eAAe,MAAM;AACzB,UAAI,SAAS,oBAAoB,aAAa,qBAAqB,GAAG;AACpE,gBAAQ;AAAA,MACV;AAAA,IACF;AACA,aAAS,iBAAiB,oBAAoB,YAAY;AAC1D,aAAS;AAAA,MAAK,MACZ,SAAS,oBAAoB,oBAAoB,YAAY;AAAA,IAC/D;AAEA,UAAM,UAAU,MAAM;AACpB,UAAI,qBAAqB,GAAG;AAC1B,gBAAQ;AAAA,MACV;AAAA,IACF;AACA,WAAO,iBAAiB,SAAS,OAAO;AACxC,aAAS,KAAK,MAAM,OAAO,oBAAoB,SAAS,OAAO,CAAC;AAEhE,QAAI,QAAQ,UAAU,QAAQ,SAAS,GAAG;AACxC,YAAM,WAAW,OAAO,YAAY,MAAM;AACxC,YAAI,SAAS,oBAAoB,UAAW,SAAQ;AAAA,MACtD,GAAG,QAAQ,MAAM;AACjB,eAAS,KAAK,MAAM,OAAO,cAAc,QAAQ,CAAC;AAAA,IACpD;AAEA,WAAO,MAAM;AACX,iBAAW,WAAW,SAAU,SAAQ;AAAA,IAC1C;AAAA,EACF;AAEA,SAAO,EAAE,sBAAsB,uBAAuB;AACxD;;;ACnDO,SAAS,0BAA0B,QAAmC;AAC3E,QAAM,EAAE,KAAK,cAAc,IAAI;AAC/B,QAAM,WAAW,OAAO,WAAW;AACnC,QAAM,YAAY,OAAO,YAAY;AAErC,MAAI,qBAGO;AACX,MAAI,wBAAoE;AAExE,iBAAe,0BAA+D;AAC5E,QAAI,sBAAsB,KAAK,IAAI,IAAI,mBAAmB,KAAK,UAAU;AACvE,aAAO,mBAAmB;AAAA,IAC5B;AACA,QAAI,sBAAuB,QAAO;AAClC,6BAAyB,YAAY;AACnC,UAAI;AACF,cAAM,MAAM,MAAM,IAAI,IAAI,SAAS;AACnC,YAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,MAAM,cAAc,GAAG,CAAC;AACrD,cAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,cAAM,SAAqC;AAAA,UACzC,eAAe,KAAK,iBAAiB,CAAC;AAAA,UACtC,cAAc,KAAK,gBAAgB;AAAA,QACrC;AACA,6BAAqB,EAAE,MAAM,QAAQ,IAAI,KAAK,IAAI,EAAE;AACpD,eAAO;AAAA,MACT,UAAE;AACA,gCAAwB;AAAA,MAC1B;AAAA,IACF,GAAG;AACH,WAAO;AAAA,EACT;AAEA,iBAAe,0BAA0B,IAA2B;AAClE,UAAM,MAAM,MAAM,IAAI,KAAK,GAAG,SAAS,GAAG,EAAE,QAAQ;AACpD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,MAAM,cAAc,GAAG,CAAC;AACrD,yBAAqB;AAAA,EACvB;AAEA,iBAAe,gCAA+C;AAC5D,UAAM,MAAM,MAAM,IAAI,KAAK,GAAG,SAAS,WAAW;AAClD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,MAAM,cAAc,GAAG,CAAC;AACrD,yBAAqB;AAAA,EACvB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC9CO,SAAS,oBAAoB,QAA6B;AAC/D,QAAM,EAAE,KAAK,cAAc,IAAI;AAC/B,QAAM,kBACJ,OAAO,iBAAiB;AAC1B,QAAM,iBACJ,OAAO,iBAAiB;AAC1B,QAAM,mBACJ,OAAO,mBAAmB;AAC5B,QAAM,UAAU,OAAO,qBAAqB;AAC5C,QAAM,WAAW,OAAO,sBAAsB;AAE9C,WAAS,YAAqB;AAC5B,WACE,OAAO,WAAW,eAClB,mBAAmB,aACnB,iBAAiB,UACjB,kBAAkB;AAAA,EAEtB;AAEA,iBAAe,cAAsC;AACnD,UAAM,MAAM,MAAM,IAAI,IAAI,eAAe;AACzC,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,MAAM,cAAc,GAAG,CAAC;AACrD,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAEA,iBAAe,kBAA6D;AAC1E,UAAM,WAAW,MAAM,UAAU,cAAc,gBAAgB,QAAQ;AACvE,WAAO,YAAY;AAAA,EACrB;AAEA,WAAS,sBAAsB,QAAyC;AACtE,UAAM,UAAU,IAAI,QAAQ,IAAK,OAAO,SAAS,KAAM,CAAC;AACxD,UAAM,cAAc,SAAS,SAAS,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC1E,UAAM,MAAM,KAAK,UAAU;AAC3B,UAAM,SAAS,IAAI,WAAW,IAAI,MAAM;AACxC,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,GAAG;AACtC,aAAO,CAAC,IAAI,IAAI,WAAW,CAAC;AAAA,IAC9B;AACA,WAAO;AAAA,EACT;AAEA,iBAAe,WAAkC;AAC/C,QAAI,CAAC,UAAU,EAAG,QAAO;AACzB,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,YAAY;AAAA,IAC1B,QAAQ;AACN,aAAO;AAAA,IACT;AACA,QAAI,CAAC,IAAI,WAAW,CAAC,IAAI,WAAY,QAAO;AAC5C,QAAI,aAAa,eAAe,SAAU,QAAO;AACjD,UAAM,eAAe,MAAM,gBAAgB;AAC3C,UAAM,eAAe,MAAM,cAAc,YAAY,gBAAgB;AACrE,QAAI,gBAAgB,aAAa,eAAe,WAAW;AACzD,aAAO;AAAA,IACT;AACA,QAAI,aAAa,eAAe,UAAW,QAAO;AAClD,WAAO;AAAA,EACT;AAEA,iBAAe,SAAwB;AACrC,QAAI,CAAC,UAAU,GAAG;AAChB,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAIA,UAAM,aAAa,MAAM,aAAa,kBAAkB;AACxD,QAAI,eAAe,WAAW;AAC5B,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AAEA,UAAM,MAAM,MAAM,YAAY;AAC9B,QAAI,CAAC,IAAI,WAAW,CAAC,IAAI,YAAY;AACnC,YAAM,IAAI,MAAM,sDAAsD;AAAA,IACxE;AAEA,QAAI,eAAe,MAAM,gBAAgB;AACzC,QAAI,CAAC,cAAc;AACjB,qBAAe,MAAM,UAAU,cAAc,SAAS,SAAS;AAAA,QAC7D,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AACA,UAAM,UAAU,cAAc;AAE9B,UAAM,WAAW,MAAM,aAAa,YAAY,gBAAgB;AAChE,UAAM,eACJ,YACC,MAAM,aAAa,YAAY,UAAU;AAAA,MACxC,iBAAiB;AAAA,MACjB,sBAAsB,sBAAsB,IAAI,UAAU;AAAA,IAC5D,CAAC;AAEH,UAAM,OAAO,aAAa,OAAO;AAIjC,QAAI,CAAC,KAAK,YAAY,CAAC,KAAK,MAAM,UAAU,CAAC,KAAK,MAAM,MAAM;AAC5D,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACzE;AAEA,UAAM,MAAM,MAAM,IAAI,KAAK,gBAAgB;AAAA,MACzC,UAAU,KAAK;AAAA,MACf,MAAM,EAAE,QAAQ,KAAK,KAAK,QAAQ,MAAM,KAAK,KAAK,KAAK;AAAA,MACvD,YAAY,OAAO,cAAc,cAAc,UAAU,YAAY;AAAA,IACvE,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,MAAM,cAAc,GAAG,CAAC;AAAA,EACvD;AAEA,iBAAe,UAAyB;AACtC,QAAI,CAAC,UAAU,EAAG;AAClB,UAAM,eAAe,MAAM,gBAAgB;AAC3C,UAAM,eAAe,MAAM,cAAc,YAAY,gBAAgB;AACrE,QAAI,CAAC,aAAc;AAEnB,UAAM,WAAW,aAAa;AAC9B,QAAI;AACF,YAAM,aAAa,YAAY;AAAA,IACjC,QAAQ;AAAA,IAER;AAEA,UAAM,MAAM,MAAM,IAAI,KAAK,kBAAkB,EAAE,SAAS,CAAC;AACzD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,MAAM,cAAc,GAAG,CAAC;AAAA,EACvD;AAEA,SAAO,EAAE,WAAW,UAAU,QAAQ,QAAQ;AAChD;;;AC9IO,SAAS,kBAAkB,MAAoB;AACpD,SAAO,KAAK;AAAA,IACV,CAAC,QAAQ,IAAI,WAAW,aAAa,IAAI,WAAW;AAAA,EACtD;AACF;AAEO,SAAS,oBAAoB,QAA2B;AAC7D,SAAO,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AAC3D;AAEO,SAAS,oBAAoB,OAAyB;AAC3D,QAAM,UAAU,OAAO,QAEpB,QAAQ,oBAAoB,GAAG,EAC/B,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACR,MAAI,QAAS,QAAO;AACpB,MAAI,OAAO,eAAe,SAAU,QAAO;AAC3C,MAAI,OAAO,eAAe;AACxB,WAAO;AACT,MAAI,OAAO,eAAe,YAAa,QAAO;AAC9C,MAAI,OAAO,eAAe,SAAU,QAAO;AAC3C,SAAO;AACT;;;AC5CO,IAAM,2BAA2B;AAAA,EACtC,EAAE,IAAI,WAAW,OAAO,eAAe;AAAA,EACvC,EAAE,IAAI,WAAW,OAAO,eAAe;AAAA,EACvC,EAAE,IAAI,kBAAkB,OAAO,sBAAsB;AAAA,EACrD,EAAE,IAAI,YAAY,OAAO,gBAAgB;AAAA,EACzC,EAAE,IAAI,aAAa,OAAO,iBAAiB;AAAA,EAC3C,EAAE,IAAI,mBAAmB,OAAO,kBAAkB;AAAA,EAClD,EAAE,IAAI,0BAA0B,OAAO,+BAA+B;AACxE;AAEA,IAAM,yBAAyB,yBAAyB;AAAA,EACtD,CAAC,WAAW,OAAO;AACrB;AAEO,SAAS,yBACd,QACa;AACb,MAAI,CAAC,UAAU,OAAO,WAAW,GAAG;AAClC,WAAO,IAAI,IAAI,sBAAsB;AAAA,EACvC;AACA,SAAO,IAAI,IAAI,MAAM;AACvB;AAEO,SAAS,yBAAyB,UAAiC;AACxE,MAAI,SAAS,QAAQ,uBAAuB,QAAQ;AAClD,WAAO,CAAC;AAAA,EACV;AACA,SAAO,uBAAuB,OAAO,CAAC,OAAO,SAAS,IAAI,EAAE,CAAC;AAC/D;;;ACZO,SAAS,aAAa,QAA8B;AACzD,QAAM,gBAAgB,OAAO,iBAAiB;AAC9C,QAAM,eAAe,OAAO,gBAAgB;AAE5C,SAAO,KAAK,UAAU;AAAA,IACpB,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,MAAM,OAAO;AAAA,IACb,KAAK,OAAO;AAAA,IACZ,aAAa,OAAO;AAAA,IACpB,qBAAqB,OAAO,uBAAuB;AAAA,IACnD,GAAI,OAAO,yBACP,EAAE,wBAAwB,OAAO,uBAAuB,IACxD,CAAC;AAAA,IACL,iBAAiB,OAAO,mBAAmB;AAAA,IAC3C,qBAAqB;AAAA,IACrB,iBAAiB,OAAO,mBAAmB;AAAA,IAC3C,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,OAAO,OAAO,SAAS;AAAA,MACvB,eAAe,OAAO,iBAAiB;AAAA,IACzC;AAAA,IACA,WAAW;AAAA,MACT,SAAS;AAAA,MACT,MAAM;AAAA,MACN,KAAK;AAAA,IACP;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,MAAM;AAAA,MACN,KAAK;AAAA,IACP;AAAA,IACA,YAAY,OAAO,cAAc;AAAA,EACnC,CAAC;AACH;;;AChDA,SAAS,WAAW,gBAAgB;AAEpC,IAAM,mBAAmB;AACzB,IAAM,iBAAiB;AAEhB,SAAS,aAAsB;AACpC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,YAAU,MAAM;AACd,UAAM,QAAQ,MAAM;AAClB,YAAM,QAAO,oBAAI,KAAK,GAAE,SAAS;AACjC,aAAO,QAAQ,oBAAoB,OAAO;AAAA,IAC5C;AACA,eAAW,MAAM,CAAC;AAElB,QAAI;AACJ,UAAM,SAAS,MAAM;AACnB,YAAM,OAAO,MAAM;AACnB,iBAAW,CAAC,YAAa,YAAY,OAAO,OAAO,OAAQ;AAC3D,gBAAU,WAAW,QAAQ,GAAM;AAAA,IACrC;AACA,cAAU,WAAW,QAAQ,GAAM;AACnC,WAAO,MAAM,aAAa,OAAO;AAAA,EACnC,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;","names":["res","data"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenide-io/suite-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Framework-agnostic platform layer shared across Xenide ShellStack apps — API client, auth, workspace, cross-app sync, notifications, errors.",
|
|
5
5
|
"author": "Xenide",
|
|
6
6
|
"license": "MIT",
|