@marketrix.ai/widget 4.0.123 → 4.0.125

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.
@@ -16,7 +16,7 @@ interface FlexProps extends SurfaceProps {
16
16
  direction?: 'row' | 'column';
17
17
  children?: ReactNode;
18
18
  }
19
- export type StackProps = Omit<FlexProps, 'direction'>;
19
+ type StackProps = Omit<FlexProps, 'direction'>;
20
20
  export declare const Flex: import("react").ForwardRefExoticComponent<FlexProps & import("react").RefAttributes<HTMLElement>>;
21
21
  export declare const Stack: import("react").ForwardRefExoticComponent<StackProps & import("react").RefAttributes<HTMLElement>>;
22
22
  export {};
@@ -16,6 +16,12 @@
16
16
  * resolving `display` itself because `resolveLayoutStyle` is applied ahead of it. `resolveLayoutStyle`
17
17
  * is handed the whole `props` (it reads only layout keys), while the DOM spread goes through
18
18
  * `stripLayoutProps` — a layout token left on the props bag reaches the element as an unknown attribute.
19
+ *
20
+ * `forwardRef<HTMLElement, SurfaceProps>` is fixed, not generic per `as`: `forwardRef` cannot be made
21
+ * generic across call sites without its own internal cast, so a caller holding a `RefObject` typed to
22
+ * its specific element (`HTMLDivElement`, …) casts at the call site instead — `RefObject.current` is
23
+ * mutable and therefore invariant, so no subtyping relationship lets a `RefObject<HTMLDivElement>` stand
24
+ * in for `Ref<HTMLElement>` without one. `WidgetFab.tsx`, `MessageList.tsx` (×2) are the three sites.
19
25
  */
20
26
  import { type ElementType } from 'react';
21
27
  import { type ShadowToken } from '../../design-system/component-tokens';
@@ -13,12 +13,15 @@
13
13
  * unify both). Pointer state is tracked in a ref; movement under DRAG_THRESHOLD_PX stays a click, beyond
14
14
  * it the wrapper is translated on a rAF loop with velocity sampled into `velocityHistoryRef` so a flick
15
15
  * lands where it was heading — `projectFlickVelocity` (module-level, pure) turns that sample history into
16
- * a projected pixel delta. On release
16
+ * a projected pixel delta: zero with fewer than two samples, else the average px/ms over the sampled span
17
+ * projected forward in px/s. On release
17
18
  * `getNearestCornerByTranslation` picks the corner, the wrapper animates there for SNAP_DURATION_MS via
18
19
  * `left`/`top` transitions, and `commitPositionAfterAnimation` calls `onPositionCommit` on
19
20
  * `transitionend` (with a timeout fallback, since a hidden tab fires no transition events) — the
20
21
  * committed corner is the one being animated TO, so two snaps in flight cannot commit the abandoned one
21
- * (`abandonSnapRef`). `suppressUntilRef` stamps a time after which a click may open the widget again,
22
+ * (`abandonSnapRef`), which the unmount effect also calls so a snap animating when the widget is torn
23
+ * down does not leave its `transitionend` listener and fallback timer running past the component's life.
24
+ * `suppressUntilRef` stamps a time after which a click may open the widget again,
22
25
  * so the pointer-up that ends a drag is not read as a tap. The wrapper is measured with a
23
26
  * ResizeObserver in a layout effect so the pixel position is right on the first paint; preview mode
24
27
  * disables everything. Exported so `WidgetFab.test.tsx`'s `renderHook` case can drive it directly.
@@ -21,7 +21,7 @@
21
21
  * more work is coming; a message with no parts at all falls back to a bare `Surface`.
22
22
  */
23
23
  import React from 'react';
24
- import type { ChatMessage } from '../../types';
24
+ import { type ChatMessage } from '../../types';
25
25
  interface MessageItemProps {
26
26
  message: ChatMessage;
27
27
  isLastMessage: boolean;
@@ -17,6 +17,12 @@
17
17
  * which carries no task id. Every failure on the tool and stop paths reaches `uiActions.setError` as well
18
18
  * as the console: an undelivered `tool/response` leaves the agent waiting on a reply that never comes, so
19
19
  * the run stalls with nothing on screen unless the visitor is told, and `do` mode may still be clicking.
20
+ *
21
+ * `event.type === 'task/status' && isTerminalTaskStatus(event.status)` reads `event.status` on every
22
+ * branch, but only the `task/status` variant of `WidgetEvent` carries a `status` field at all — on any
23
+ * other event it is `undefined`, which `isTerminalTaskStatus` (an `in` check against the status map)
24
+ * always reports as non-terminal. The `&&` can never observably differ from an `||` here; it stays `&&`
25
+ * because that is what a reader expects a type-narrowing guard to say.
20
26
  */
21
27
  import React from 'react';
22
28
  import type { ChatMessage, InstructionType } from '../types';
@@ -1,16 +1,14 @@
1
1
  /**
2
2
  * `UIStateProvider` / `useUIStateContext` — the widget's view state: open/closed, active view, current
3
3
  * mode (tell/show/do) and the error banner. Actions are stable (memoised once) so consumers can depend
4
- * on them without re-rendering; `applyState` merges a partial for restore-from-storage.
4
+ * on them without re-rendering; `applyState` merges a partial for restore-from-storage. `UIState` is a
5
+ * `Pick` of the public `WidgetState` (`../types`) rather than its own duplicate shape, and stays
6
+ * unexported — it never needs to be named outside this file, and `applyState`'s declaration-file
7
+ * emission resolves through the already-exported `WidgetState` instead.
5
8
  */
6
9
  import React from 'react';
7
- import type { InstructionType, WidgetView } from '../types';
8
- export interface UIState {
9
- isOpen: boolean;
10
- activeView: WidgetView;
11
- currentMode: InstructionType;
12
- error?: string | undefined;
13
- }
10
+ import type { InstructionType, WidgetState, WidgetView } from '../types';
11
+ type UIState = Pick<WidgetState, 'isOpen' | 'activeView' | 'currentMode' | 'error'>;
14
12
  interface UIStateActions {
15
13
  setActiveView: (view: WidgetView) => void;
16
14
  toggleWidget: () => void;
@@ -24,7 +24,7 @@
24
24
  * closed is noise, which is what a duplicated `chat/response` looks like.
25
25
  */
26
26
  import type { WidgetEvent } from '../sdk';
27
- import { type ChatMessage, type InstructionType } from '../types';
27
+ import type { ChatMessage, InstructionType } from '../types';
28
28
  type TaskPhase = 'idle' | 'running' | 'stopped';
29
29
  export interface TaskState {
30
30
  phase: TaskPhase;
@@ -42,7 +42,7 @@ export interface SseEffect {
42
42
  mode: InstructionType;
43
43
  explanation: string;
44
44
  }
45
- interface ReduceResult {
45
+ export interface ReduceResult {
46
46
  state: SseState;
47
47
  effects: SseEffect[];
48
48
  }
@@ -6,6 +6,11 @@
6
6
  * no two tokens are synonyms. Per-tenant values are NOT here: `semantic-tokens.ts` owns everything derived
7
7
  * from widget settings, and this file is only what no tenant can change.
8
8
  *
9
+ * Every `notificationToneStyles` colour is fixed (no tenant setting touches a toast), so each pair is
10
+ * measured once here against the WCAG threshold it actually needs — `closeColor` is a UI icon (3:1
11
+ * against `background`), `titleColor`/`bodyColor` are text (4.5:1) — and a test pins every ratio so a
12
+ * future palette edit can't silently regress below it.
13
+ *
9
14
  * `LAYER_TOKENS` is the z-index ladder (screen-edge glow < panel < dialog < toast), based just above the
10
15
  * 2^31-ish ceiling most host pages use so the widget sits over everything without the values overflowing a
11
16
  * 32-bit int. `showHighlight`/`showPopup` are a separate, much higher pair near the int32 ceiling: Show
@@ -10,6 +10,14 @@
10
10
  * settings object against those defaults and derives the muted/faint/hover/contrast variants;
11
11
  * `semanticTokensToCssCustomProperties`, the token → `--var` map.
12
12
  *
13
+ * `color.ring` is NOT the raw accent — a customer's `widget_accent_color` has no contrast guarantee
14
+ * against whatever it sits next to (WCAG 2.4.11/1.4.11 need the focus indicator ≥3:1 against the colours
15
+ * adjacent to it on both sides), so it is `getContrastingColor(widget_background_color)`, the same
16
+ * black/white pick `primaryForeground`/`secondaryForeground` use. `index.css` pairs it with
17
+ * `--ring-offset` (the background colour itself) as a two-tone ring: an inset halo matching the surface,
18
+ * then the ring outside it, so both sides of the ring measure against the one colour it is guaranteed to
19
+ * clear against, whatever the outlined control's own colour is.
20
+ *
13
21
  * Radius and both durations are fixed rather than per-tenant: every widget row in production holds these
14
22
  * values and no surface writes them. Settings are filtered for explicit `undefined` before merging —
15
23
  * a plain spread would let an `undefined` key shadow its default instead of falling back to it. The
@@ -31,6 +39,7 @@ type SemanticTokens = {
31
39
  secondaryForeground: string;
32
40
  secondaryBg: string;
33
41
  secondaryHover: string;
42
+ ring: string;
34
43
  };
35
44
  radius: string;
36
45
  motion: {
@@ -0,0 +1 @@
1
+ export declare function useLatest<T>(value: T): React.RefObject<T>;
@@ -29,6 +29,11 @@ export declare const useWidget: () => {
29
29
  closeWidget: () => void;
30
30
  setMode: (mode: import("..").InstructionType) => void;
31
31
  setError: (error: string | undefined) => void;
32
- applyState: (payload: Partial<import("../context/UIStateContext").UIState>) => void;
32
+ applyState: (payload: Partial<{
33
+ error?: string | undefined | undefined;
34
+ currentMode: import("..").InstructionType;
35
+ isOpen: boolean;
36
+ activeView: import("../types").WidgetView;
37
+ }>) => void;
33
38
  };
34
39
  };
@@ -3,7 +3,8 @@
3
3
  * dashboard wrapper and the script-tag auto-init hook. `README.md` is the customer-facing surface these
4
4
  * exports make up, named and default alike. `initWidget` is the guarded, coalescing production entry over
5
5
  * `initWidgetInternal`; `mount` builds the shadow-DOM container; `unmountWidget` tears down stream,
6
- * recorder, screen share and tree;
6
+ * recorder, screen share, any in-flight show-mode overlay (which owns host-page listeners and DOM
7
+ * nodes outside the shadow root, so it does not go with `active.instance.unmount()`) and tree;
7
8
  * `updateMarketrixConfig` re-mounts with client settings merged in, on whichever path the current mount
8
9
  * came from; `mountWidget` dispatches on shape — `settings` → preview (no network), credentials → live.
9
10
  *
@@ -38,7 +39,7 @@ export { getCurrentConfig };
38
39
  export declare const MarketrixWidgetPreview: React.FC<MarketrixWidgetPreviewProps>;
39
40
  export declare const mountWidget: (config: AddWidgetConfig) => Promise<void>;
40
41
  export type { InstructionType } from './sdk';
41
- export type { AddWidgetConfig, ChatMessage, ClientOwnedConfig, MarketrixConfig, MarketrixWidgetPreviewProps, WidgetState, } from './types';
42
+ export type { AddWidgetConfig, ChatMessage, ClientOwnedConfig, MarketrixConfig, MarketrixWidgetPreviewProps, WidgetSettingsData, WidgetState, } from './types';
42
43
  declare const _default: {
43
44
  MarketrixWidgetPreview: React.FC<MarketrixWidgetPreviewProps>;
44
45
  mountWidget: (config: AddWidgetConfig) => Promise<void>;
@@ -17,6 +17,10 @@
17
17
  * same failure. `httpUrl` admits only http(s) — `extract` feeds the model page-controlled hrefs, so a raw
18
18
  * target would let `javascript:` run in the HOST origin; its bare catch is the same verdict as a rejected
19
19
  * protocol, an unparseable string being exactly a value that is not a URL.
20
+ *
21
+ * `scrollToText`'s walker only ever yields a Text node from inside `document.body`'s tree, so
22
+ * `node.parentElement` is never null there — the optional-chained read is for TypeScript, not a runtime
23
+ * branch this loop can take.
20
24
  */
21
25
  import type { InstructionType } from '../types';
22
26
  interface TextData {
@@ -7,9 +7,9 @@
7
7
  * That is why `StreamClient.ready` must resolve BEFORE the POST: registration is what gives the reply
8
8
  * somewhere to land.
9
9
  *
10
- * The `widget_question` activity-log row is no longer filed from here — `StreamClient` sends `user_id`
10
+ * `ChatService` never files the `widget_question` activity-log row itself: `StreamClient` sends `user_id`
11
11
  * once, at `widgetStream` registration, and the api derives the row from `chat_id`'s bound application on
12
- * every Tell/Show/Do command, so a per-message credential re-send is gone.
12
+ * every Tell/Show/Do command, so no per-message credential re-send is needed here.
13
13
  */
14
14
  import type { InstructionType } from '../sdk';
15
15
  export declare function chatPost(message: string, mode: InstructionType, requestId: string): Promise<void>;
@@ -20,6 +20,12 @@
20
20
  * `ACCENT_COLOR`/`TEXT_COLOR` are raw literals rather than design-system tokens: the highlight and popup
21
21
  * mount to `document.body` on the HOST page, outside the shadow root, so `index.css`'s `:host`-scoped
22
22
  * CSS custom properties never reach them.
23
+ *
24
+ * `trackElement`'s `!this.currentElement || !this.currentHighlight` and the click handler's
25
+ * `!this.currentElement || !this.resolvePromise` cannot observe a mixed state: `showToolAction` sets
26
+ * every one of these fields together and `cleanup` clears every one of them together, so within a
27
+ * single stage they are always all-null or all-set. The guards stay as defensive redundancy against a
28
+ * future edit that breaks that pairing, not because either can independently be null today.
23
29
  */
24
30
  interface ShowModeOptions {
25
31
  element: HTMLElement;
@@ -15,18 +15,23 @@
15
15
  * another's.
16
16
  *
17
17
  * The chat snapshot is `{messages, currentMode, isOpen}` — chat_id, config and timestamp are deliberately
18
- * excluded. Reading revives `timestamp` to a `Date` and backfills a text part for messages stored before
19
- * `parts` existed; writing drops `videoStream` (unserializable, dead on reload), rewriting it as "Screen
20
- * sharing ended".
18
+ * excluded. `StoredMessage` is the ONE place `content` still exists as a field: a live `ChatMessage` has
19
+ * none (every reader derives `messageText(parts)` instead), but a transcript written before `parts`
20
+ * existed has only `content` on disk, so `readChatSnapshot` backfills a text part from it and
21
+ * `writeChatSnapshot` derives `content` back from `parts` on the way out, keeping the persisted shape
22
+ * readable by an older widget version without carrying the redundant field in memory. Reading also
23
+ * revives `timestamp` to a `Date`; writing drops `videoStream` (unserializable, dead on reload),
24
+ * rewriting it as "Screen sharing ended".
21
25
  *
22
26
  * `sanitizeStoredContext` is hand-rolled, not zod, per the package-level rule that a schema imported as a
23
27
  * VALUE anywhere reachable from `src/index.tsx` pulls zod's whole runtime into the bundle. Each field
24
28
  * falls back to `DEFAULT_CONTEXT`'s value individually, so a partially-corrupt payload keeps the fields
25
29
  * that DID parse rather than discarding the whole context.
26
30
  */
27
- import type { ChatMessage, InstructionType, MarketrixConfig, ValidWidgetConfig } from '../types';
31
+ import { type ChatMessage, type InstructionType, type MarketrixConfig, type ValidWidgetConfig } from '../types';
28
32
  type StoredMessage = Omit<ChatMessage, 'videoStream' | 'timestamp'> & {
29
33
  timestamp: string;
34
+ content: string;
30
35
  };
31
36
  export interface ChatSnapshot {
32
37
  messages: ChatMessage[];
@@ -17,6 +17,11 @@
17
17
  * read by a visitor on a customer's page, so they name the state and the way out rather than the counter, and
18
18
  * `giveUp` needs no console line of its own for the same reason. A dial or stream that will be retried warns;
19
19
  * only the rejected credential is an error, being the one failure nothing here recovers from.
20
+ *
21
+ * `send` never turns its own POST failure into a user-facing `onError` — the raw failure (network cause, a
22
+ * 4xx/5xx body) goes only to `logWarn`, since every caller already reports its own human sentence on the
23
+ * same catch (`ChatContext`'s message/tool-response/stop paths); notifying here too would have shown the
24
+ * caller's visitor a second, raw-text toast racing the first.
20
25
  */
21
26
  import { type WidgetCommand, type WidgetEvent } from '../sdk';
22
27
  export declare class StreamGaveUpError extends Error {
@@ -15,8 +15,10 @@
15
15
  * `ChatMessage.pendingContent` queues a message behind an open screen-access request, sent once it
16
16
  * resolves. A `streaming` `MessagePart` accumulates `chat/delta` fragments until the final
17
17
  * `chat/response` replaces it. `taskStatus`/`MessagePart.status` are presentational only, not the wire
18
- * vocabulary (`task/status.status`). `messageText` joins text parts and IS the text; `content` is kept
19
- * equal to it by every writer.
18
+ * vocabulary (`task/status.status`). `messageText` joins text parts and IS the text `ChatMessage` has
19
+ * no `content` field of its own; a render site calls `messageText(msg.parts)` directly. The one
20
+ * exception is `StorageService`'s persisted `StoredMessage`, which keeps its own `content` string to
21
+ * migrate a transcript stored before `parts` existed — see that file's header.
20
22
  */
21
23
  import type { InstructionType, WidgetSettingsData } from '../sdk';
22
24
  import type { WidgetRenderedSettings } from '../utils/validation';
@@ -37,7 +39,6 @@ export type MarketrixConfig = Partial<WidgetRenderedSettings> & ClientOwnedConfi
37
39
  export type ValidWidgetConfig = MarketrixConfig & Required<Pick<MarketrixConfig, keyof WidgetRenderedSettings | 'isPreviewMode'>>;
38
40
  export interface ChatMessage {
39
41
  id: string;
40
- content: string;
41
42
  sender: 'user' | 'agent';
42
43
  timestamp: Date;
43
44
  mode?: InstructionType | undefined;
@@ -3,10 +3,14 @@
3
3
  * `lastIndexWhere` (also used by `useScreenShare`), and `addProgressLine` / `markProgressLineComplete` /
4
4
  * `markProgressLineFailed`, which append or settle the open progress part for one `browserToolName` via
5
5
  * `openLineFor` and the shared `patchPart` copy-on-write. `createMessage` and its per-sender constructors are
6
- * the ONLY way a `ChatMessage` is built: ids are `<prefix>-<uuid>` since two messages minted in one millisecond
7
- * used to collide, and empty content yields no `text` part, the screen-share bubble rendering from `videoStream`
6
+ * the ONLY way a `ChatMessage` is built: ids are `<prefix>-<uuid>` since two messages minted in the same millisecond
7
+ * would otherwise collide, and empty content yields no `text` part, the screen-share bubble rendering from `videoStream`
8
8
  * alone and a placeholder having nothing to say yet. `SCREEN_ACCESS_PROMPT` is the one wording of the
9
9
  * screen-access ask, the transcript card and the toolbar dialog being two renderings of one question.
10
+ * `CHAT_FAILURE_TEXT` is the one human sentence for "the assistant could not process that turn" —
11
+ * `ChatContext`'s message-post catch and `sseReducer`'s `chat/error` case both settle a bubble with it
12
+ * rather than the raw POST failure or the raw server `error` string, which may carry request/response
13
+ * internals a visitor must never see.
10
14
  *
11
15
  * `findMessageForProgress` picks the agent reply a `tool/call` or progress event renders into, by ranked
12
16
  * predicates: the first rank matching anything wins, within a rank the newest message, and no match at all is a
@@ -37,7 +41,8 @@ export declare const createUserMessage: (content: string, mode?: InstructionType
37
41
  export declare const createAgentMessage: (content: string) => ChatMessage;
38
42
  export declare const createSystemMessage: (content: string, idPrefix: string) => ChatMessage;
39
43
  export declare const SCREEN_ACCESS_PROMPT = "Can I take a look at your screen?";
44
+ export declare const CHAT_FAILURE_TEXT = "I'm sorry, I encountered an error processing your request. Please try again.";
40
45
  export declare const createScreenAccessRequestMessage: (mode: InstructionType | undefined, pendingContent?: string) => ChatMessage;
41
- export declare const createScreenshareMessage: (stream: MediaStream, mode?: InstructionType) => ChatMessage;
46
+ export declare const createScreenshareMessage: (stream: MediaStream) => ChatMessage;
42
47
  export declare const createPlaceholderMessage: (mode: InstructionType) => ChatMessage;
43
48
  export {};
@@ -6,12 +6,17 @@
6
6
  * `rgb()`/`rgba()` string into channels, and returns null for anything else. Only those two notations are
7
7
  * read: a named colour, `hsl()` or a `var(--…)` custom property is unreadable here by design, and a channel
8
8
  * above 255 is refused rather than clamped, so a malformed setting never silently becomes a valid colour.
9
- * `getContrastingColor` picks the black or white foreground for a background from that colour's WCAG
10
- * relative luminance — sRGB gamma-decoded per channel, weighted .2126/.7152/.0722, split at 0.5. An
11
- * unreadable background falls back to BLACK, never white: tenant surfaces skew light, so black stays
12
- * legible where white would vanish. `addOpacity` re-emits a colour as `rgba()` at the given alpha and
13
- * passes an unreadable one through UNCHANGED it stays a CSS value the browser can still resolve, where
14
- * an `rgba(NaN, )` would render nothing.
9
+ * `contrastRatio` is the one home for the WCAG formula relative luminance is sRGB gamma-decoded per
10
+ * channel, weighted .2126/.7152/.0722, and the ratio is `(lighter + 0.05) / (darker + 0.05)`.
11
+ * `getContrastingColor` picks whichever of black or white scores higher against it: this is the single
12
+ * place a tenant colour without its own paired foreground setting (a button's accent, a status pill)
13
+ * gets one synthesized, and comparing the two ratios directly (rather than a `luminance > 0.5` split,
14
+ * whose crossover sits at ≈0.179, not 0.5) means every synthesized foreground clears the AA 4.5:1
15
+ * threshold by construction — the higher of the two ratios is never below ~4.6:1. An unreadable
16
+ * background falls back to BLACK, never white: tenant surfaces skew light, so black stays legible where
17
+ * white would vanish. `addOpacity` re-emits a colour as `rgba()` at the given alpha and passes an
18
+ * unreadable one through UNCHANGED — it stays a CSS value the browser can still resolve, where an
19
+ * `rgba(NaN, …)` would render nothing.
15
20
  *
16
21
  * `backgroundGradient` is the one home for `widget_background_color` as a `backgroundImage`: the setting may
17
22
  * already be a gradient, which is legal only as `backgroundImage`, so a flat colour is emitted as a same-stop
@@ -24,6 +29,7 @@ type Rgb = {
24
29
  b: number;
25
30
  };
26
31
  export declare function toRgb(color: string): Rgb | null;
32
+ export declare function contrastRatio(a: string, b: string): number | null;
27
33
  export declare function getContrastingColor(color: string): string;
28
34
  export declare function addOpacity(color: string, opacity: number): string;
29
35
  export declare function backgroundGradient(color: string): string;
@@ -14,8 +14,8 @@
14
14
  * key — and `RENDER_CONSTANT_NAMES` mirrors api's `WIDGET_RENDER_CONSTANTS` by hand under the same check.
15
15
  *
16
16
  * `parseWidgetSettings` PICKS as well as validates: a widget's settings arrive carrying the render constants and the
17
- * result is spread into the widget config, so unknown keys passing through would leak them where zod used to drop
18
- * them. Render constants stay guarded, since a legacy bundle's stored value must keep passing, but are dropped
17
+ * result is spread into the widget config, so unknown keys passing through would leak them where a zod-parsed
18
+ * object would drop them. Render constants stay guarded, since a legacy bundle's stored value must keep passing, but are dropped
19
19
  * from the picked result, since nothing renders them.
20
20
  */
21
21
  import type { WidgetSettingsData } from '../sdk';