wickchart
v2.2.0
Published
<wick-chart> — a modern, dependency-free financial charting web component. Candles, line & area charts, crosshair, zoom/pan, indicators (incl. a safe expression mini-language), live streaming via <wick-feed> (incl. tick/volume/dollar bars), a worker compu
Maintainers
Readme
WickChart
<wick-chart> — a modern, simpler, more useful charting web component.
A TradingView-style financial chart as a single framework-agnostic Web Component. One file, zero dependencies, one HTML tag. Canvas-rendered, fast, themeable, and streaming-ready.
Install
npm install wickchart// any bundler / framework — TypeScript types included
import 'wickchart'; // registers <wick-chart>
import WickChart from 'wickchart'; // for WickChart.registerIndicator(...)
import { encodeStateQuery } from 'wickchart/core'; // pure helpers
import { WickChart } from 'wickchart/react'; // React bindings (optional)Or straight from a CDN — no install, no build:
<script type="module" src="https://unpkg.com/wickchart"></script>
<wick-chart label="BTC · 1h" type="candles" indicators="sma:20 volume"></wick-chart>
<script type="module">
const chart = document.querySelector('wick-chart');
chart.setData(bars); // [{ time, open, high, low, close, volume }]
chart.update(bar); // stream live updates
</script>Works in plain HTML, React, Vue, Svelte, Angular — anywhere a <div> works.
TypeScript declarations ship inside the package (generated at pack time from
the JSDoc-annotated source — the repo itself stays 100% dependency-free JS).
Declarative live charts with <wick-feed>
One more script tag and your chart is fully live — data, backfill, streaming — with zero JavaScript written:
<script type="module" src="https://unpkg.com/wickchart/feed"></script>
<wick-feed for="chart" binance="BTCUSDT" tf="1h"></wick-feed>
<wick-chart id="chart" indicators="sma:20 volume" profile></wick-chart>| Attribute | Meaning |
| ----------- | ----------------------------------------------------------------------- |
| for | target <wick-chart> id (auto-pairs with the first chart when omitted) |
| binance | Binance symbol (BTCUSDT) — REST load + WebSocket live + backfill |
| demo | deterministic offline synthetic feed (demo="ETH" picks a base price) |
| url | generic REST endpoint returning a JSON array of bars (+ poll="10" sec) |
| tf | timeframe: 1m 3m 5m 15m 30m 1h 2h 4h 6h 12h 1d 3d 1w |
| limit | initial bars (default 500) |
| live | live="false" loads history without streaming |
| aggregate | information-based bars from a trade stream (see below) |
The element reflects its state in the status attribute (loading, live,
polling, fallback, loaded, waiting, idle) and emits
wick-feed:status / wick-feed:fallback events. When Binance is unreachable
(geo-blocked, offline), it degrades gracefully: WebSocket → REST polling → a
synthetic stream bridged from the last real price, so the chart never goes
blank. It also wires chart.onloadmore for infinite backfill automatically.
Information-based bars (advanced bars)
Add aggregate to any feed and bars close on information, not the clock:
<wick-feed for="c" binance="BTCUSDT" aggregate="dollar:50000"></wick-feed>
<wick-chart id="c" indicators="volume"></wick-chart>tick:200 closes a bar every 200 prints, volume:50 every 50 base units,
dollar:50000 every $50k traded — the quant-grade alternative to time
candles, built client-side from the raw trade tape (Binance aggTrade
WebSocket + paginated REST backfill; offline synthetic prints with demo=;
your own JSON trades endpoint with url= + poll=). The value you pass is
the bar size — tune it per instrument. The machinery is exported too:
import { TickBarAggregator, aggregateTrades } from 'wickchart/feed' to pipe
any trade stream through the same aggregator.
Web Worker compute path (1M-bar histories)
One extra import, one attribute — and the built-in indicators compute in a Web Worker, built for million-bar histories:
import 'wickchart/worker'; // once — wires a shared worker pool into the chart
<wick-chart worker indicators="sma:20 bb:20 rsi:14"></wick-chart>The dataset crosses once per bulk load as six transferable Float64Arrays
(~25 ms per million bars; a structured clone of bar objects would cost ~1 s),
and indicator tasks reference it worker-side. First paint of every indicator
line happens off the main thread; wick:worker fires as results land.
Engages at 50k+ bars with built-in indicators (custom/scripted defs are
closures and stay sync, as does everything below the threshold); results are
cached per data epoch, so streamed ticks stop recomputing the full series per
bar. No worker available? Everything silently stays synchronous — the
attribute is an optimization, never a dependency. Live demo with freeze
numbers: demo/worker.html.
Incremental tick updates (automatic, worker or not): a streamed tick —
appending a bar or replacing the forming one — patches every online-capable
series by recomputing a bounded tail with the same batch definition
(O(warm-up) ≈ 0.1 ms, not O(full history)) and only writing the last
period values, so history is never degraded by tail warm-up error. This
works on worker-computed bases too: the forming bar's indicator value stays
fresh instead of waiting for the next bulk load. Cumulative indicators
(obv, vwap) and the stateful supertrend are excluded and keep the
full-recompute behavior.
Report export (branded snapshots)
One shareable PNG — chart, visible-range stats, watermark — composed from public surfaces only, as an opt-in entry:
import { exportReport, downloadReport } from 'wickchart/report';
const url = await exportReport(chart); // PNG data URL
const blob = await exportReport(chart, { as: 'blob' });
await downloadReport(chart, 'btc-1h.png', { source: 'binance: BTCUSDT' });The header carries the title (the chart's label by default), the visible
range and the brand; the chart keeps its full DPR resolution with a corner
watermark; the stats grid covers the visible window (return, annualized
vol, max drawdown, bars, up/down, average volume, high, low); the footer
credits your source and a timestamp. Theme follows the chart's own
--wick-* CSS variables (or theme: 'dark' | 'light'), scale 1–4.
reportModel(chart, opts) is exported too — plain data, if you want your
own layout.
Why another chart library?
TradingView's charting library is powerful but heavy and enterprise-licensed; most wrappers add build steps and framework lock-in. WickChart takes the opposite bet:
- Zero dependencies, no build step required (~67 KB gzipped for the whole
component —
core.js+wick-chart.js, held to a 68 KB CI budget) - One tag, sane defaults — drop it in and it renders; everything optional
- Built-in usefulness — crosshair + OHLC legend, last-price line, wheel zoom, drag pan, pinch, keyboard navigation, live streaming, PNG export
- Themeable with CSS variables — two built-in themes, full control from outside the component (Shadow DOM friendly)
- Accessible — focusable, arrow-key crosshair, ARIA summary of the data
Try it online
The demo site is deployed to GitHub Pages: https://benyblack.github.io/wickchart/ — a landing page with a live hero chart, the full interactive demo, the zero-JavaScript declarative page, and a React demo driven entirely by React state.
Full documentation lives at benyblack.github.io/wickchart/docs.html — every attribute, method, event, the WickScript reference, overlays (with a live JSON playground), feeds, theming and framework bindings, each with runnable examples. The Plugins hub documents every opt-in package — draw, sessions, replay, compare, navigator, alerts+, layouts, signals, tape, grid, paper — each with its own live playground. This README covers the same ground in plain markdown.
Run the demo locally
npm run dev # serves on http://localhost:5173
# or: npx serve . -l 5173
# or: python -m http.server 5173Then open http://localhost:5173/demo/.
The demo ships with an offline synthetic feed (random walk with volatility regimes + live ticking), and optionally loads real Binance data (REST + WebSocket) for BTC/ETH/SOL when the API is reachable from your network — with graceful fallback to synthetic data if it isn't.
Frameworks
<wick-chart> is framework-agnostic — attributes, one data property,
standard DOM events. The one opinionated wrapper ships as wickchart/react,
which turns that contract into idiomatic React with proper event
subscription/cleanup. react is an optional peer dependency: nothing
changes if you never import wickchart/react.
React
npm install wickchart reactimport { WickChart, useWickChart } from 'wickchart/react';
// drop-in component — props map 1:1 onto the element
export function PriceChart({ bars, onRange }) {
return (
<WickChart
type="candles"
indicators="sma:20 ema:50 volume"
volshading
label="BTC · 1h"
data={bars} // bars are assigned as a property
onRange={onRange} // subscribes to wick:range
onAlert={(e) => toast(`crossed ${e.detail.price}`)}
style={{ height: 420 }}
/>
);
}
// or the hook, when you need the imperative API
function PracticeChart({ bars }) {
const { ref, chart } = useWickChart({ data: bars, indicators: 'sma:20' });
// chart.getDataWindow(), chart.addAlert(...), chart.getState() … after mount
return <wick-chart ref={ref} style={{ height: 420 }} />;
}Rules of thumb:
- Pass a fresh array to
datawhen the bars change — the binding compares by reference, and reassignment is what triggers a redraw (don't mutate). The same rule applies tooverlays(see Server-side overlays). - String/number/boolean props become attributes (
type,indicators,volshading, …);className/style/idreach React as usual. onXxxsubscribes towick:xxxwith cleanup on unmount; anevents={{ range: fn }}object works too.- Works the same on React 16.8 → 19 — no custom-element event caveats.
No build step? The React demo runs straight off a CDN
import map — react and react-dom from esm.sh, the bindings from the
package source.
Vue 3
<script setup>
import { ref, onMounted } from 'vue';
import 'wickchart';
const chart = ref(null);
const bars = ref([]);
onMounted(async () => {
bars.value = await loadBars();
chart.value.data = bars.value;
chart.value.addEventListener('wick:range', (e) => console.log(e.detail));
});
</script>
<template>
<wick-chart ref="chart" type="candles" indicators="sma:20"
style="height: 420px"></wick-chart>
</template>Svelte
<script>
import 'wickchart';
let el;
let bars = [];
$: if (el && bars.length) el.data = bars;
</script>
<wick-chart bind:this={el} type="candles" indicators="sma:20"
on:wick:alert={(e) => console.log(e.detail)}
style="height: 420px"></wick-chart>Data format
Bars are plain objects; time accepts milliseconds or seconds (auto-detected).
For line-style data you can pass { time, value } instead of full OHLCV.
chart.setData([
{ time: 1694000000000, open: 100.5, high: 101.2, low: 99.8, close: 100.9, volume: 1200 },
// ...
]);Attributes
| Attribute | Default | Description |
| ------------- | ---------- | ------------------------------------------------------------------ |
| theme | dark | dark or light |
| type | candles | candles, line, area, bars (OHLC), hollow (hollow up-candles), heikin (Heikin-Ashi) |
| indicators | volume* | Space/comma-separated: sma:20, ema:50, bb:20, vwap, supertrend:10/3, donchian:20, keltner:20/2, ichimoku:9/26/52/26, rsi:14, macd:12/26/9, stoch:14/3, atr:14, obv, cci:20, wr:14, volume, or any registered indicator |
| label | – | Text shown in the legend (e.g. "BTC · 1h") |
| log | off | Logarithmic price scale |
| auto | on | Keep the right edge pinned to the latest bar while streaming |
| precision | auto | Forced decimal places for prices (auto-detected from magnitude) |
| stats | off | Live statistics chip for the visible range |
| profile | off | Volume profile overlay (POC + 70% value area) |
| annotations | off | Smart annotations (volume spikes, gaps, pivots, RSI divergences) |
| volshading | off | Volatility-regime background shading (see below) |
| overlays | – | JSON array of server-side zones & levels (see below) |
* indicators="" disables everything, including volume. Token syntax:
name[:param[/param…]][@color] — e.g. sma:20@#ff0000, macd:12/26/9.
Built-in indicators
| Name | Kind | Params | Notes |
|---|---|---|---|
| sma | overlay | period (20) | |
| ema | overlay | period (50) | |
| bb | overlay | period, mult (20, 2) | Bollinger bands (3 lines) |
| vwap | overlay | – | hlc3 VWAP, resets each UTC day |
| supertrend | overlay | period, mult (10, 3) | ATR trend line, breaks at flips |
| donchian | overlay | period (20) | high/low channel + mid |
| keltner | overlay | period, mult (20, 2) | EMA ± mult×ATR channel |
| ichimoku | overlay | tenkan, kijun, senkouB, disp (9, 26, 52, 26) | 5 lines + shaded kumo; senkou spans projected disp bars ahead |
| rsi | pane | period (14) | fixed 0–100 scale, 30/70 guides |
| macd | pane | fast/slow/signal (12/26/9) | 2 lines + histogram |
| stoch | pane | period, smooth (14, 3) | %K + %D, fixed 0–100, 20/80 guides |
| atr | pane | period (14) | Wilder ATR |
| obv | pane | – | on-balance volume |
| cci | pane | period (20) | ±100 guides |
| wr | pane | period (14) | Williams %R, fixed −100–0, −80/−20 guides |
| volume | overlay | – | histogram at the bottom of the price pane |
Custom indicators
Register your own — anything from a one-liner moving average to a multi-line pane:
WickChart.registerIndicator('cvwap', { // cumulative VWAP over the whole dataset
kind: 'overlay', // or 'pane'
params: { period: 20 }, // defaults; set via indicators="cvwap:30"
compute(bars, params) { // bars: normalized {time,open,high,low,close,volume}
const out = new Array(bars.length).fill(null);
let pv = 0, vv = 0;
for (let i = 0; i < bars.length; i++) {
pv += bars[i].close * bars[i].volume;
vv += bars[i].volume;
out[i] = vv ? pv / vv : null;
}
return out; // single series — or { lines:[{name,values}], histogram }
},
// pane-only extras: guides:[30,70], range:[0,100], fmt:'price'|'fixed1'|'compact'
});
chart.indicators = 'cvwap';import WickChart from 'wickchart' gives you the class for
WickChart.registerIndicator(...) (the element is registered as a side effect
of importing the package).
WickScript — custom indicators as expressions
No build step, no JS: write an indicator inline in the attribute. expr:{…}
draws on the price chart; pexpr:{…} gets its own pane. Add an optional
@color, mix freely with named indicators, and it all round-trips through
shareable URLs.
<wick-chart indicators="sma:20 expr:{(close - sma(close,20)) / sma(close,20) * 100}@ff6a00"></wick-chart>
<!-- oscillator in its own pane -->
<wick-chart indicators="pexpr:{rsi(close,14)} pexpr:{change(close) / close * 100}"></wick-chart>| Series variables | |
|---|---|
| open high low close volume | raw bar fields |
| hl2 hlc3 ohlc4 | classic derived prices |
| Functions | |
|---|---|
| sma(x,n) ema(x,n) wma(x,n) stddev(x,n) | moving stats (window n must be a whole number ≥ 1) |
| rsi(x,n) | RSI of any series |
| hh(x,n) ll(x,n) | rolling highest / lowest |
| prev(x[,k]) change(x) | shifted series / bar-to-bar delta |
| abs(x) sqrt(x) log(x) min(a,b) max(a,b) | element-wise math |
| crossup(a,b) crossdown(a,b) | 1 on a strict cross, else 0 |
| vwap() obv() atr(n) | bar-level series — callable anywhere, e.g. crossup(close, vwap()) in alerts |
Operators are + - * / % with usual precedence, unary -, and parentheses.
Values before a window fills are NaN (not drawn), division by zero yields
NaN, and identifiers are case-insensitive.
The expression is compiled by a hand-written tokenizer + recursive-descent
parser in wickchart/core — no eval, no new Function — with caps on
length (512), tokens (128) and nesting (24). Invalid scripts are reported via
the parse result's unknown list and simply not drawn; they can never execute
anything.
Programmatically, compile once and reuse, or register it under a name for the attribute syntax:
import { scriptIndicator } from 'wickchart/core';
WickChart.registerIndicator('spread', scriptIndicator('close - ema(close,21)'));
chart.indicators = 'spread'; // now usable like any built-inThe demo has a live input for it (type an expression, optionally tick pane, press + Expr — invalid expressions show the compiler's error inline).
Volatility-regime shading
<wick-chart volshading> tints the price pane background by realized
volatility — the rolling stddev of log returns (20 bars by default),
classified against its own full-history percentiles: calm (≤ 30th
percentile, subtle blue), normal (untinted), hot (≥ 70th percentile,
subtle red). Market state at a glance: quiet ranges and violent expansions
read instantly, and the legend shows the hovered bar's regime and
percentile (VOL 30/70 · hot · 94%ile).
<wick-chart volshading></wick-chart> <!-- defaults 30/70, 20 bars -->
<wick-chart volshading="20/85"></wick-chart> <!-- custom cutoffs -->
<wick-chart volshading="20/85/50"></wick-chart> <!-- + 50-bar vol window -->Cutoffs are clamped so the low percentile always stays at least 2 points
below the high one; the toggle and custom cutoffs round-trip through
shareable URLs (vsh=1 / vsh=20/85). A degenerate history (flat series)
classifies everything as normal. The pieces are exported from
wickchart/core (calcRealizedVol, volRegimeBands, percentileOfSorted)
if you want to build on them.
Server-side overlays (zones & levels)
Draw analysis from your own API straight onto the chart: supply/demand
zones (time × price rectangles) and horizontal levels, rendered
behind the candles. Zones without a to extend into future space past the
last bar, like TradingView drawings.
const res = await fetch('https://api.example.com/analysis?symbol=BTC');
chart.setOverlays(await res.json());[
// zone: from/to are timestamps (ms or s); null → chart edge
{ type: 'zone', from: 1753920000000, priceFrom: 33000, priceTo: 35600,
color: '#ef5350', alpha: 0.25, label: 'demand' },
{ type: 'zone', from: 1753920000000, // no `to` → extends
priceFrom: 37700, priceTo: 40900, color: '#26a69a' }, // to the right edge
// level: horizontal price line, full width by default
{ type: 'level', price: 28700, color: '#3f51b5', label: 'S1' },
{ type: 'level', price: 22800, color: '#3f51b5', dash: true },
]addOverlay(o)upserts one (byid),removeOverlay(id),clearOverlays(), andchart.overlaysreads them back.- Colors accept hex /
rgb()/ CSS names plus the palette keysup|down|accent;alphaclamps to 0.02–0.8 (default 0.22). - Timestamps snap to bars (before the first bar clamps left, after the last clamps right); invalid entries are dropped, never thrown — it's API data.
- Fully declarative, too — the same JSON as an attribute:
<wick-chart overlays='[{"type":"level","price":28700,"color":"#3f51b5","label":"S1"}]'></wick-chart>The React binding takes overlays as a prop (fresh array → re-apply), and
normalizeOverlays / barIndexForTime / resolveOverlayColor are exported
from wickchart/core.
Scenario mode — ghost paths & volatility cones
Project what-if into future space: a ghost path of hypothetical prices plus a volatility cone (±1σ/±2σ bands widening with √h from realized vol).
chart.setScenario({
path: [64000, 65500, 66800, 68000], // prices for future bars 1..N
cone: true, // σ-bands from realized vol (default)
label: 'bull case',
color: 'up', // up | down | accent or safe colors
});
chart.setScenario({ horizon: 48 }); // cone-only projection
chart.clearScenario();Setting a scenario reserves future space on the right so the cone stays
visible; the horizon defaults to the path length (1–500) and levels are σ
multipliers (default [1, 2]). Like overlays, scenarios are analysis data —
excluded from shareable state, and the same shape a server-side model could
push. calcVolCone / normalizeScenario are exported from wickchart/core.
Risk planner — R-multiple grid
Plan the trade on the chart: entry + stop define 1R (the risk unit) and reward lines are drawn at kR beyond the entry, with the risk/reward zones shaded. Direction is derived from the stop side.
chart.setRiskPlan({ entry: 64500, stop: 63800, multiples: [1, 2, 3] });
chart.setRiskPlan({ entry: 64500, stop: 63800, targets: [65900, 67300] }); // prices → kR
chart.clearRiskPlan();
chart.riskPlan; // { entry, stop, risk, direction, levels: [{ k, price }], maxK, label }Explicit targets convert to their R multiple (wrong-side prices drop);
multiples win when both are given. At most 8 levels, each ≤ 20R; invalid
specs clear the plan, never throw. normalizeRiskPlan is exported from
wickchart/core.
Bar-walk narrator — history as a story
narrate() builds the timeline of a window (pivot highs/lows, volume
spikes, gaps, RSI divergences, plus derived legs — the move between
opposite pivots); walk() replays the chart through it while wick:walk
events announce each step, so a caption bar can narrate the replay.
chart.narrate(); // [{ i, time, type, note, legPct?, legBars? }]
chart.walk({ from: 0, to: 500, speed: 120, step: 10 });
chart.addEventListener('wick:walk', (e) => {
// { phase: 'step' | 'end' | 'stop', index, events: [...], from, to }
});
chart.stopWalk(); // any pointer/wheel/key input stops it toonarrateWindow (the analyzer) is exported from wickchart/core.
Delta brush — drag-select with stats
<wick-chart brush> makes a plain drag select bars instead of panning:
a live band follows the pointer with a delta chip (Δ% · bars · high · low ·
Σvol); on release the selection commits and fires wick:brush with the
range statistics. Esc (or clearBrush()) clears it.
<wick-chart brush></wick-chart>chart.addEventListener('wick:brush', (e) => {
// { bars, from: {index, time}, to: {index, time}, delta, deltaPct,
// firstOpen, lastClose, high, low, volume }
});
chart.brushSelection; // { i0, i1, stats } | null
chart.clearBrush();Brush mode replaces plain-drag panning (shift-drag still measures);
replacing the dataset clears a committed selection. brushStats is
exported from wickchart/core.
Story mode — guided tours of chart state
A story is an array of scenes (view range, type, indicators,
overlays, scenario, risk plan + title/note). playStory() applies each
scene, eases the camera to its range, holds for dwell, and narrates
through wick:story. Record scenes with captureScene() while you
arrange the chart, or generate them from an analysis.
const story = [chart.captureScene('Overview', 'the full picture')];
story.push({ title: 'The breakout', range: { from, to }, indicators: 'sma:20' });
chart.playStory(story, { dwell: 2200, panMs: 900, loop: false });
chart.addEventListener('wick:story', (e) => {
// { phase: 'scene' | 'end' | 'stop', index, total, scene, title, note }
});
chart.stopStory(); chart.getStory();Any user interaction stops the tour. Scenes are plain data — serialize
or share them. normalizeScene / sceneList / easeInOutCubic are
exported from wickchart/core.
AI-ready data window — getDataWindow()
One call turns whatever is on screen into a compact, LLM-pasteable summary. Everything is computed locally from the visible bars — trend (least-squares drift + fit), realized-vol percentile, SMA/RSI snapshot, up/down bar mix, volume profile notes, and the same pattern detection that powers smart annotations (gaps, spikes, pivots, divergences). Nothing leaves the page until you copy it somewhere.
const s = chart.getDataWindow();
s.text; // markdown — ready to paste into any AI chat
s.trend; // { label: 'strong uptrend', slopePctPerBar: 0.77, r2: 0.94 }
s.volPctile; // 84 → hot regime relative to the window itself
s.patterns; // [{ time, note }] — most recent firstAI agent interface — the chart as a tool surface
The chart can publish its own tool manifest and accept validated tool-calls, so any LLM can operate it with zero glue code — the chart never touches the network; you supply the model call.
chart.aiTools(); // manifest: get_data_window, set_indicators, set_overlays, add_alert, …
chart.aiPrompt(); // system prompt demanding JSON [{tool, args}] ops
chart.aiContext(); // grounding: current state + visible-window summary
chart.applyAI(ops); // validated dispatcher — per-op {ok, result} / {ok:false, error}
const { results } = await chart.ask(
'add RSI, mark the demand zone, and alert me on volume spikes',
{ run: async (payload) => (await callMyLLM(payload)).ops }
);Every op is whitelisted and its args validated (indicator names checked
against the registry, overlays through the sanitizer, enums enforced) — LLM
output is treated as untrusted input, and a bad op returns an error the
model can self-correct from instead of throwing. The
docs page has a live playground driving applyAI() with an
offline demo agent (no network, no keys).
text renders like:
CHART SUMMARY — BTC · 1h · 214 bars · 2026-08-21 → 2026-09-07
- Close 97.03 (−1.20% over window). High 104.20 on 2026-08-28, low 91.40 on 2026-09-01. Max drawdown 8.1%.
- Trend: downtrend (drift −0.061%/bar, fit r² 0.58). Price below SMA20 (99.10). RSI(14) 41.3.
- Volatility: annualized 48%; latest realized vol at the 84th percentile of the window (hot regime).
- Bars: 96 up / 117 down. Volume avg 1.2K/bar, peak 8.9K on 2026-09-01.
- Notable: Gapped down −1.42% (2026-09-01); Volume 4.1× average (2026-09-03).The demo's Explain button shows this in a panel with a one-click copy.
The pure function behind it (windowSummary(bars, i0, i1, opts)) is exported
from wickchart/core for server-side use.
Sonification — the chart by ear
<wick-chart sonify> maps price to pitch (180–880 Hz across the visible
scale, log-aware): moving the crosshair with the mouse or arrow keys plays
a short tone per bar, so trend and shape are audible — a rare accessibility
win for screen-reader users. chart.playRange() sweeps the whole visible
range as a ~4-second pitch sequence, riding the crosshair along for sighted
users. Audio starts lazily within the enabling user gesture (autoplay-policy
safe).
Cross-tab co-view & presence
Tag charts with the same channel and they share pointers — across browser tabs, or between multiple charts on one page:
<wick-chart co-view="btc-room" co-view-name="ben"></wick-chart>Hovering in one tab draws a ghost crosshair (accent, dotted, with the time
pill) in every peer. Positions are synced by bar time, so peers with
different history depths still line up. Ghosts fade ~2.5 s after the peer
stops moving. Same-origin only (BroadcastChannel); the connection follows the
co-view attribute and closes with the element.
Peers also see where everyone is looking: each peer's viewport renders as a colored band (with name) along the top of the plot, updated live as they pan or zoom and swept away ~12 s after they go quiet.
chart.getPeers(); // [{ id, name, range: { from, to }, at }]
chart.addEventListener('wick:peers', (e) => {
// { peers, joined, left } — membership changes only
});PresenceTracker (the TTL bookkeeping) is exported from wickchart/core
for apps that sync presence over their own transport instead.
Smart annotations
<wick-chart annotations> marks notable events on the visible range — volume
spikes (>3× average), price gaps, 41-bar pivot highs/lows, and RSI
divergences — with lettered badges (V/G/H/L/D). Hover a badged bar and the
legend shows a one-line insight ("Volume 4.2× average", "Bearish RSI
divergence"). The current set is emitted on every recompute via the
wick:annotations event, so hosts can build their own UI from it. Badges are
hidden at extreme zoom-out, where bars collapse into columns.
Example: VWAP via the registry
VWAP ships in the demo but not as a builtin — it's the reference for writing your own (session-anchored, resets each trading day):
import WickChart from 'wickchart';
WickChart.registerIndicator('vwap', {
kind: 'overlay',
params: {},
compute(bars) {
const out = new Array(bars.length).fill(null);
let pv = 0, vv = 0, day = -1;
for (let i = 0; i < bars.length; i++) {
const b = bars[i];
const d = new Date(b.time).setHours(0, 0, 0, 0);
if (d !== day) { day = d; pv = 0; vv = 0; }
const tp = (b.high + b.low + b.close) / 3;
pv += tp * b.volume;
vv += b.volume;
out[i] = vv ? pv / vv : null;
}
return out;
},
});
chart.indicators = 'vwap';Plugin layers — extend without forking
addLayer() is the whole extension surface: an external draw hook that paints
into the render pipeline (above chart content, under the crosshair) and can
claim pointer gestures so drags reach your code instead of panning the chart.
Four public coordinate transforms — timeToX/xToTime (extrapolating past
the last bar into future space) and priceToY/yToPrice — anchor your
content in data space so it rides along with zoom and pan:
chart.addLayer({
id: 'flags',
draw(api) {
const x = api.timeToX(t), y = api.priceToY(p); // anchors, not pixels
api.ctx.fillStyle = api.palette.accent;
// …paint in CSS pixels
},
onPointer(ev) {
if (ev.type === 'down' && hitsMyContent(ev)) return true; // claim the drag
},
});
chart.requestDraw(); // repaint hook for interactive layers
chart.removeLayer('flags'); // detach by handle or idA claimed gesture delivers move/up (and cancel on Escape) to the layer
while the chart suppresses pan/brush/measure. Markers, watermarks, signal
badges — or a whole drawing toolkit — plug in without the core growing a
single tool. The main entry is covered by a CI gzip budget (68 KB) so it
stays that way.
Drawings — the wickchart-draw plugin
The first official plugin: TradingView-style drawing tools as opt-in bytes
(~8 KB gz, own CI budget). Trendlines (segment/ray), horizontal levels,
rectangles, fibonacci retracements and text — all plain { time, price }
data that rides zoom & pan, survives reloads, extrapolates into future
space, and serializes to JSON. Anchors magnet-snap to bar times and OHLC.
npm install wickchart wickchart-draw // drawings are a separate opt-in package
import { attachDrawings } from 'wickchart-draw';
const draw = attachDrawings(chart);
draw.setTool('trendline'); // drag to draw; setTool(null) = select/move mode
draw.getDrawings(); // → JSON array (save it); setDrawings(saved)
draw.undo(); draw.clear();
draw.setShare(true); // shared drawings: draw on one tab, appears on all
chart.addEventListener('wick:drawings', (e) => save(e.detail.drawings));Select mode: click a drawing to select it, drag to move, drag the square
handles to re-anchor, Delete removes, Esc cancels a gesture; clicks on
empty space fall through to the chart. Placing a note opens an inline editor
(type + Enter); click a selected note again to re-edit. setShare(true)
reuses the chart's co-view room (or pass an explicit room name) — last
writer wins, remote updates never touch the local undo stack. Peer
dependency: wickchart ≥ 1.4. See the live playground in the docs (Drawings
section — it shares a room, so open it twice and draw on either chart).
Sessions — the wickchart-sessions plugin
Market session shading as opt-in bytes (~6 KB gz, own CI budget): Asia /
London / New York and other sessions drawn as translucent bands, with labels,
closed-weekend shading for equities/futures, and crosshair hover events.
Presets for crypto & forex use the common UTC convention; equity/futures
presets use IANA timezones, so 09:30 is the real 09:30 across DST changes.
Custom defs ({ name, start, end, tz?, days?, color?, alpha? }) cover
midnight-crossing sessions and weekday filters.
npm install wickchart wickchart-sessions // sessions are a separate opt-in package
import { attachSessions } from 'wickchart-sessions';
const sessions = attachSessions(chart, { preset: 'crypto' });
sessions.setPreset('nyse'); // 'crypto' | 'forex' | 'nyse' | 'cme' | null
sessions.setSessions([...]); // custom defs (validated; getSessions() → JSON)
sessions.setWeekends(true); // shade closed Sat+Sun (default for nyse/cme)
chart.addEventListener('wick:sessions', (e) => status.textContent = e.detail.hover || '');The hover bridge listens to the chart's own crosshair events, so shading never claims a pointer gesture — pan/zoom/measure work untouched. Peer dependency: wickchart ≥ 1.4.
Replay — the wickchart-replay plugin
Bar replay as opt-in bytes (~3 KB gz, own CI budget): play history forward
bar-by-bar or at speed while the future stays hidden. The whole engine runs
on the public data API — a setData slice hides the future, update()
appends one bar per step — so the core stays replay-free. A badge layer shows
the mode and position at a glance.
npm install wickchart wickchart-replay // replay is a separate opt-in package
import { attachReplay } from 'wickchart-replay';
const replay = attachReplay(chart);
replay.start(); // head at ~70% of the data (or pass a time/index)
replay.play(); // 4 bars/sec — play(15) for faster, pause() stops
replay.step(); // reveal one bar
replay.seek('2026-03-06'); // jump the head
replay.setLoop(true); // wrap to the anchor at the end
replay.stop(); // exit — the full dataset is restored
chart.addEventListener('wick:replay', (e) => progress.textContent =
e.detail.active ? `${e.detail.index + 1}/${e.detail.total}` : '');Anchors accept bar indices, timestamps (ms/s) or date strings; every change
fires wick:replay with the full state. Pause live feeds while replaying —
an external update()/setData() aborts replay instead of corrupting the
chart (the demo pauses its feed automatically). Paper trading and an equity
curve are the planned 0.2 follow-up. Peer dependency: wickchart ≥ 1.4.
Compare — the wickchart-compare plugin
Normalized multi-asset overlays as opt-in bytes (~4 KB gz, own CI budget):
percent-rebased compare lines (ETH against BTC, TradingView-style) plus
derived ratio and diff lines (BTC/ETH, BTC−ETH), drawn over the
main pane against their own invisible scale so the price axis is untouched.
A legend chip row shows each series with its live value.
npm install wickchart wickchart-compare // compare is a separate opt-in package
import { attachCompare } from 'wickchart-compare';
const cmp = attachCompare(chart);
cmp.setSeries([
{ label: 'ETH', data: ethBars }, // OHLC or {time, value}
{ label: 'BTC/ETH', op: 'ratio', a: btcBars, b: ethBars }, // derived
]);
cmp.setRebase('visible'); // 0% at the window edge, re-normalized while
// panning; 'first' or an epoch anchor also work
cmp.clear(); cmp.detach();Series are sampled onto the main chart's bar times, so timeframes can mix and gaps break the line instead of bridging. Rebased values share one invisible scale inset 8% from the pane edges; the price scale is never distorted. Validated, capped at 6 series, invalid entries dropped. Peer dependency: wickchart ≥ 1.4.
Navigator — the wickchart-navigator plugin
The most-missed TradingView affordance: a silhouette of the whole dataset docked below the chart with a draggable viewport window (~3 KB gz, own CI budget). Drag the window to pan, grab an edge to resize, click outside it to jump — pan/zoom and the window stay in sync live, both directions.
npm install wickchart wickchart-navigator // navigator is a separate opt-in package
import { attachNavigator } from 'wickchart-navigator';
const nav = attachNavigator(chart, { height: 46 }); // strip height, 24..120
nav.detach(); // remove the strip againThe strip needs bottom space, so this plugin pairs with a small core hook:
a layer may declare insetBottom (px) — the largest declared inset reserves
a docked strip at the bottom of the canvas, panes and the time axis shrink
above it, and layers draw it as api.layout.dock. On charts without the
hook the navigator degrades silently. The silhouette is O(n) once per
(dataset, width) and cached. Peer dependency: wickchart ≥ 1.6.
Alerts+ — the wickchart-alerts-plus plugin
The "pro" alert tier (~3 KB gz, own CI budget). Core alerts are runtime-only
by design; this adds what a trading tool actually needs, without the core
growing any of it: persistence (the alert list mirrors into
localStorage and re-arms on reload), desktop notifications + a WebAudio
beep while the tab is hidden, and an optional webhook that receives
every fire as POST { id, price, when, time, bar, key }.
npm install wickchart wickchart-alerts-plus // alerts-plus is a separate opt-in package
import { attachAlertsPlus } from 'wickchart-alerts-plus';
const ap = attachAlertsPlus(chart, {
key: 'BTC:1h', // one storage key per symbol+timeframe
notify: true, sound: true, // hidden-tab surfacing
webhook: 'https://example.com/hook', // optional
});
await ap.requestNotify(); // ask for the notification permission
ap.add({ price: 100, direction: 'above' }); // persisted, re-armed on reload
ap.add({ when: 'rsi(close,14) < 30' }); // scripted alerts persist too
ap.list(); ap.remove(id); ap.clear(); ap.sync(); ap.detach();Once-fired alerts drop out of storage automatically; alerts added directly on the chart are captured at the next save point; storage/fetch are injectable and every storage failure degrades to memory-only, never throwing. Peer dependency: wickchart ≥ 1.4.
Layouts — the wickchart-layouts plugin
Named workspace persistence (~3 KB gz, own CI budget): save and restore
whole chart setups by name — type, theme, log scale, toggles, indicators,
view range, positions, alerts — plus the drawing list when wickchart-draw
is attached. Everything rides the core's public getState()/setState().
npm install wickchart wickchart-layouts // layouts is a separate opt-in package
import { attachLayouts } from 'wickchart-layouts';
const layouts = attachLayouts(chart, {
key: 'my-desk', // storage key (default 'wickchart-layouts')
drawings: draw, // optional wickchart-draw handle — include drawings
});
layouts.save('swing'); // capture the current setup under a name
layouts.load('swing'); // apply it back
layouts.list(); // → [{ name, at, drawingCount }] newest first
layouts.export(); // → JSON string — share it, store it anywhere
layouts.import(json); // merge layouts back (replaces same names)
chart.addEventListener('wick:layouts', (e) => console.log(e.detail.action, e.detail.name));Entries are capped (oldest evicted), storage is injectable, storage
failures degrade to an in-memory store for the session and never throw.
Pair a load with wickchart-alerts-plus's sync() if you also persist
alerts, since a layout load replaces the chart's alert list. Peer
dependency: wickchart ≥ 1.4.
Signals — the wickchart-signals plugin
Candlestick pattern badges (~4 KB gz, own CI budget): bullish/bearish
engulfing, pin bars (hammer / shooting star) and inside bars
drawn as direction-colored letter chips above/below the bar. Hover a badged
bar and the plugin draws the explanation ("Bullish engulfing") and fires
wick:signals — the same passive crosshair bridge as wickchart-sessions,
so badges never claim a pointer gesture.
npm install wickchart wickchart-signals // signals is a separate opt-in package
import { attachSignals } from 'wickchart-signals';
const signals = attachSignals(chart);
signals.setKinds(['engulfing', 'pinbar']); // subset (default: all three; [] = off)
signals.setLabels(false); // hover explanations off
chart.addEventListener('wick:signals', (e) => status.textContent = e.detail?.label || '');Detection is O(n), cached per dataset and kind subset — pan/zoom are pure repaints. Peer dependency: wickchart ≥ 1.4.
Tape — the wickchart-tape plugin
Time & sales (~4.6 KB gz, own CI budget): a live trade-print strip docked at
the bottom of the canvas through the insetBottom hook — time · price ·
size rows colored by side with proportional size bars, oversized prints
highlighted. Display-only: it never claims a pointer gesture. Prints carry
an optional side; without one the plugin applies the classic tick rule
(uptick → buy, downtick → sell), carried continuously across pushes. The
same stream drives the chart: chart.setData(tape.toBars(60000)).
npm install wickchart wickchart-tape // tape is a separate opt-in package
import { attachTape } from 'wickchart-tape';
const tape = attachTape(chart, { rows: 7, bigSize: 50 });
socket.onmessage = (m) => tape.push(m.trades); // single print or batch
tape.setRows(4); tape.hide(); tape.detach(); // rows 3–8; hide frees the dock
chart.addEventListener('wick:tape', (e) => status.textContent = e.detail.total + ' prints');Keeps the newest 500 prints. Peer dependency: wickchart ≥ 1.6 (the dock hook); shares the bottom strip with wickchart-navigator, so attach one or the other.
Methods
| Method | Description |
| ------------------------------- | ------------------------------------------------ |
| setData(bars) | Replace the dataset (sorted automatically) |
| update(bar) | Stream: replaces last bar or appends a new one |
| clearData() | Empty the chart |
| fit() | Reset zoom to the default view (~150 bars) |
| getVisibleRange() | → { from, to } (ms timestamps) |
| setVisibleRange({from, to}) | Jump to a time window |
| exportPNG() | → PNG data URL of the current canvas |
| getDataWindow() | → AI-ready summary of the visible window (see below) |
| getState() | → serializable snapshot (type, indicators, view, positions, alerts) |
| setState(state) | Apply a snapshot; a pending view applies after the next setData() |
| addLayer(layer) / removeLayer(idOrHandle) | Register/detach a plugin layer (draw hook + optional pointer claim + optional insetBottom dock strip) |
| requestDraw() | Repaint on the next frame (interactive layers) |
| timeToX(t) / xToTime(x) | Bar time ⇄ x-pixel; extrapolates into future space |
| priceToY(p) / yToPrice(y) | Price ⇄ y-pixel in the main pane (log-aware) |
Infinite history (loadMore)
Assign a callback and the chart fetches older bars whenever the user scrolls toward the left edge — the view stays anchored while data is prepended:
chart.onloadmore = async (fromTime) => {
const res = await fetch(`/api/bars?before=${fromTime}&limit=500`);
return res.json(); // [{ time, open, high, low, close, volume }, …]
};Return an empty array (or throw) when history is exhausted and the chart stops asking. Data gaps (weekends, session breaks) are marked with subtle dashed dividers on the time axis.
Positions & alerts
Visualize trades directly on the chart — entry/stop/target zones, a live P&L chip, and price alerts that fire during streaming updates:
chart.addPosition({ side: 'long', entry: 64200, stop: 62900, target: 66800, qty: 0.5 });
chart.addPosition({ id: 'x1', side: 'short', entry: 66000, qty: 1 });
chart.removePosition('x1');
chart.addAlert({ price: 65000, direction: 'above' }); // 'above' | 'below' | 'cross'
chart.addEventListener('wick:alert', (e) => {
console.log('crossed!', e.detail.id, e.detail.price);
});
// scripted alerts — any WickScript predicate, fired on its false→true edge
chart.addAlert({ when: 'crossup(rsi(close,14), 30)' });
chart.addAlert({ when: 'volume > sma(volume,20) * 3', once: false }); // re-arms
// evaluate only on final candles, so the signal cannot repaint
chart.addAlert({ when: 'crossup(rsi(close,14), 30)', evaluate: 'close' });The P&L chip recalculates on every streamed bar. Alerts are edge-triggered
(fire once per crossing) and one-shot by default (once: false to re-arm).
Scripted alerts are evaluated locally on every streamed bar — the event
carries the triggering close as price plus the when source; an invalid
predicate is rejected (addAlert returns null), never thrown.
Live vs closed-candle evaluation. Alerts evaluate on every update by
default, the still-forming candle included — so a technical signal can
repaint (RSI crosses 30 mid-candle, price reverses, the candle closes back
above 30). Pass evaluate: 'close' to fire only on final candles, or set
<wick-chart alert-evaluate="close"> as the chart-wide default (per-alert
evaluate still wins). A candle is final once a newer bar arrives, or as
soon as the feed says so via closed: true on update() — <wick-feed>
forwards Binance's k.x flag, so the signal lands at the close rather than
one candle later. Historical corrections and backfilled candles never fire
live alerts in either mode.
Timezone & VWAP sessions
Axis labels and the crosshair readout use the viewer's timezone by default.
Pin them with timezone — local, utc, or any IANA zone, DST included:
<wick-chart timezone="Europe/Stockholm"></wick-chart>
<wick-chart timezone="America/New_York"></wick-chart>Day dividers and month/year ticks follow the chosen zone, so a "1 Feb" tick is 1 February there. An unrecognised zone falls back to UTC and warns once.
VWAP's session boundary is deliberately separate from the display zone — changing the axis to Stockholm shouldn't silently re-anchor a BTC chart. It defaults to the UTC day (the crypto convention) and moves only when asked:
<wick-chart indicators="vwap" vwap-anchor="America/New_York"></wick-chart>vwap-anchor takes utc (default), local, an IANA zone, or a fixed offset
in milliseconds. Equities, futures and FX rarely open at UTC midnight, so the
default is right for crypto and wrong for most other markets — set it
deliberately. calcVWAP(bars, anchor) takes the same values directly.
Stats & measure
<wick-chart stats> shows live statistics of the visible range — return %,
max drawdown, annualized volatility, up/down bar counts, average volume —
recalculated as you pan and zoom.
Hold Shift and drag across the chart to measure a move: an overlay shows
Δprice, Δ%, bar count and elapsed time, and a wick:measure event fires on
release (detail.from / detail.to carry index, time and price). Click or
press Esc to clear.
Shareable URLs
getState() / setState() serialize everything about the chart, and
encodeStateQuery / decodeStateQuery (exported from src/core.js) turn a
state into a compact query string — the demo maps it to the page hash, so any
chart configuration is one link away:
import { encodeStateQuery, decodeStateQuery } from 'wickchart/core';
const link = `${location.origin}#${encodeStateQuery(chart.getState())}`;
history.replaceState(null, '', link);
// later, on load:
chart.setState(decodeStateQuery(location.hash.slice(1)));Reflected properties (chart.type = 'line') work for theme, type, label,
indicators.
Events
| Event | Detail |
| --------------- | ---------------------------------------------------------- |
| wick:crosshair | { index, bar, x, y, price } on hover / arrows, null on leave |
| wick:range | { from, to } after zoom / pan / jump |
| wick:select | { index, bar, price } on click/tap (e.g. open an order form at that price) |
Theming
All colors are CSS custom properties settable on the element (they pierce the Shadow DOM):
wick-chart {
--wick-bg: #0d1117; /* transparent works too */
--wick-up: #16c784;
--wick-down: #ea3943;
--wick-accent: #4c8dff; /* line & area color */
--wick-text: #8b949e; /* axis text */
--wick-text-strong: #e6edf3; /* legend values */
--wick-grid: rgba(230,237,243,.05);
--wick-border: rgba(230,237,243,.09);
--wick-crosshair: rgba(230,237,243,.42);
--wick-rsi: #a78bfa;
--wick-overlay-0: #f0b429; /* SMA color, …-1, -2, … for more overlays */
}Interactions
| Gesture | Action |
| -------------------------- | ----------------------------------- |
| Mouse wheel / trackpad ⌘+scroll | Zoom, anchored at the cursor |
| Trackpad horizontal scroll | Pan |
| Drag | Pan (auto-follow re-arms at the right edge) |
| Pinch (touch) | Zoom |
| Long press (touch) | Open the crosshair, then drag to scrub across bars |
| Vertical swipe (touch) | Scrolls the page, not the chart |
| Double-click / double-tap | Reset view |
| ← → (+Shift ×10) | Move crosshair |
| + / − | Zoom in / out |
| Home / End | Jump to oldest / newest |
| Esc | Clear crosshair |
Performance
Canvas 2D with a rAF-batched, visible-range-only render pipeline. Measured on a desktop (Chromium, 1100×760, all indicators on: SMA + EMA + RSI + volume):
| Scenario | Per full render | | ----------------------------------------- | --------------- | | 600–50,000 bars, default view (~150 visible) | ~0.2 ms | | 5,000 bars, max zoom-out (~2,900 visible) | ~6 ms | | 50,000 bars, max zoom-out (~3,100 visible) | ~16 ms | | Streaming tick (update + full re-render) | 0.5–19 ms |
A 60 fps frame budget is 16.7 ms, so the default view uses ~1% of a frame.
Hot paths are deliberately allocation-light: date labels are built lazily only
for actual axis ticks (with cached Intl.DateTimeFormats), and candles/volume
are drawn in two batched passes by direction instead of one draw call per bar.
Deep zoom-outs are columnar: when more bars are visible than ~1.5× the pixel width, bars aggregate into per-pixel min/max columns (first open / max high / min low / last close / summed volume), so rendering any history at any zoom costs O(screen width), not O(bars). The minimum zoom level adapts to the dataset — every chart can be zoomed out until the entire history fits.
If you ever push past this (100k+ simultaneously visible bars, dozens of series, high-frequency ticks), the scaling levers are: incremental indicator updates (SMA/EMA/RSI are O(1) online), min/max columnar downsampling per pixel column, and an offscreen layer so hover only repaints the crosshair.
Architecture notes
- Single ES module, Custom Element + Shadow DOM, Canvas 2D with
devicePixelRatio scaling and rAF-batched invalidation. The ratio is watched
with a
resolutionmedia query, so moving a window between monitors re-renders at the new resolution rather than staying soft - Only visible bars are drawn; indicator series are computed lazily and cached per data version (prefix-sum SMA, Wilder RSI)
- Time axis picks tick steps from bar interval (minutes → months) and labels day/month boundaries like a pro terminal
- No dependencies, no build step required — but it bundles/tree-shakes fine
Tests
Two suites, and they answer different questions.
npm test # Node: pure functions, indicator maths, parsing, plugins
npm run test:e2e # Playwright: the chart in a real browsernpm test is the fast one and covers the bulk of the library. What it cannot
reach is anything that only exists once a browser is involved: custom-element
upgrade, a real canvas, wheel/pointer/touch input, devicePixelRatio,
ResizeObserver, and React re-renders against a live DOM node. Bugs have
shipped in exactly that gap — a React parent re-render used to silently reset
the user's zoom, and a chart moved to a monitor with a different pixel ratio
kept rendering at the old resolution. Both are covered in e2e/ now.
The browser suite serves the repository over a small dependency-free static
server (e2e/server.mjs) and loads the library from source, so it tests the
files that ship rather than a build artifact. The React fixture pulls React
from esm.sh, the same way demo/react.html does.
Running it locally. npm run test:e2e downloads Playwright's bundled
Chromium the first time. If that CDN is blocked on your machine, point the
suite at a browser you already have:
WICK_E2E_CHANNEL=chrome npm run test:e2e # or msedgeVisual regression is opt-in. Canvas output is not pixel-identical across operating systems, so a committed baseline from one machine red-lights everyone else; the rest of the suite compares the chart against itself instead (repaint X, assert only what should have moved did). To gate on real screenshots, generate baselines on the platform that will run them:
WICK_E2E_VISUAL=1 npm run test:e2e -- --update-snapshotsRoadmap ideas
- More overlays (Bollinger, VWAP), MACD pane, drawing tools
- Data callbacks (
loadMorefor infinite history) - Incremental (O(1)) indicator updates for high-frequency streaming
- Min/max downsampling and/or an offscreen hover layer if profiling ever demands
Migrating from 1.x to 2.x
2.0 removes the deprecated 0.x hab-* aliases (they warn once per surface
from 1.7.1) and moves the six optional feature families out of the core
entry into their own packages — the core drops back under ~65 KB gz. The
full plan and delivery sequence: ROADMAP-V2.md.
Aliases removed. Renaming is mechanical; each warns in 1.7.1 already:
| removed in 2.0 | use instead |
|---|---|
| <hab-chart> / <hab-feed> | <wick-chart> / <wick-feed> |
| hab:* events (every chart event fired twice in 1.x) | wick:* of the same name |
| hab-feed:* events | wick-feed:* |
| --hab-bg, --hab-up, … (runtime and stylesheet fallbacks) | --wick-* of the same name |
Still on 0.x? Upgrade through 1.x first — the 1.x releases carry the aliases with warnings, so the rename can be done lazily there.
Features moved to packages. Calls keep their shape: each
attachX(chart) installs the familiar methods on the instance, so
existing call sites survive with one added import line. Without the
package, the core methods become warn-once stubs naming it:
| 1.x (in core) | 2.0 package |
|---|---|
| narrate() · walk() / stopWalk() · playRange() · the sonify attribute · captureScene() / getStory() / playStory() / stopStory() | wickchart-narrator |
| the co-view / co-view-name attributes · getPeers() | wickchart-coview |
| setScenario() / clearScenario() · setRiskPlan() / clearRiskPlan() | wickchart-scenario |
| aiTools() · aiPrompt() · aiContext() · applyAI() · ask() | wickchart-ai |
import { attachNarrator } from 'wickchart-narrator';
attachNarrator(chart); // chart.narrate() / walk() / playStory() … work as beforeUnaffected: getDataWindow() stays in core (a data API, not an LLM
API), getState() / setState() serialize none of the moved features, and
all four packages are already published and documented on the
plugins hub — you can
adopt them today, on 1.x (attaching simply shadows the core's identical
methods).
Releases
Versioned per semver; every release is a tagged GitHub Release with the changelog — see CHANGELOG.md.
License
MIT
