fix(FN-7692): recover blank mobile terminal when xterm screen collapses to 0x0

The mobile terminal rendered blank even though the WebSocket was Connected and
the shell prompt had already streamed in. Root cause (reproduced live): on the
mobile fullscreen layout xterm's CharSizeService can measure a 0-width character
cell, so FitAddon.fit() proposes 0 columns/rows and .xterm-screen (plus the WebGL
canvas) collapses to 0x0 — prompt bytes arrive and are written into xterm's row
DOM but paint into a zero-size box. Renderer-independent and mobile-layout-
specific; not fixed by resize/font-size re-fits because prior guards only validate
the container width and font load, never the resulting measured screen/cell width.

Add guardAgainstCollapsedTerminalScreen (app/utils/terminalPreferences.ts) and arm
it from both terminal surfaces (TerminalModal + SessionTerminal) right after their
initial fit. While the container has a width but .xterm-screen does not, it forces
a genuine DOM-strategy remeasure (forceTerminalFontRemeasure) + fit, re-driven by a
ResizeObserver until the screen has a real width. It waits (does not give up) while
the container is not yet measurable, is bounded so it never spins, and is disposed
on every re-init/close path. Recurrence of FN-7620/FN-7686.

- Add isTerminalScreenCollapsed + guardAgainstCollapsedTerminalScreen with tests
- Wire + dispose the guard across all xterm (re)init/close paths in both surfaces
- Add changeset (patch) and a docs/solutions write-up

Note: reproduced via mobile emulation (393px, iPhone UA, forced touch), not a
physical device; the guard is the structural fix — confirm on a real device.

Fusion-Task-Id: FN-7692

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-08 13:22:10 -07:00
parent 461a4a2711
commit b7b1b71cae
6 changed files with 405 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix the terminal rendering blank on mobile even though the shell prompt already loaded.
category: fix
dev: Adds `guardAgainstCollapsedTerminalScreen` (app/utils/terminalPreferences.ts), wired into TerminalModal and SessionTerminal. On the mobile fullscreen layout xterm could measure a 0-width character cell, so FitAddon.fit() proposed 0 columns/rows and `.xterm-screen` (plus the WebGL canvas) collapsed to 0x0 — prompt bytes arrived but painted into a zero-size box. The guard watches for that collapsed state (container has width, `.xterm-screen` does not) and forces a genuine remeasure+fit, re-driven by a ResizeObserver until the screen has a real width. Renderer-agnostic and bounded. Recurrence of FN-7620/FN-7686.

View File

@@ -0,0 +1,74 @@
---
title: "Mobile terminal renders blank because xterm's screen collapses to 0x0"
date: 2026-07-08
category: ui-bugs
module: packages/dashboard/app/utils/terminalPreferences.ts
problem_type: rendering
component: embedded_terminal
applies_when: "The standalone (TerminalModal) or task-session (SessionTerminal) terminal shows nothing on mobile even though the WebSocket says Connected and the prompt has arrived."
symptoms:
- "Mobile terminal is blank for many seconds (or indefinitely) after opening"
- "Header shows Connected; no prompt text is visible"
- ".xterm-screen has style width:0px;height:0px while the .xterm container has a real size"
root_cause: xterm_charsize_service_measured_zero_width_cell_on_mobile_layout
resolution_type: measurement_validity_guard
severity: high
related_components:
- packages/dashboard/app/components/TerminalModal.tsx
- packages/dashboard/app/components/SessionTerminal.tsx
tags: [terminal, xterm, mobile, fit-addon, char-size, blank-screen, fn-7620, fn-7686, fn-7692]
---
# Mobile terminal renders blank: xterm screen collapses to 0x0
## Problem
On the mobile fullscreen terminal layout the terminal opens, the WebSocket connects, the shell prompt
data streams in and is written into xterm's row DOM — but the terminal is visibly blank. It is NOT a
network, PTY, or login-shell latency problem (measured live: `POST /terminal/sessions` 3ms, WS first
prompt bytes ~215ms, desktop renders <1s).
## Root cause
`terminal.open()` + `FitAddon.fit()` can run while xterm's `CharSizeService` resolves the character
cell to **0 width**. `FitAddon.proposeDimensions()` then yields 0 columns/rows and `.xterm-screen`
(and, on desktop, the WebGL renderer canvas) collapses to **0x0**. The prompt is painted into a
zero-size box, so it is invisible. Reproduced live via mobile emulation: the `.xterm` container
measured 385x758 while `.xterm-screen` stayed `width:0px;height:0px` and xterm's own
`.xterm-char-measure-element` read 0 — while an identical monospace span in the same container measured
~295px. So the correct measurement is achievable and xterm is holding a stale 0.
The bug is:
- **Renderer-independent** — reproduced on both the DOM renderer (touch/mobile) and the WebGL renderer.
- **Mobile-layout-specific** — desktop widths render immediately.
- **Not self-healing** — a `window.resize` and a font-size change both re-fit but do not recover, because
every prior guard validates the *container* width (`clientWidth > 0`) and font load
(`waitForTerminalFontMetrics`), never the *resulting* measured screen/cell width, and a single 0
measurement is cached and never re-validated.
## Fix
`guardAgainstCollapsedTerminalScreen(container, terminal, fit, fontFamily)` in
`app/utils/terminalPreferences.ts`, armed by both TerminalModal and SessionTerminal right after their
initial fit:
- Detects the collapsed state: container has a width but `.xterm-screen` does not
(`isTerminalScreenCollapsed`).
- Forces a genuine `CharSizeService` remeasure via `withDomBasedTerminalCharacterMeasurement(() =>
forceTerminalFontRemeasure(...))` followed by `fit()`.
- Re-driven by a `ResizeObserver` so it re-attempts exactly when the mobile modal/keyboard geometry
finally settles (it waits, rather than giving up, while the container itself is not yet measurable).
- Bounded by `maxAttempts` so it never spins, and a no-op once the screen has a non-zero width.
- Tied to the xterm instance lifetime — disposed on every re-init/close/reinitialize path.
## Verification
- Unit tests in `app/utils/__tests__/terminalPreferences.test.ts`
(describe "guardAgainstCollapsedTerminalScreen"): classifies collapse only when the container is
measurable; forces remeasure+fit and stops once the screen reports a width; waits while the container
is not yet measurable and recovers on the ResizeObserver relayout; stays bounded when the screen never
recovers; no-op when already healthy.
- Physical-device note: the root cause and DOM signature were reproduced in the automation browser via
mobile emulation (393px, iPhone UA, forced touch), not a physical iPhone. Confirm on a real device
when possible.

View File

@@ -11,11 +11,13 @@ import { isMobileViewport, MOBILE_MEDIA_QUERY } from "../hooks/useViewportMode";
import {
TERMINAL_PREFERENCES_KEY,
forceTerminalFontRemeasure,
guardAgainstCollapsedTerminalScreen,
readTerminalPreferences,
resolveTerminalFontFamily,
resolveTerminalGlyphFontFamily,
waitForTerminalFontMetrics,
withDomBasedTerminalCharacterMeasurement,
type TerminalScreenGuardHandle,
} from "../utils/terminalPreferences";
/**
@@ -192,6 +194,8 @@ export function SessionTerminal({
const containerRef = useRef<HTMLDivElement | null>(null);
const xtermRef = useRef<XTerm | null>(null);
const fitAddonRef = useRef<ITerminalAddon | null>(null);
/** FN-7692: active collapsed-screen guard for the current xterm; disposed on teardown. */
const screenGuardRef = useRef<TerminalScreenGuardHandle | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const [postureTooltipOpen, setPostureTooltipOpen] = useState(false);
@@ -508,6 +512,28 @@ export function SessionTerminal({
/* container not measurable yet */
}
/*
FNXC:Terminal 2026-07-08-16:20:
FN-7692: arm the collapsed-screen guard on this attach surface too. The task-session terminal
opens inside the same mobile fullscreen layout, so its initial open()+fit() can likewise measure a
0-width cell and collapse .xterm-screen to 0x0 — prompt bytes stream in but paint into a zero-size
box (blank terminal). Force a genuine remeasure+fit (re-driven by a ResizeObserver as the layout
settles) until the screen has a real width. Tied to this xterm instance; disposed on teardown.
*/
screenGuardRef.current?.dispose();
if (containerRef.current) {
screenGuardRef.current = guardAgainstCollapsedTerminalScreen(
containerRef.current,
term,
() => {
if (disposed || xtermRef.current !== term) return;
(fitAddon as unknown as { fit: () => void }).fit();
sendResize(term.cols, term.rows);
},
resolvedFontFamily,
);
}
void (async () => {
const fontMetricsSettled = await waitForTerminalFontMetrics(
terminalPreferences.fontSize,
@@ -684,6 +710,8 @@ export function SessionTerminal({
}
wsRef.current = null;
}
screenGuardRef.current?.dispose(); // FN-7692: stop the collapsed-screen watchdog on teardown
screenGuardRef.current = null;
const term = xtermRef.current;
if (term) {
try {

View File

@@ -42,6 +42,7 @@ import {
TERMINAL_FONT_FAMILY_PRESETS,
clampTerminalFontSize,
forceTerminalFontRemeasure,
guardAgainstCollapsedTerminalScreen,
readTerminalPreferences,
resolveTerminalFontFamily,
resolveTerminalGlyphFontFamily,
@@ -50,6 +51,7 @@ import {
writeTerminalPreferences,
type TerminalPreferences,
type TerminalRenderer,
type TerminalScreenGuardHandle,
} from "../utils/terminalPreferences";
import "@xterm/xterm/css/xterm.css";
@@ -606,6 +608,12 @@ export function TerminalModal({ isOpen, onClose, initialCommand, initialCommandG
const initializedRendererRef = useRef<TerminalRenderer>(terminalPreferences.renderer);
/** Tracks a pending requestAnimationFrame for deferred xterm re-fit. */
const pendingFitRef = useRef<number | null>(null);
/**
* FN-7692: active collapsed-screen guard for the current xterm instance. Disposed and re-armed on
* every (re)init so the mobile 0x0-screen watchdog never outlives its terminal. See
* `guardAgainstCollapsedTerminalScreen`.
*/
const screenGuardRef = useRef<TerminalScreenGuardHandle | null>(null);
/*
FNXC:Terminal 2026-06-22-09:00:
Docked-resize, floating-drag, and floating-resize each attach pointer listeners and schedule a rAF for the duration of a drag. If the modal closes or the component unmounts mid-drag, those listeners + the pending frame would leak. Track the active drag teardown here and run it from the close/unmount effect.
@@ -1388,6 +1396,8 @@ export function TerminalModal({ isOpen, onClose, initialCommand, initialCommandG
if (xtermRef.current && (xtermInitializedRef.current !== currentSessionId || projectChanged)) {
xtermRef.current.dispose();
xtermRef.current = null;
screenGuardRef.current?.dispose(); // FN-7692: guard is tied to the disposed xterm instance
screenGuardRef.current = null;
fitAddonRef.current = null;
xtermInitializedRef.current = false;
if (windowResizeListenerRef.current) {
@@ -1558,6 +1568,28 @@ export function TerminalModal({ isOpen, onClose, initialCommand, initialCommandG
xtermInitializedRef.current = currentSessionId;
void remeasureAfterTerminalFontLoad(currentSessionId, terminal, fitAddon);
/*
FNXC:Terminal 2026-07-08-16:20:
FN-7692: arm the collapsed-screen guard for this instance. On the mobile fullscreen layout the
initial open()+fit() above can measure a 0-width cell and collapse .xterm-screen to 0x0, leaving
the terminal blank even though the prompt already streamed in. The guard forces a genuine
remeasure+fit (re-driven by a ResizeObserver as the mobile modal/keyboard geometry settles) until
the screen has a real width. Dispose any prior guard first so it is tied to this xterm's lifetime.
*/
screenGuardRef.current?.dispose();
if (terminalRef.current) {
screenGuardRef.current = guardAgainstCollapsedTerminalScreen(
terminalRef.current,
terminal,
() => {
if (xtermInitializedRef.current !== currentSessionId) return;
fitAddon.fit();
resizeRef.current?.(terminal.cols, terminal.rows);
},
fontFamilyAtInit,
);
}
// If the virtual keyboard opened while xterm was still in async
// initialization for this tab, force a post-init fit so this new
// session uses the already-constrained mobile modal height.
@@ -1703,6 +1735,8 @@ export function TerminalModal({ isOpen, onClose, initialCommand, initialCommandG
xtermRef.current.dispose();
xtermRef.current = null;
}
screenGuardRef.current?.dispose(); // FN-7692: stop the collapsed-screen watchdog on close
screenGuardRef.current = null;
fitAddonRef.current = null;
xtermInitializedRef.current = false;
if (windowResizeListenerRef.current) {
@@ -2089,6 +2123,8 @@ export function TerminalModal({ isOpen, onClose, initialCommand, initialCommandG
xtermRef.current.dispose();
xtermRef.current = null;
}
screenGuardRef.current?.dispose(); // FN-7692: re-armed by the re-running init effect
screenGuardRef.current = null;
fitAddonRef.current = null;
xtermInitializedRef.current = false;
if (windowResizeListenerRef.current) {
@@ -2154,6 +2190,8 @@ export function TerminalModal({ isOpen, onClose, initialCommand, initialCommandG
xtermRef.current.dispose();
xtermRef.current = null;
}
screenGuardRef.current?.dispose(); // FN-7692: drop the watchdog before a manual re-init
screenGuardRef.current = null;
fitAddonRef.current = null;
xtermInitializedRef.current = false;
if (windowResizeListenerRef.current) {

View File

@@ -1,10 +1,12 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
DEFAULT_TERMINAL_PREFERENCES,
LEGACY_TERMINAL_FONT_SIZE_KEY,
TERMINAL_PREFERENCES_KEY,
XTERM_FONT_FAMILY,
forceTerminalFontRemeasure,
guardAgainstCollapsedTerminalScreen,
isTerminalScreenCollapsed,
readTerminalPreferences,
waitForTerminalFontMetrics,
writeTerminalPreferences,
@@ -169,4 +171,138 @@ describe("terminalPreferences", () => {
expect(terminal.options.fontFamily).toBe("system-mono, monospace");
});
});
/*
FN-7692 regression: the mobile terminal rendered blank because xterm's `.xterm-screen` collapsed to
0x0 (character cell measured 0) even though the prompt data had already arrived. These tests pin the
guard that detects the collapsed state (container has width, screen does not), forces a genuine
remeasure + fit until the screen has a real width, waits (does not give up) while the container is not
yet measurable, and stays bounded when the screen never recovers. Symptom under test = a screen stuck
at width 0; assertion it is gone = a forced remeasure+fit runs until the screen reports a width.
*/
describe("guardAgainstCollapsedTerminalScreen", () => {
let rafQueue: Array<() => void>;
let resizeObservers: Array<{ cb: () => void; disconnected: boolean }>;
beforeEach(() => {
rafQueue = [];
resizeObservers = [];
vi.stubGlobal("requestAnimationFrame", (cb: () => void) => {
rafQueue.push(cb);
return rafQueue.length;
});
vi.stubGlobal("cancelAnimationFrame", () => {});
vi.stubGlobal(
"ResizeObserver",
class {
constructor(cb: () => void) {
resizeObservers.push({ cb, disconnected: false });
}
observe(): void {}
disconnect(): void {
const entry = resizeObservers[resizeObservers.length - 1];
if (entry) entry.disconnected = true;
}
},
);
});
afterEach(() => {
vi.unstubAllGlobals();
});
function flushRaf(max = 200): void {
let n = 0;
while (rafQueue.length && n++ < max) {
const cb = rafQueue.shift();
cb?.();
}
}
function makeContainer(init: { containerWidth: number; screenWidth: number }) {
const state = { ...init };
const screen = {
getBoundingClientRect: () => ({ width: state.screenWidth }) as DOMRect,
};
const container = {
getBoundingClientRect: () => ({ width: state.containerWidth }) as DOMRect,
querySelector: (sel: string) => (sel === ".xterm-screen" ? screen : null),
} as unknown as HTMLElement;
return { container, state };
}
it("classifies collapse only when the container is measurable but the screen is not", () => {
expect(isTerminalScreenCollapsed(385, 0)).toBe(true);
expect(isTerminalScreenCollapsed(385, 300)).toBe(false);
expect(isTerminalScreenCollapsed(0, 0)).toBe(false);
});
it("forces a remeasure + fit and stops once the screen reports a width", () => {
const { container, state } = makeContainer({ containerWidth: 385, screenWidth: 0 });
const terminal = { options: { fontFamily: XTERM_FONT_FAMILY } };
let fitCalls = 0;
// A genuine remeasure+fit recovers the collapsed screen on the first attempt.
const fit = () => {
fitCalls += 1;
state.screenWidth = 300;
};
guardAgainstCollapsedTerminalScreen(container, terminal, fit, XTERM_FONT_FAMILY);
expect(fitCalls).toBe(1);
expect(state.screenWidth).toBe(300);
// Recovered synchronously on the initial kick — nothing left scheduled.
expect(rafQueue.length).toBe(0);
});
it("does not act while the container itself is not yet measurable, then recovers on relayout", () => {
const { container, state } = makeContainer({ containerWidth: 0, screenWidth: 0 });
const terminal = { options: { fontFamily: XTERM_FONT_FAMILY } };
let fitCalls = 0;
const fit = () => {
fitCalls += 1;
state.screenWidth = 300;
};
guardAgainstCollapsedTerminalScreen(container, terminal, fit, XTERM_FONT_FAMILY);
// Container has no width yet (mobile modal not settled): must wait, not give up.
expect(fitCalls).toBe(0);
expect(resizeObservers[0]?.disconnected).toBe(false);
// Container becomes measurable; the ResizeObserver re-drives the guard.
state.containerWidth = 385;
resizeObservers[0].cb();
expect(fitCalls).toBe(1);
expect(state.screenWidth).toBe(300);
});
it("stays bounded (never spins) when the screen never recovers", () => {
const { container } = makeContainer({ containerWidth: 385, screenWidth: 0 });
const terminal = { options: { fontFamily: XTERM_FONT_FAMILY } };
let fitCalls = 0;
const fit = () => {
fitCalls += 1; // never sets a width — the collapse persists
};
guardAgainstCollapsedTerminalScreen(container, terminal, fit, XTERM_FONT_FAMILY, {
maxAttempts: 3,
});
flushRaf();
expect(fitCalls).toBe(3);
expect(rafQueue.length).toBe(0);
});
it("is a no-op when the screen already has a width at mount", () => {
const { container } = makeContainer({ containerWidth: 385, screenWidth: 300 });
const terminal = { options: { fontFamily: XTERM_FONT_FAMILY } };
let fitCalls = 0;
guardAgainstCollapsedTerminalScreen(container, terminal, () => (fitCalls += 1), XTERM_FONT_FAMILY);
expect(fitCalls).toBe(0);
expect(rafQueue.length).toBe(0);
});
});
});

View File

@@ -294,6 +294,127 @@ export function withDomBasedTerminalCharacterMeasurement<T>(fn: () => T): T {
}
}
/*
FNXC:Terminal 2026-07-08-16:20:
FN-7692 (mobile blank-terminal, recurrence of FN-7620/FN-7686): Both terminal surfaces (standalone
TerminalModal and task-session SessionTerminal) can run `terminal.open()` + `FitAddon.fit()` on the
mobile fullscreen layout while xterm's `CharSizeService` resolves the character cell to 0 width. When
the cell width is 0, `FitAddon.proposeDimensions()` yields 0 columns/rows and `.xterm-screen` (plus the
WebGL renderer canvas) collapses to 0x0 — the shell prompt DOES arrive over the WebSocket and IS written
into xterm's row DOM, but it is painted into a zero-size box, so the terminal looks permanently blank
("view renders but no output for many seconds" on mobile). Reproduced live: `.xterm` container measured
385x758 while `.xterm-screen` stayed `width:0px;height:0px` and xterm's own `.xterm-char-measure-element`
read 0 even though an identical monospace span in the same container measured ~295px — i.e. the correct
measurement is achievable and xterm is holding a stale 0. The bug is renderer-independent (repro'd on both
DOM and WebGL renderers) and mobile-layout-specific (desktop widths render immediately).
Every prior guard validates the CONTAINER width (`clientWidth > 0`) and font load
(`waitForTerminalFontMetrics`), but none validate that the RESULTING measured screen/cell width is
non-zero, and a single 0 measurement is cached and never re-validated. `guardAgainstCollapsedTerminalScreen`
closes that gap: while the container has a width but `.xterm-screen` does not, it forces a genuine
CharSizeService remeasure (DOM-strategy `forceTerminalFontRemeasure`) followed by a `fit()`, re-driven by a
`ResizeObserver` so it re-attempts exactly when the mobile modal/keyboard geometry finally settles. It is
bounded (`maxAttempts`) so it never spins, and a no-op once the screen has a non-zero width.
*/
/** True when the terminal has a laid-out container but its rendered screen collapsed to zero width. */
export function isTerminalScreenCollapsed(containerWidth: number, screenWidth: number): boolean {
return containerWidth > 0 && screenWidth <= 0;
}
/** Handle returned by {@link guardAgainstCollapsedTerminalScreen}; call `dispose()` on teardown. */
export interface TerminalScreenGuardHandle {
dispose: () => void;
}
/**
* Watch a mounted xterm terminal for the collapsed-screen state (container has width, `.xterm-screen`
* does not) and force a genuine remeasure + fit until the screen is no longer collapsed or `maxAttempts`
* is exhausted. See the FN-7692 note above for the root cause. Renderer-agnostic and bounded.
*
* @param container The `.xterm` host element (or its wrapper) that also contains `.xterm-screen`.
* @param terminal The xterm Terminal (only its mutable `options.fontFamily` is used, to force remeasure).
* @param fit Runs `FitAddon.fit()` (callers also notify the server of the new cols/rows here).
* @param fontFamily The already-resolved xterm font family to land back on after the remeasure sentinel.
*/
export function guardAgainstCollapsedTerminalScreen(
container: HTMLElement,
terminal: { options: { fontFamily?: string } },
fit: () => void,
fontFamily: string,
options?: { maxAttempts?: number },
): TerminalScreenGuardHandle {
const maxAttempts = options?.maxAttempts ?? 40;
let attempts = 0;
let disposed = false;
let rafId: number | null = null;
let observer: ResizeObserver | null = null;
const containerWidth = (): number => container.getBoundingClientRect().width;
const screenWidth = (): number => {
const screen = container.querySelector<HTMLElement>(".xterm-screen");
return screen ? screen.getBoundingClientRect().width : 0;
};
const dispose = (): void => {
disposed = true;
if (rafId !== null && typeof cancelAnimationFrame === "function") {
cancelAnimationFrame(rafId);
}
rafId = null;
observer?.disconnect();
observer = null;
};
const attemptRecovery = (): void => {
if (disposed) return;
// Healthy — the screen has a real width; stop watching.
if (screenWidth() > 0) {
dispose();
return;
}
// Not yet measurable — the container itself has no width (mobile modal/keyboard geometry has
// not settled). Do NOT give up: wait for the ResizeObserver to re-drive when it becomes
// measurable. Only a screen that stays collapsed WITH a measurable container is the bug.
if (!isTerminalScreenCollapsed(containerWidth(), screenWidth())) {
return;
}
if (attempts >= maxAttempts) {
dispose();
return;
}
attempts += 1;
try {
// Force xterm's DOM-based CharSizeService strategy for the remeasure so the cell width is
// measured through the same pipeline the DomRenderer paints through (see FN-7603 note above).
withDomBasedTerminalCharacterMeasurement(() =>
forceTerminalFontRemeasure(terminal, fontFamily),
);
fit();
} catch {
// Ignore transient fit/measure errors during mobile viewport/keyboard transitions.
}
if (!isTerminalScreenCollapsed(containerWidth(), screenWidth())) {
dispose();
return;
}
if (typeof requestAnimationFrame === "function") {
rafId = requestAnimationFrame(attemptRecovery);
}
};
// Re-drive on container relayout: on mobile the width only becomes measurable after the fullscreen
// modal and keyboard CSS vars settle, which is exactly when a fresh remeasure finally succeeds.
if (typeof ResizeObserver !== "undefined") {
observer = new ResizeObserver(() => attemptRecovery());
observer.observe(container);
}
// Kick once immediately in case the container is already measurable at mount.
attemptRecovery();
return { dispose };
}
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}