import { AnimationEventHandler } from 'react'; import { AriaRole } from 'react'; import { BrowserRouter } from 'react-router'; import { ButtonHTMLAttributes } from 'react'; import { ChangeEventHandler } from 'react'; import { ClipboardEventHandler } from 'react'; import { Component } from 'react'; import { ComponentType } from 'react'; import { CompositionEventHandler } from 'react'; import { Control } from 'react-hook-form'; import { Controller } from 'react-hook-form'; import { ControllerRenderProps } from 'react-hook-form'; import { CSSProperties } from 'react'; import { default as default_2 } from 'dexie'; import { DefaultOptions } from '@tanstack/react-query'; import { DOMElement } from 'react'; import { DragEventHandler } from 'react'; import { ErrorInfo } from 'react'; import { FieldArrayWithId } from 'react-hook-form'; import { FieldError } from 'react-hook-form'; import { FieldErrors } from 'react-hook-form'; import { FieldPath } from 'react-hook-form'; import { FieldValues } from 'react-hook-form'; import { FocusEventHandler } from 'react'; import { FormHTMLAttributes } from 'react'; import { FormProvider } from 'react-hook-form'; import { ForwardRefExoticComponent } from 'react'; import { HashRouter } from 'react-router'; import { HTMLAttributes } from 'react'; import { ImgHTMLAttributes } from 'react'; import { InfiniteData } from '@tanstack/react-query'; import { InputEventHandler } from 'react'; import { InputHTMLAttributes } from 'react'; import { JSX } from 'react'; import { KeyboardEvent as KeyboardEvent_2 } from 'react'; import { KeyboardEventHandler } from 'react'; import { LabelHTMLAttributes } from 'react'; import { lazy } from 'react'; import { Link } from 'react-router'; import { MemoryRouter } from 'react-router'; import { MouseEvent as MouseEvent_2 } from 'react'; import { MouseEventHandler } from 'react'; import { Navigate } from 'react-router'; import { NavigateOptions } from 'react-router'; import { NavLink } from 'react-router'; import { Outlet } from 'react-router'; import { Params } from 'react-router'; import { Path } from 'react-hook-form'; import { PersistOptions } from 'zustand/middleware'; import { PointerEvent as PointerEvent_2 } from 'react'; import { PointerEventHandler } from 'react'; import { QueryClient } from '@tanstack/react-query'; import { QueryKey } from '@tanstack/react-query'; import { ReactElement } from 'react'; import { ReactEventHandler } from 'react'; import { ReactNode } from 'react'; import { ReactPortal } from 'react'; import { redirect } from 'react-router'; import { Ref } from 'react'; import { RefAttributes } from 'react'; import { RefObject } from 'react'; import { Resolver } from 'react-hook-form'; import { Route } from 'react-router'; import { Routes } from 'react-router'; import { SelectHTMLAttributes } from 'react'; import { StateCreator } from 'zustand'; import { StoreApi } from 'zustand'; import { SubmitEventHandler } from 'react'; import { SubmitHandler } from 'react-hook-form'; import { Table as Table_2 } from 'dexie'; import { TextareaHTMLAttributes } from 'react'; import { To } from 'react-router'; import { ToggleEventHandler } from 'react'; import { TouchEvent as TouchEvent_2 } from 'react'; import { TouchEventHandler } from 'react'; import { TransitionEventHandler } from 'react'; import { UIEventHandler } from 'react'; import { UseBoundStore } from 'zustand'; import { useFieldArray } from 'react-hook-form'; import { UseFieldArrayReturn } from 'react-hook-form'; import { useForm } from 'react-hook-form'; import { useFormContext } from 'react-hook-form'; import { UseFormProps } from 'react-hook-form'; import { UseFormRegister } from 'react-hook-form'; import { UseFormReturn } from 'react-hook-form'; import { useFormState } from 'react-hook-form'; import { UseInfiniteQueryOptions } from '@tanstack/react-query'; import { useLocation } from 'react-router'; import { useMatch } from 'react-router'; import { UseMutationOptions } from '@tanstack/react-query'; import { UseMutationResult } from '@tanstack/react-query'; import { useNavigate } from 'react-router'; import { useParams } from 'react-router'; import { UseQueryOptions } from '@tanstack/react-query'; import { UseQueryResult } from '@tanstack/react-query'; import { useRouteError } from 'react-router'; import { useSearchParams } from 'react-router'; import { useWatch } from 'react-hook-form'; import { WheelEventHandler } from 'react'; import { z } from 'zod'; /** * What every formatter prints when it has nothing to print. * * An em dash rather than an empty string, because in a table those are two * different claims: a blank cell reads as "this column does not apply here", * while `—` reads as "this value is missing". `formatDurationMs` in `/perf` has * answered this way since it shipped, and `formatPercent` joined it in 0.65.0; * this constant is what stops the third answer from appearing. */ export declare const ABSENT_TEXT = "\u2014"; /** * A pluggable access-control strategy. Implementations decide whether a given * action is allowed, returning a plain boolean, a {@link CanResult}, or a * promise of either (for async sources such as a remote policy server). */ export declare interface AccessControl { /** * Resolve whether the described action is permitted. * * @param params - The action/resource being checked. * @returns `true`/`false`, a {@link CanResult}, or a promise of either. */ can: (params: CanParams) => boolean | CanResult | Promise; } /** * Provide an {@link AccessControl} strategy to the React tree. Components such * as `` and the `useCan` hook read it from context. * * @example * ```tsx * * * * ``` */ export declare function AccessControlProvider({ control, children }: AccessControlProviderProps): JSX.Element; export declare interface AccessControlProviderProps { /** The access-control strategy made available to descendants. */ control: AccessControl; children: ReactNode; } /** * Accessible accordion. Each item collapses/expands its content. Single-mode by * default — pass `multiple` to allow more than one item open at a time. Can be * controlled via `value` + `onChange`, or uncontrolled via `defaultValue`. */ export declare function Accordion({ items, multiple, value, defaultValue, onChange, className, }: AccordionProps): JSX.Element; export declare interface AccordionItem { /** Stable identifier. */ id: string; title: ReactNode; children: ReactNode; disabled?: boolean; } export declare interface AccordionProps { items: AccordionItem[]; /** When `true` multiple items can be open simultaneously. Default `false`. */ multiple?: boolean; /** Controlled open ids. */ value?: string[]; /** Uncontrolled default open ids. */ defaultValue?: string[]; onChange?: (openIds: string[]) => void; className?: string; } /** * A conversation with a model: role-based turns, Markdown answers, a reasoning * block, a streaming caret, per-turn actions and a composer that turns into a stop * button while a turn is generating. * * This is the shape ChatGPT, Claude and DeepSeek converged on, and it is a different * component from {@link Chat}, not a variant of it. A human thread is addressed by * author and cares about delivery state; a model transcript is addressed by role, * has no delivery state at all, and needs three things a human thread never does — * partial output, reasoning separate from the answer, and re-asking. * * Presentational and controlled, like the rest of the SDK: it takes a list and emits * intent (`onSend`, `onStop`, `onRegenerate`, `onEditSubmit`, `onFeedback`). The * transport stays with the app, because "how do I stream from my backend" has a * different answer per provider — the SDK's `createEventStream` covers SSE, `fetch` * with a `ReadableStream` covers the rest, and either way the app owns the * `AbortController` it hands to `onStop`. * * @example * ask(text)} * onStop={() => controller.current?.abort()} * onRegenerate={(turn) => reask(turn)} * onFeedback={(turn, vote) => track("answer_rated", { id: turn.id, vote })} * suggestions={["Resuma o último relatório", "Quais pedidos atrasaram?"]} * /> */ export declare function AIChat({ messages, onSend, onStop, onRegenerate, onEditSubmit, onFeedback, onRetry, pending, suggestions, renderAvatar, renderContent, votes, header, emptyState, showSystem, defaultReasoningOpen, showLineNumbers, locale, placeholder, composerActions, composerRef, composerFooter, composerDisabled, maxRows, onSendError, className, ...rest }: AIChatProps): JSX.Element; /** A file carried by a turn — an upload on the way in, a document on the way out. */ export declare interface AIChatAttachment { /** Stable identity. Used as the React key. */ id: string; /** Name shown in the chip. */ name: string; /** Size in bytes. Formatted for display when given. */ size?: number; /** Image URL. When set the attachment renders as a thumbnail instead of a chip. */ url?: string; /** MIME type. Used as the chip's secondary label when there is no size. */ mimeType?: string; } /** * The prompt field of a conversation with a model: a textarea that grows with its * content, sends on `Enter`, keeps `Shift+Enter` for a newline, and turns into a * stop button while a turn is streaming. * * Uncontrolled on purpose. A draft changes on every keystroke, and lifting that into * app state re-renders the whole transcript per character — with a streaming answer * above, that is the one place where "controlled by default" costs something * visible. Apps that need the draft (a persisted composer, a slash-command menu) * read it from `onChange` or drive it through the ref. * * @example * ask(text)} * onStop={() => controller.abort()} * footer={Claude Opus 5 · pode errar} * /> */ export declare const AIChatComposer: ForwardRefExoticComponent>; /** Imperative handle, so a thread can focus, read or refill the field. */ export declare interface AIChatComposerHandle { focus: () => void; /** Replace the draft — used to put a prompt back in the field. */ setValue: (text: string) => void; /** * The current draft. * * The counterpart `setValue` needs to be usable for anything **additive**. The * field is uncontrolled, so without this the only way to append to a draft — a * dictated phrase, a picked slash-command, a pasted citation — is to shadow the * whole value in app state through `onChange` and hope the two never drift. */ getValue: () => string; } export declare interface AIChatComposerProps extends Omit, OverriddenDomProps_9> { /** Called with the trimmed prompt. The field clears only when this does not throw. */ onSend: (text: string) => void | Promise; /** * Abort the turn in flight. * * When given together with `generating`, the send button becomes a stop button * and `Escape` aborts too. */ onStop?: () => void; /** A turn is being generated. Replaces send with stop and refuses to send. */ generating?: boolean; /** Locale for the placeholder and the button labels. Default `"pt-BR"`. */ locale?: "pt-BR" | "en"; /** Left of the send button — an attach control, a model picker, a tool toggle. */ actions?: ReactNode; /** Under the field — a token count, the model name, a disclaimer. */ footer?: ReactNode; /** Largest height the field grows to, in lines. Default 8. */ maxRows?: number; /** * Called when `onSend` rejects. The draft is kept either way. * * Without it the rejection is swallowed after the draft is preserved: re-throwing * out of a DOM event handler surfaces as an unhandled promise rejection, which is * console noise for the developer and nothing the user can act on. The visible * signal is the prompt still sitting in the field; wire this to a toast to say why. */ onError?: (error: unknown) => void; } /** One turn of a conversation with a model. */ export declare interface AIChatMessage { /** Stable identity. Used as the React key and by every callback. */ id: string; role: AIChatRole; /** * The text of the turn. * * An assistant turn is rendered as Markdown; a user turn is rendered as plain * text with newlines preserved. That asymmetry is deliberate: a model emits * Markdown by contract, while a person typing `2 * 3 * 4` did not mean to open * an emphasis span. */ content: string; /** * Reasoning the model exposed before answering — extended thinking, a * chain-of-thought trace. * * Rendered in its own collapsible block above the answer, so a long trace never * pushes the answer off screen. */ reasoning?: string; /** * The turn is still arriving. * * Shows the caret, marks the block `aria-busy`, and hides the action row — * copying or rating half an answer is never what somebody meant to do. */ streaming?: boolean; /** Generation failed. Shown under whatever streamed, with the retry control. */ error?: string; /** Epoch milliseconds. */ createdAt?: number; /** Model that produced the turn. Shown in the meta row of an assistant turn. */ model?: string; attachments?: readonly AIChatAttachment[]; /** Anything the app wants to carry through to its own renderers. */ data?: Record; } export declare interface AIChatProps extends Omit, OverriddenDomProps_8> { /** The transcript, **oldest first**. Never reordered by the component. */ messages: readonly AIChatMessage[]; /** Renders the composer when given. Receives the trimmed prompt. */ onSend?: (text: string) => void | Promise; /** Abort the turn in flight. Shows the stop button while generating. */ onStop?: () => void; /** Ask again for the newest assistant turn. */ onRegenerate?: (message: AIChatMessage) => void; /** Re-submit an edited user turn. The app decides what to drop after it. */ onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise; /** Rating on an assistant turn. */ onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void; /** Retry a turn that carries an `error`. */ onRetry?: (message: AIChatMessage) => void; /** * The request is out and nothing has arrived yet. * * Distinct from a turn with `streaming: true`: apps that only push a message * once the first token lands need somewhere to say "we asked", and without it the * screen is frozen for however long the model takes to start. */ pending?: boolean; /** Prompts offered on an empty transcript. Clicking one sends it. */ suggestions?: readonly string[]; /** Avatar for a turn — an ``, an ``, a logo. */ renderAvatar?: (message: AIChatMessage) => ReactNode; /** Render a body yourself — a tool-call card, a chart, a citation list. */ renderContent?: (message: AIChatMessage) => ReactNode; /** Ratings to show as pressed, by message id. Omit to keep them local. */ votes?: Readonly>; /** Rendered above the transcript, inside the panel. */ header?: ReactNode; /** Shown when there are no turns and no suggestions. */ emptyState?: ReactNode; /** Show `"system"` turns. Default `false`. */ showSystem?: boolean; /** Reasoning blocks start expanded. Default `false`. */ defaultReasoningOpen?: boolean; /** Show line numbers in fenced code. Default `false`. */ showLineNumbers?: boolean; /** Locale for labels. Default `"pt-BR"`. */ locale?: "pt-BR" | "en"; /** Placeholder for the composer. */ placeholder?: string; /** Extra controls inside the composer, before the send button. */ composerActions?: ReactNode; /** * Reach the composer imperatively — `focus()`, `getValue()`, `setValue()`. * * What makes dictation (or a slash-command menu, or "edit and resend") possible * without this component knowing anything about them: pair it with * `composerActions` and the button you put in the composer can write into the * field. Speech recognition is **not** wired in here on purpose — it would make * every consumer of `AIChat` pay for an API that streams audio to a third party. */ composerRef?: Ref; /** Under the composer field — token count, model name, a disclaimer. */ composerFooter?: ReactNode; /** Disable the composer — no credits, conversation archived, offline. */ composerDisabled?: boolean; /** Largest height the composer grows to, in lines. Default 8. */ maxRows?: number; /** * Called when `onSend` **or** `onEditSubmit` rejects. The draft stays in the field * either way. */ onSendError?: (error: unknown) => void; } /** Who produced a turn. */ export declare type AIChatRole = "user" | "assistant" | "system"; /** Labels the conversation needs, per locale. */ export declare interface AIChatStrings { thread: string; empty: string; emptyHint: string; placeholder: string; send: string; stop: string; regenerate: string; copy: string; copied: string; edit: string; save: string; cancel: string; editing: string; good: string; bad: string; reasoning: string; thinking: string; generating: string; done: string; stopped: string; you: string; assistant: string; system: string; retry: string; jumpToLatest: string; attachment: string; turnActions: string; } /** Locale strings for the conversation. */ export declare function aiChatStrings(locale: "pt-BR" | "en"): AIChatStrings; /** * One turn of a conversation with a model. * * Exported for apps that build their own transcript layout (a split view, a diff of * two answers) but still want the SDK's turn: Markdown body, reasoning block, * attachments, streaming caret, error state and the action row. * * @example * reask(m)} /> */ export declare function AIChatTurn({ message, locale, canRegenerate, onRegenerate, onFeedback, onEditSubmit, onEditError, onRetry, renderAvatar, renderContent, vote, defaultReasoningOpen, showLineNumbers, }: AIChatTurnProps): JSX.Element; export declare interface AIChatTurnProps { /** The turn to render. */ message: AIChatMessage; /** Locale for the labels. Default `"pt-BR"`. */ locale?: "pt-BR" | "en"; /** * Offer the regenerate control. * * Only the newest assistant turn should get it — re-asking an older one throws * away every turn after it, which is a different operation and needs its own * confirmation. */ canRegenerate?: boolean; onRegenerate?: (message: AIChatMessage) => void; onFeedback?: (message: AIChatMessage, vote: AIChatVote) => void; /** Enables the edit control on a user turn. Receives the edited prompt. */ onEditSubmit?: (message: AIChatMessage, text: string) => void | Promise; /** * Called when `onEditSubmit` rejects. The editor stays open with the draft either * way. * * Without it the rejection is swallowed after the draft is preserved: re-throwing * out of a click handler surfaces as an unhandled promise rejection, which is * console noise for the developer and nothing the user can act on. */ onEditError?: (error: unknown) => void; /** Enables the retry control on a turn that carries an `error`. */ onRetry?: (message: AIChatMessage) => void; renderAvatar?: (message: AIChatMessage) => ReactNode; /** Render the body yourself — a tool-call card, a chart, a citation list. */ renderContent?: (message: AIChatMessage) => ReactNode; /** * Rating to show as pressed. * * Pass it to keep votes in app state (persisted across a reload); leave it out * and the pressed state is kept locally, which is enough for a fire-and-forget * `onFeedback`. */ vote?: AIChatVote; /** Reasoning blocks start expanded. Default `false`. */ defaultReasoningOpen?: boolean; /** Show line numbers in fenced code. Default `false`. */ showLineNumbers?: boolean; } /** Rating an app can collect on an assistant turn. */ export declare type AIChatVote = "up" | "down"; /** * Inline alert / notice with tone (info/success/warning/danger) and appearance * (soft/solid/outline). Accepts optional `icon`, `title`, `description` and * a dismiss button via `onClose`. */ export declare function Alert({ variant, appearance, title, description, icon, onClose, closeLabel, className, children, ...props }: AlertProps): JSX.Element; export declare type AlertAppearance = "soft" | "solid" | "outline"; export declare interface AlertProps extends Omit, "title"> { variant?: AlertVariant; /** Visual style: soft (default tinted bg), solid (filled), outline (bordered). */ appearance?: AlertAppearance; title?: ReactNode; description?: ReactNode; icon?: ReactNode; /** Show a close button and invoke this when clicked. */ onClose?: () => void; /** Custom close button label for screen readers. */ closeLabel?: string; } export declare type AlertVariant = "neutral" | "info" | "success" | "warning" | "danger"; /** Every symbology in the spec, used to validate what a detector reports back. */ export declare const ALL_BARCODE_FORMATS: readonly BarcodeFormat[]; /** * Announce a message to screen readers, from anywhere — a hook, an event handler, * a plain function outside React. * * ## Why the same string announces twice * * Screen readers announce a live region when its **content changes**. Writing the * same text again is not a change, so "Item removido" twice in a row is read once — * the classic reason these announcers are quietly broken. Instead of mutating text, * every call replaces the region's child with a **new element**. The DOM mutation is * real even when the string is identical, so the second announcement happens, and * the reader hears the exact message with no padding characters bolted on. * * @param message - Text to read out. Empty strings are ignored. * @param politeness - `"polite"` (default) or `"assertive"`. * * @example * announce(`${count} pedidos encontrados`); * announce("Falha ao salvar", "assertive"); */ export declare function announce(message: string, politeness?: AnnouncePoliteness): void; /** * How urgently a screen reader should interrupt. * * `"polite"` waits for a pause in what is being read. `"assertive"` cuts in * immediately, which is right for an error the user must act on and wrong for * everything else — an assertive announcement can truncate the sentence the user * was in the middle of. */ export declare type AnnouncePoliteness = "polite" | "assertive"; /** * Any React component, whatever props it declares. * * Props sit in a *parameter* position, so a bound over them is contravariant: * `ComponentType` reads as "a component that accepts every possible * props object", and only a component that declares no props at all satisfies * it. A page typed `({ mode }: Props) => …` is rejected, and so is one whose * props are entirely optional. `any` is the only bound that admits every * component — which is why React itself declares `lazy` and * `LazyExoticComponent` as `ComponentType`. `ComponentType` is not * an escape: it fails React's own bound through * `ComponentClass.getDerivedStateFromProps`, where the props land back in a * covariant position. * * This relaxes the *bound* only. `T` is still inferred as the concrete * component, so the rendered element keeps checking its props and `preload()` * still resolves to the concrete module. */ declare type AnyComponent = ComponentType; declare type AnyEventTarget = EventTarget | { current: EventTarget | null; } | null | undefined; /** * Any page component a route can point at, whatever props it declares. * * Same contravariance as `lazyWithRetry`, which this field is handed to: * `ComponentType` means "accepts every possible props object", so a * route module default-exporting `({ id }: Props) => …` would not assign here — * and neither would one whose props are all optional. React declares its own * `lazy` as `ComponentType` for the same reason. */ declare type AnyRouteComponent = ComponentType; /** * Translation key the {@link useDescribeApiError} hook looks up. * * A catalog that does not define it falls back to * {@link DEFAULT_API_ERROR_STRINGS}, because `t` returns the key itself when the * lookup misses and printing `tempest.error.offline` at the user would be worse * than printing pt-BR at them. */ export declare const API_ERROR_OFFLINE_KEY = "tempest.error.offline"; /** * Translation key for the validation sentence, looked up the same way as * {@link API_ERROR_OFFLINE_KEY}. */ export declare const API_ERROR_VALIDATION_KEY = "tempest.error.validation"; export declare interface ApiClient { request(path: string, options?: RequestOptions): Promise; get(path: string, options?: RequestOptions): Promise; post(path: string, options?: RequestOptions): Promise; put(path: string, options?: RequestOptions): Promise; patch(path: string, options?: RequestOptions): Promise; delete(path: string, options?: RequestOptions): Promise; /** * Download a binary body through the full client pipeline. * * Same base URL, `getToken` header, 401 refresh-and-replay, `onUnauthorized`, * logging, timeout and retry policy as {@link ApiClient.request} — only the * decoding differs. Without it every download left the client: a hand-rolled * `fetch` that re-declares the `Authorization` header and re-implements the * error handling, and that has no refresh, which is the client's reason to * exist. * * @param path - Path joined onto the client's base URL. * @param options - The same options `request` takes; `method` defaults to GET. * @returns The response body as a `Blob`. * @throws TempestApiError On any non-2xx response, carrying the status. */ blob(path: string, options?: RequestOptions): Promise; /** * Download a binary body as an `ArrayBuffer`, for the callers a `Blob` does * not serve — a decoder, a hash, a typed array fed to WebGL or to * `onnxruntime-web`. * * @param path - Path joined onto the client's base URL. * @param options - The same options `request` takes; `method` defaults to GET. * @returns The response body as an `ArrayBuffer`. * @throws TempestApiError On any non-2xx response, carrying the status. */ arrayBuffer(path: string, options?: RequestOptions): Promise; upload(path: string, formData: FormData, method?: "POST" | "PUT" | "PATCH", options?: Omit): Promise; } export declare interface ApiClientConfig { /** * Base URL for every request. Required. * * May carry a path (`https://api.example.com/api`) — it is kept, and a * request for `"/auth/login"` lands on `/api/auth/login`. May also be * relative (`"/api"`), which resolves against the current origin and is the * shape to use behind a dev-server or reverse proxy. */ baseURL: string; /** * Path segment every request is nested under, such as `"/api"` — the * `root_path` a Tempest FastAPI service is usually mounted on. * * The alternative to writing it into `baseURL`, and the better one when the * base comes from an environment variable that other things also use (an * SSE endpoint, a media host): the variable stays the bare origin and only * the client carries the prefix. * * Applied at most once — a path that already opens with the prefix is left * alone, so call sites can migrate one at a time. */ prefix?: string; /** Returns the current bearer token (or null/undefined). Called per request. */ getToken?: () => string | null | undefined; /** * Origins besides `baseURL`'s that may receive the `getToken` credential. * * Since 0.66.0 the bearer token is scoped to the origin of `baseURL`. A * request to any other origin still goes out — it just goes without the * header. That matters because a path may be an absolute URL, in which case * it overrides `baseURL` completely, so the destination of a credentialed * request could be decided by a value the app read off the network: a * pagination link, a `download_url`, a `Location`. * * List the second host here when it genuinely needs the API's token — a CDN * behind the same identity, a sibling service. An origin compares as an * origin (`"https://cdn.acme.com"`), so path and trailing slash are * ignored. * * A relative `baseURL` needs nothing here: a relative target resolves * against the document and cannot cross an origin. */ trustedOrigins?: readonly string[]; /** * Per-request correlation id sent as the `X-Request-ID` header, matching the * Tempest FastAPI SDK `RequestIDMiddleware`. Defaults to a generated id. * Return an empty string to disable the header. */ requestId?: () => string; /** * Called when a request ends up unauthorized — a 401 with no `refresh` * configured, a `refresh()` that threw, or a retry that came back 401 again * after a refresh that resolved. Use it to end the session. */ onUnauthorized?: (response: Response) => void | Promise; /** * Where the client reports each request it finished. Off when absent — the * client writes to no console of its own. * * One entry per attempt (so a refresh replay and every retry show up), at * `debug` under 400 and at `warn` from 400 up, carrying `requestId`, * `status` and the elapsed `ms`. Firing `onUnauthorized` gets its own `warn`, * which is what a session dying mid-session looks like in the log. * * Deliberately **not** a `debug: boolean`: the level lives in the logger you * pass, so `createLogger({ level })` decides what survives, the sink decides * where it goes (console in dev, Sentry in production, an array in a test), * and one namespace per client keeps two clients apart. * * Never logs a body, a header, or the query string — a bearer token in * `Authorization`, a password in a login payload and an `access_token` query * param would all end up in whatever the sink writes to. What is logged is * the method, the path as the call site wrote it, and the numbers. */ logger?: ApiClientLogger; /** * Optional refresh hook. When provided and the original request returns 401, * the client awaits `refresh()` then retries the request once. */ refresh?: () => Promise; /** * Retry failed requests with exponential backoff. Off by default, so the * client keeps failing fast unless you opt in. * * `true` uses the built-in policy, which is deliberately conservative: only * idempotent methods (`GET`, `HEAD`, `OPTIONS`) and only failures a retry * can plausibly fix — a network error, `408`, `425`, `429`, or any `5xx`. A * `POST` is never retried on its own, so nothing gets duplicated. * * Pass {@link RetryOptions} to tune it. Supplying your own `shouldRetry` * replaces the built-in policy entirely, method check included — that is the * escape hatch for retrying a write you made idempotent with * {@link generateIdempotencyKey}. * * Retries wrap the whole request, refresh included, and each attempt carries * its own `X-Request-ID`. A `4xx` outside the three listed above is never * retried: repeating an identical request cannot fix a bad payload or a * permission the caller does not have. */ retry?: boolean | RetryOptions; /** Whether to send cookies on cross-origin requests (default: false). */ /** * Milliseconds before a request is abandoned. Default `15_000`. `null` turns * it off. * * There was no timeout at all before, and the failure it leaves open is not * an error: a TCP connection that dies without a FIN never answers, so the * browser can hold the request for minutes or forever. In an offline-first * SDK that is the wrong place to have no floor — the eternal spinner lands * exactly on the bad network this package exists to survive. * * A timeout surfaces as an {@link ApiError} with `status: 0`, the same shape * the client already uses for "never reached the server", so the built-in * retry policy replays it without a special case. */ timeout?: number | null; /** * Milliseconds before a `FormData` request is abandoned. Default `300_000`. * `null` turns it off. * * A binary upload is not a slow request, it is a different kind of request. A * single timeout forces a choice between one short enough to protect a normal * call and one long enough to finish a file, and 15 seconds cuts an upload * mid-body — which the server then has to interpret as a truncated payload. * * Detected from the body being `FormData`, the same test that already decides * the `Content-Type`. Override per request with `options.timeout` when a * particular call does not fit either default. */ uploadTimeout?: number | null; withCredentials?: boolean; /** Default headers merged into every request. */ headers?: Record; /** Optional fetch implementation (defaults to globalThis.fetch). */ fetcher?: typeof fetch; } /** * The slice of {@link Logger} the client writes to: `debug` for a request that * came back under 400, `warn` for everything else. Structural, so the SDK * logger fits without adapting and so does any object with those two methods. */ export declare type ApiClientLogger = Pick; export declare interface ApiError { /** HTTP status code (0 for network failures). */ status: number; /** Human-readable message — the backend envelope's `detail` (or `message`). */ detail: string; /** * Programmatic error code from the Tempest FastAPI SDK envelope (`code`), * e.g. `"EMAIL_TAKEN"`. Lets callers branch without parsing `detail`. */ code?: string; /** * Correlation id echoed from the backend envelope's `details.request_id` * (or the `X-Request-ID` response header). Pair it with `createLogger`. */ requestId?: string; /** * Seconds to wait before retrying, parsed from the `Retry-After` response * header (commonly on `429`/`503`). Honored by {@link retry}. */ retryAfter?: number; /** * Field-level messages from a validation response, keyed by the field path * the backend named (`"email"`, `"items.0.price"`, `"phone"`). * * Present whenever the body named a field, through either shape a Tempest * stack sends: * * - FastAPI's validation list — `detail: [{ loc, msg, type }]`, one entry per * field, each keyed by its `loc` path. * - The singular keys a `tempest-fastapi-sdk` backend uses once it owns the * handler — `detail.field`, then top-level `field`, then `details.field`, in * that order. There the single value is the same sentence as `detail`. * * This is the shape a form needs: `detail` is one line for a log or a * developer, and pulling the fields back out of it means parsing prose. The * first message wins when a field appears twice, because a field shows one * error at a time. */ fields?: Record; /** The raw parsed error body, when available. */ body?: unknown; } /** The fixed sentences {@link describeApiError} may need. */ export declare interface ApiErrorStrings { /** Shown when the request never reached the server. */ offline: string; /** * Shown when the backend rejected the payload field by field. * * The per-field messages are on `error.fields`, to be attached to the inputs * themselves; this sentence is what the toast says. * * It does not apply to the one rejection that named a single field with a * finished sentence — there the server's own `detail` is shown instead, since * it is the same string `fields` carries. Pass `useDetail: false` to force * this sentence in that case too. */ validation: string; } /** * Mobile-first top app bar for PWAs — leading (back / brand) + title + * trailing actions, sticky with safe-area padding out of the box. * * Consumers customise via tokens (`--tempest-*`) and slots; the SDK ships the * layout, sticky/safe-area behaviour, and accessible back button so apps don't * hand-roll one per screen. For a desktop three-slot nav use [[Navbar]]. * * @example * // Detail screen with back + a trailing action * navigate(-1)} * actions={} onClick={openSettings} />} * /> * * @example * // Home screen — brand left, centered title disabled * } actions={} /> */ export declare function AppBar({ title, leading, showBack, onBack, backLabel, backIcon, brand, actions, centered, sticky, tone, bordered, safeArea, className, ...props }: AppBarProps): JSX.Element; export declare interface AppBarProps extends Omit, "title"> { /** Page title — string or any node. Rendered as the bar's `

`. */ title?: ReactNode; /** * Replace the whole left slot. When set, the auto back button and `brand` * are ignored — you own the leading content. */ leading?: ReactNode; /** Show a back button in the left slot. Ignored when `leading` is set. */ showBack?: boolean; /** * Back-button handler. Defaults to `window.history.back()`. With a router, * pass `onBack={() => navigate(-1)}`. */ onBack?: () => void; /** Accessible label for the back button. Default `"Go back"`. */ backLabel?: string; /** Custom back icon. Default a left arrow. */ backIcon?: ReactNode; /** Brand / logo node shown in the left slot (after the back button). */ brand?: ReactNode; /** Right slot — action buttons / menu. One node or many. */ actions?: ReactNode; /** Center the title (three-column grid). Default `false` (left-aligned). */ centered?: boolean; /** * Stick to the top of the scroll container. Default `true`. * * The page has to leave the body out of the scrolling: `body { overflow-x: * hidden }` makes the body a scroll container and the bar scrolls away with * the content. Use `overflow-x: clip` on `html` and `body` instead — in * development the bar says so in the console when it detects it. */ sticky?: boolean; /** Visual tone. Default `"surface"`. */ tone?: AppBarTone; /** Thin bottom border. Default `true`. */ bordered?: boolean; /** Add `env(safe-area-inset-top)` padding (iOS notch / PWA). Default `true`. */ safeArea?: boolean; } export declare type AppBarTone = "surface" | "primary" | "transparent"; /** * Run a filter set over an in-memory list. * * Closes the loop `FilterBar` opens: the bar produces `Filter[]`, this applies * them. Filters combine with `AND`, matching the flat model the bar builds, and * an incomplete filter is skipped rather than treated as a match of nothing — a * half-filled form should not empty the table underneath it. * * Comparison follows the row's type, not the filter's: numbers compare * numerically, dates compare by day, and everything else compares as text with * `numeric: true` so `"item 2"` lands before `"item 10"`. * * A few behaviours differ from the SQL the server-side twin produces, and the * difference is deliberate rather than accidental: * * - `ne` matches rows whose value is absent. In SQL, `column <> 'x'` is `NULL` * for a `NULL` column and the row drops out. Here "is not paid" shows the rows * with no status at all, which is what the chip claims. * - `empty` matches `NULL`, blank text and empty lists; `__isnull` on the server * only matches `NULL`. A column that stores `""` instead of `NULL` is where * the two disagree. * * @example * const visible = applyFilters(orders, filters); * * Each filter's values are normalised once, before the scan, rather than inside * the per-row predicate: the arity, the `between` ordering and the value array * depend only on the filter, so deriving them per row multiplied that work by * the row count. `empty`/`notEmpty` ignore their values entirely, which is why * the prepared shape does not need to distinguish them. * * @param items - The full list. * @param filters - Applied filters; incomplete ones are ignored. * @returns A new array with the rows that satisfy every complete filter. */ export declare function applyFilters(items: readonly T[], filters: readonly Filter[]): T[]; /** * Apply a {@link KanbanMove} to a column list, returning new arrays. * * Exported because every consumer needs the same reducer. * * `toIndex` is the position of the card that was dropped **onto**, read from the * board as it looked before the move. Inserting at that same index *after* removing * the dragged card lands where the user aimed in both directions — moving down, the * removal shifts the target up by one and the insertion lands after it; moving up, * nothing shifted. Compensating for the shift (the "obvious" `toIndex - 1`) is what * turns a one-step move down into a no-op. * * @param columns - Current board. * @param move - The move reported by `onMove`. * @returns A new column list, or the same reference when the move cannot apply. */ export declare function applyKanbanMove(columns: KanbanColumn[], move: KanbanMove): KanbanColumn[]; /** * Install a generated theme (or raw CSS) into the document. * * Safe to call outside a browser: with no `document` it is a no-op returning a * no-op disposer, so app bootstrap code does not need a `typeof window` guard. * * @param theme - A {@link GeneratedTheme} from `createTheme`, or CSS text. * @param options - Style element id and mount target. * @returns A function that removes the injected `