Beamline

Docs · Updated

Connect your data

Part of the documentation that comes with Beamline's complete system (docs/DATA-INTEGRATION.md in the kit). Your coding agent can do all of this for you through Beamline's MCP server: connect it.

Components never fetch. They take plain data through props and report what people do through callbacks; your app owns the requests, the persistence and the decisions. This page shows the shapes and where your backend plugs in.

The rules every component follows

  • Data in, events out. rows, bids, candles, items come in as props; onValueChange, onSelect, onSubmit, onApprove go out. Stateful props have both forms: value + onValueChange (you hold the state) or defaultValue (the component holds it).
  • Your handler decides the outcome. Actions that stand for an external operation (send, approve, place an order, delete) accept a handler that may return a promise. The component shows pending while it runs, done when it resolves, and failed with your error when it rejects. It never shows success on a timer.
  • Missing is not zero. Pass null for a value you do not have: it renders as unmeasured (hatched), never as 0.
  • Numbers stay raw. Pass numbers, and a format(value) function where the default formatting does not fit. Prices keep their precision.

Market data (trading)

Types and formatters live in @beamline/ui/lib/market:

import type { Candle, Level, Trade, MarketFeed, FeedRequest, UseMarketFeed, Ticker, Position } from "@beamline/ui/lib/market";

Single components take the shapes directly: <OrderBook bids={bids} asks={asks} />, <PriceChart candles={candles} />, <TradesTape trades={trades} />.

The trading terminal takes a feed hook you write around your exchange stream. It is called once per open market (a new symbol or interval remounts it) and returns the current MarketFeed on every update:

import { useEffect, useState } from "react";
import type { MarketFeed, UseMarketFeed } from "@beamline/ui/lib/market";

export const useExchangeFeed: UseMarketFeed = ({ symbol, intervalSec }) => {
  const [feed, setFeed] = useState<MarketFeed>({ candles: [], bids: [], asks: [], trades: [], last: 0, prev: 0, change: 0 });
  useEffect(() => {
    const socket = new WebSocket(`wss://your-exchange.example/stream?symbol=${symbol}&interval=${intervalSec}`);
    socket.onmessage = (e) => setFeed((prev) => applyUpdate(prev, JSON.parse(e.data))); // your mapping
    return () => socket.close();
  }, [symbol, intervalSec]);
  return feed;
};

Trading goes through an account you implement on your exchange's order API (TradingAccount in @beamline/ui/lib/market): the balance, positions and resting orders you hold in state, and the calls that change them. A call that fails rejects with an Error; its message appears in the order ticket as is. The terminal never fills an order itself.

import type { TradingAccount } from "@beamline/ui/lib/market";

function useExchangeAccount(): TradingAccount {
  const { balance, positions, orders, fills } = useAccountStream(); // your private socket or polling
  return {
    balance, positions, orders, fills,
    submitOrder: async (o) => {
      const res = await api.post("/orders", o); // map OrderRequest to your API
      return res.filled ? { status: "filled", price: res.avgPrice, size: res.filledSize } : { status: "resting", order: res.order };
    },
    cancelOrder: (id) => api.delete(`/orders/${id}`),
    closePosition: (symbol) => api.post(`/positions/${symbol}/close`).then((r) => ({ status: "filled", price: r.avgPrice, size: r.size })),
  };
}

<TradingTerminal symbols={tickers} useFeed={useExchangeFeed} account={useExchangeAccount()} leverage={10} />

useMockFeed from @beamline/ui/demo/market-simulator produces the feed shape from a random walk, and usePaperAccount from @beamline/ui/demo/paper-account is a paper-trading account that fills orders against it in the browser. The reference app and gallery use both, and they are labelled as simulated wherever they appear.

Tables and records

DataGrid takes rows and typed columns (text, number, currency, percent, date, status, tags), with sorting, filtering, pinning, resizing, inline editing (onRowsChange) and footer aggregates. The CRM screen shows a full example; @beamline/ui/demo/crm-fixtures generates its sample deals.

Streaming text (AI)

Response renders markdown that is still arriving, useSmoothText (@beamline/ui/hooks/use-smooth-text) evens out bursty token delivery. Feed them the text you have received so far, and a streaming flag while more is coming.

Blocks

Each block's props are documented in catalog/index.html and in its source/ui/blocks/<id>/meta.json. The reference app wires every block to an adapter in reference-app/src/data/; replace an adapter's body with calls to your API and keep the shape.

Personal profile and continued edits

The ordinary Settings configuration uses name, email, timeZone and notifications. In the matching 0.2 release, omit workspace and slug to omit Workspace. Optional extra inputs are declared by profileFields and stored under values.profile; their changed keys use profile.<id>. An optional Job title can therefore use the existing save path instead of a second form or handler. Empty optional values are valid. Inspect the installed types before applying these newer fields to an older artifact.

onSave(values, changed) resolves only after the destination has persisted that record and rejects when it has not. The block trims name/email before invoking it. A general Error retains the draft and retry. An Error carrying a known field key adds an inline error and focuses that field, or offers a return to its section. Edits made while the promise is pending remain a new unsaved draft after that snapshot succeeds. The callback currently returns void; if your server canonicalizes other fields, coordinate the saved-value contract rather than silently displaying a different record.

createBrowserSettingsApi in demo/settings-fixtures is a labelled sample adapter: it writes the current origin's localStorage under beamline-profile-sample-v1 by default. Reload in the same browser/origin retains its fictional record; clearing browser storage removes it, and another browser/account does not share it. The old createSettingsApi remains an in-memory demonstration for specialized workspace sections, so reload resets those changes. Replace either with the application's existing handler for real product data.

For an API adapter, preserve its existing endpoint and request mapping, check the response status and reject on failures. Do not mark the UI successful before the operation resolves. Keep a failed draft available for retry; validation must also protect the actual persistence boundary. Verify the stored result after reload, rejection without mutation, and a controlled failure at that real boundary. A toast proves only the UI response.

When embedded in a host that already supplies navigation, matching Settings releases use navigation="tabs" and heading={null}. When the host routes sections, use controlled section with navigation="none". These choices prevent an accidental second application shell.