Changelog
Release notes for AXIS.
This page
Manuals
- PWA engine
fetchno longer sendsX-AXIS-Client. That custom header forced a CORS preflight on every/health,/run, and/sessioncall. Firefox caches a successful OPTIONS for 24h (Access-Control-Max-Age: 86400); if the cached allow-list omitted the header, Pine eval failed with “Cannot reach the Pine engine” and a CORS storm. Trust staysOrigin: https://axis.hoox.shon the Worker and Pro API;engineHeaders()still sends JSON + optional bearer. Pinned bytests/engine-request-headers.test.ts. - AXIS Worker
/runaccepts the first-party PWA without an API key during open beta. ExactOrigin: https://axis.hoox.sh(orX-AXIS-Client: pwafrom IDE/preview) skipsrequireApiKeywhile per-IP / optional per-key rate limits still apply; other clients still need a key when auth is required. Pinned byworker/tests/keys-runtime.test.ts. - Chart rolling-window defaults are 10,000 bars (growth rail 12,000). The client no longer pre-caps history for a free-tier engine limit; the engine's own 413 /
max_barshandling stays as defence. Anchored-window and rolling-window tests updated.
All notable changes to AXIS (hoox-sh/axis) are documented in this file.
This changelog is recursive: it lists the full git history of the
repository, grouped by month and conventional-commit type. Agents and
humans must keep it updated on every release (see AGENTS.md § Changelog & releases).
Format roughly follows Keep a Changelog with commit SHAs for traceability.
Generated/updated: 2026-11-04 · 959 commits · describe-tag: v2.27.0
[Unreleased]
Security
- Public
/api/onchain/llama/protocolsis rate-limited and size-capped. Unauthenticated guests no longer receive the raw DefiLlama ~9.5 MB dump. The Worker slims each row to{slug,name,tvl,category}, truncates the list to 1 MiB, and allows 10 list fetches per IP per minute (RateLimit-Limit: 10,Retry-After: 60on 429). Other/api/onchain/*data routes stay at 120/min;/api/onchain/healthstays a small public JSON and is unmetered. Upstream bodies over 8 MiB (16 MiB only for the protocols fetch, so it can slim) are rejected with 413PAYLOAD_TOO_LARGEwithout buffering. Pinned byworker/tests/onchain-proxy.test.tsandworker/tests/upstream-fetch.test.ts. - Worker JSON/preflight fill nosniff, X-Frame-Options, and Referrer-Policy.
withSecurityHeadersalready stamped HSTS (max-age=15552000; includeSubDomains, no preload) on every Worker response including JSON. It now also setsX-Content-Type-Options: nosniff,X-Frame-Options: DENY, andReferrer-Policy: strict-origin-when-cross-origin. No CSP on API JSON (document policy). Pinned byworker/tests/http.test.tsandworker/tests/health-deep.test.ts. - Hashed
/assets/*cache 1y immutable; HTML revalidates; HSTS; CSPconnect-srcallowlist. Cloudflare® Pagespublic/_headersnow sendsCache-Control: public, max-age=31536000, immutablefor content-hashed/assets/*andpublic, max-age=0, must-revalidatefor HTML.Strict-Transport-Security: max-age=15552000; includeSubDomains(no preload) is on Pages and Worker responses. CSPconnect-srcdrops scheme-widehttps:/wss:for AXIS (axis.hoox.sh,worker.axis.hoox.sh), PYNE (pynescript.online, pyne-worker / pyne-agent), Pages previews, Binance/MEXC/OKX/Bybit/Coinbase/Kraken stream+REST hosts, on-chain fallbacks, Git forges, and localhost.Permissions-Policydisables camera/mic. Cloudflare® Insights beacon stays out ofscript-src(edge inject onaxis.hoox.shvspages.dev).wasm-unsafe-evalstays for Pyodide. Pinned bytests/hp/security-headers.test.ts.
Added
- PWA crawler shell: Open Graph / Twitter tags,
favicon.ico, sitemap, noscript.index.htmlnow shipsog:*/twitter:*(title AXIS, description from the existing meta, image/assets/icon-512.png) plus a canonical URL. Crawlers that request/favicon.icoget a real ICO (16/32/48 fromfavicon.svg); SVG tab icon stays.public/sitemap.xmllistshttps://axis.hoox.sh/only (Mintlify docs stay onhoox.sh/axis/docs).public/robots.txtpoints at that sitemap and skips/pyodide/,/vendor/,/plugins/. No-JS users see a blurb with a docs link; empty#appSPA is unchanged.
Changed
- Browser public Binance REST skips the Worker. The PWA fetches
data-api.binance.visiondirectly (Access-Control-Allow-Origin: *) for klines/ticker — no/api/market/binance/…hop (CF egress is geo-blocked).api.binance.comstays off the browser path (CORS spam). Signed/HMAC still uses Worker. Node/tests keep Worker-first. Pinned bytests/binance-http.test.ts. - Status bar layout: collapsed = Live / Last / Compose chips +
bars · Dataset · Script / Panecounts; expanded = latency · network · app groups with little meters (Run/Paint/Path + planes). Diagnostics is Settings-only (no Diagnostics button on the rail). Pinned bytests/hud-inspector.test.tsande2e/statusbar.spec.ts. - Deploy identity is VERSION + git SHA, proven on deploy.
VERSIONstays the semver SSOT (bun run sync:versions).public/version.jsonstays semver-only so local builds do not dirty git. Vite writesdist/version.json{version, git, gitShort, dirty, buildTime}fromgit rev-parse HEAD(orCF_PAGES_COMMIT_SHA/GITHUB_SHA). The PWA update banner compares that identity, so a same-semver Pages deploy still prompts. Worker/health.gitis the same SHA:wrangler --define WORKER_GIT_SHA:"<sha>"plus--var GIT_SHA:<sha>(env.GIT_SHAfallback).axis deploy worker/pagesfail if live/health.gitordist/version.json.git≠git rev-parse HEAD. Bunaxisloadssrc/so a stalepackages/cli/distcannot skip the stamp. Pinned bytests/git-stamp.test.ts,tests/update-manager.test.ts,tests/version.test.ts,worker/tests/http.test.ts, andpackages/cli/tests/deploy.test.ts. - Shared chrome — press, focus, disabled, and selected feedback. Compact controls share
--ui-press-y(1px translate),--ui-focus-ring-inset, and--ui-disabled-opacity/--ui-busy-opacity. Selected chips use the inset ring so idle/on keep the same footprint. Pinned bytests/phase4-ux-tokens.test.ts. - Docs hub: AXIS 2.28.0, vendored PYNE 0.6.29, glossary, first-run copy.
docs/index.mdxcitesVERSION/package.json2.28.0 and the wheel AXIS actually vendors (pynescript-0.6.29-py3-none-any.whl— not PyPI 0.6.33). Hub glossary sentence: AXIS (chart) · PYNE (language) · HOOX (mesh) ·worker-axisservice vs retiredpynescript-axisproject id. Quick start first-run is boot candles + Add to chart / Run (topbar Load is a refetch). Chart type/interval/scale documented as Chart type & scale; app-wide chrome stays Studio → Settings. In-app gear label is still “Chart settings” (Engineering). - First-use: Run vs Add, empty-chart hint, ~800px wrap, no auto-open Scripts. Editor primary is Run (never a bare Add). Topbar primary is Add to chart on first apply and Run when an instance is already on the chart; the caret stays Add instance or built-in. An empty chart with candles and 0 scripts shows a one-shot Run chip (default RSI / editor buffer) — dismiss once, not a tour. Builtin apply no longer opens the Scripts panel (the plot already landed). Command/module bars wrap at
max-width: 900pxinstead of clipping; Run keeps its label. Pinned bytests/first-run.test.tsandtests/builtin-scripts.test.ts.
Fixed
- Scripts plot section stays open across live ticks; plot rows are one line. Expanding Plots closed again every tick because Solid
<For>remounted cards when script object identity changed. Open state is now remembered by script id (like the style popover). Color swatch, name, and controls sit on a single row. Pinned by Scripts panel UX. - Scripts panel plot controls pass the script owner id (and hide channel fills). Eye / style / width / color / display toggles called pane-manager without
ownerId, so owner-scoped overlays (Donchian Upper/Lower/Mid) often no-oped; SVG fills ignored the eye entirely. Controls now passindicator.id, and eyed-off fill edges hide the matching SVG band. Pinned bytests/plot-fills-signature.test.ts. - Donchian axis mismatch paints via chart-window realign instead of resub storm. Continuous unmergeable deltas used to strip
bar_timesfrom the applier cache, so later tips fell through todelta rejectedand soft-fresh-subscribe forever (resync~18/min). Live-wire now fits series to the subscribe window, stamps chartbar_times, overlays the tip, and paints — no resume loop. Concurrent/api/ws/live?issue=ticketmints for the same Worker+key still coalesce into one GET. Pinned bytests/engine/live-wire.test.tsandtests/engine/live-ticket.test.ts. - Detaching a script releases its
/ws/livesubscription. Remove cleared overlays but left the engine sub running, so an orphaned Donchian kept painting Upper/Lower after a replacement (or Strat) was added — doubled channel lines.detachIndicatorFromChartnow callsstreamReleaseScriptbefore overlay cleanup. Pinned bytests/indicators-reapply.test.ts. - Donchian (and other channel) fills no longer vanish on live tips. Silent stream frames often omit
kind: fillplot_meta while Upper/Lower series remain; apply then calledclearPlotFillsand the SVG band disappeared after the first tip. Live applies now merge prior fill plot_meta (like input descriptors) and skip clearing fills when the silent payload has no fill bands. Pinned bytests/engine/apply-bridge.test.ts. - Live script tip lag no longer looks “live” while frozen. Venue ticks kept re-painting
applier.cachedasstream-deltaafter PYNE stopped advancingbar_times, soscriptStatusstayed live andwire.script.paintkept firing while the indicator tip trailed the chart by dozens of bars (onlyindicators.runrecovered it).evalScriptnow compares the cache tip to the last closed chart bar and, past a 5-bar threshold, asks for a soft fresh subscribe (connecting, notdegraded) with backoff before status/resync side effects — so a second live script is not flicked degraded every tick. Pinned bytests/engine/live-wire.test.ts. - MCP bridge ticket mint retries browser AbortError. Reload / HMR aborts on
GET /api/mcp/bridge?issue=ticketused to logMCP bridge ticket failed — The operation was abortedand feel like a freeze. Mint now retries aborts twice (250 ms) and soft-logsticket aborted — reconnecting. Pinned bytests/mcp-bridge-tabs.test.ts. - Live script deltas stop falling back to a 12k-point full snapshot every tick. JSON
full/deltaframes never advanced the AXB1 ingest cursor, so binaryPLOT_TIP/APPEND_BARframes were numbered from a stale count and spliced at the wrong index; every rejected splice resumed the same pooled session for another full snapshot. The cursor now follows every JSON frame, a missingseqno longer becomesseq: -1(which classified as a rewind), and two consecutive unpaintable fulls escalate fromresumeto a fresh subscribe. Pinned bytests/hp/ingest-core.test.tsandtests/engine/live-wire.test.ts. - Pages
/assets/*no longer stacks HSTS / Cache-Control. Cloudflare® joins every matching_headersrule with commas. Repeating security headers on/assets/*produced liveStrict-Transport-Security: max-age=…, max-age=…(Firefox “could not be parsed”) andCache-Control: must-revalidate, … immutable./assets/*now detaches Cache-Control (! Cache-Control) and sets immutable only; security headers stay on/*. Pinned bytests/hp/security-headers.test.ts. - Live
/api/ws/livefails closed when engine Upgrade fails. The relay usednew WebSocket(EXTERNAL_BACKEND)and still returned 101 to the browser when upstream was dead, so Firefox reported “can't establish a connection”. It nowfetches withUpgrade: websocket, usesaccept({ allowHalfOpen: true })for proxy close coordination, returns 502UPSTREAM_WSwhen the peer refuses, and skipswithSecurityHeaderson 101 responses. Pinned byworker/tests/live-proxy.test.ts. - Live ticket OPTIONS allows
X-API-Key.mintLiveTicketsendsengineHeaders()(Authorization+X-API-Key), so Firefox preflight requestsauthorization,x-api-key. WorkerAPI_CORS/BASE_HEADERSomittedX-API-Key, and a 204 still failed with “CORS Missing Allow Header”.X-API-Keyis now on every Worker CORS allow-list that inherits the base set (and on MCP). Pinned byworker/tests/http.test.ts. PYNEcors.pyalready listed it. - Venue-first live: stop PYNE OHLCV series replica on every tick. With
live_stream, multiplex still calledensureSeries+ REST/data/session/.../tickinto pynescript.online (full history + forming bars) while candles already came from the venue socket. Chart paint stays venue-first; PYNE is only script subscribe full/delta viastreamEvalScript. Also reverted the Binance vision REST fallback. Pinned bytests/multiplex-stream-drive.test.ts. - Overlay tip: no hold-forward lag. Time-join still maps EMA/SMA onto the candles they were computed for, but unevaluated newer bars stay
null/ blank instead of copying the last value. That tip-copy looked like a "usual few-bar lag" on live BTCUSDT.alignSeriesToTimes,extendSeriesToWindow,projectToCandleAxis, idleextendOverlayTips, and live tip-cache slide no longer fabricate a tip; a real engine delta must land it. Pinned bytests/runner-engine-owned-apply.test.ts,tests/chart-time-axis.test.ts, andtests/pane-manager.test.ts. - AXIS EMA (and other overlays) stay on candle time from the first paint. A live BTCUSDT 1m run often has a missing minute (
windowUniform=false, nobar_times), and the first/runis already shorter than the chart (candles that arrived during eval). Apply head-padded that short series, so the EMA crash sat to the right of the candles before any live tick, the last-value chip lagged last price, and extra candles ate the right-offset whitespace. Apply now recovers the slice fromwindowT0on the chart even when gappy, prefix-zips a too-long stamp, nulls a short bars-in/runtip instead of head-padding, and the runner copiesengineTimesfrom the bars it sent beforeengine.run. Pinned bytests/runner-engine-owned-apply.test.ts,tests/window-axis.test.ts, andtests/engine/apply-bridge.test.ts. - Skip-link, 4.5:1 faint/accent text, visible focus rings. PWA
index.htmlships a skip-link to#axis-main(outside#app; existing noscript stays). The chart workspace and editor popout expose<main id="axis-main">after mount — Solid still mounts into#app.--color-text-faintis#737b8a(dark) /#636b7a(light); light--color-accentis#4a59c4.buildChromeCssVarsfloors faint, dim, and accent-as-text at WCAG AA 4.5:1.:focus-visible { outline: none }on chrome without a ring now gets a 2px or inset accent ring (prefers-reduced-motionunchanged). Pinned bytests/a11y-chrome.test.tsandtests/theme-manager.test.ts. - Guest first visit no longer spams Binance CORS / 401 bars. The browser cannot read
api.binance.com(CORS/geo); each doomedfetchis a console error the app cannot suppress. Public klines now go Worker/api/market/binance/…first (same order as MEXC). Direct public hosts stay for tests / node /skipWorkerProxy; a real browser never hits them — oneconsole.warnif the Worker misses. GuestGET /data/series/barswithout an engine key is skipped (that route 401s); the public chart still loads via venue REST + Worker and stays degraded when WS is down (P1 Live chip). Pinned bytests/binance-http.test.ts,tests/signed-fetch.test.ts,tests/integration/load-symbol.test.ts, andtests/multiplex-engine-owned-paint.test.ts. - Footer Last and Live match the current symbol and stream arm. Switching BTCUSDT → ETHUSDT left the scanline Last on the previous pair’s tick (~75k) until reload, because
telemetry.lastTickwas not cleared onloadBarsandTickPulsenever scoped the tick tostore.symbol. Last now drops a foreign tick, clears on symbol/interval/venue load, and shows the new close or—until the first tick of the new instrument. The Live word said Live whenever the venue plugin reportedconnected, even when script cards showedStream: degraded · websocket · no-authand the engine/ws/livewire was closed. Live is now only when the arm is actually open; otherwise Degraded / Offline / REST — same truth as script cards (wire.open=falseandfeedStatus=degradedpin Degraded, not Live). Pinned bytests/hud-model.test.tsandtests/store.test.ts. - Overlay plots stay on the candle time axis after a live reload. A hard reload evaluates AXIS EMA (and other overlays) on the load window, then venue ticks add candles before the next script run. The run ships
windowT0/windowCountbut notbar_times, so apply head-padded the short series onto the longer chart — every EMA value slid onto a newer bar, the line stopped short of the last candles, and those extra candles ate therightOffsetwhitespace (phantom empty right margin / overlay "shifted left"). Apply now rebuilds the engine axis from a uniform window stamp and time-joins (holding the last value onto newer bars, never head-padding).projectToCandleAxis/padToCandleCounthold the overlay tip onto painted candles the engine has not re-run yet, so a whitespace{ time }row cannot break the line N bars left of the last candle. Overlay viewport restore rebases throughrebaseLogicalRangeso a setData cannot drop the reserved right offset. Pinned bytests/runner-engine-owned-apply.test.ts,tests/chart-time-axis.test.ts,tests/window-axis.test.ts, andtests/pane-manager.test.ts. - PWA engine
fetchno longer sendsX-AXIS-Client. That custom header forced a CORS preflight on every/health,/run, and/sessioncall. Firefox caches a successful OPTIONS for 24h (Access-Control-Max-Age: 86400); if the cached allow-list omitted the header, Pine eval failed with “Cannot reach the Pine engine” and a CORS storm. Trust staysOrigin: https://axis.hoox.shon the Worker and Pro API;engineHeaders()still sends JSON + optional bearer. Pinned bytests/engine-request-headers.test.ts. - The chart guard no longer delivers a series computed over a holed engine window. When the engine's own bar series is missing minutes in the middle, every plot it returns is Pine
nafor the chart bars it never saw — the chart showed that as a gap (or, before the line-break fix, a connector). The guard now withholds that run and keeps the last good plots while the repair runs:engineHoleTimes()(indicators/engine-hole-guard) isolates the skipped chart bars inside the engine axis' own span (a shorterwindowBarsclamp or a head-padded history is a shorter window, not a hole), andholdForEngineHole()holds for at mostENGINE_HOLE_HOLD_MS(120 s) orENGINE_HOLE_HOLD_MAX(4 applies) — a hole that outlives the budget is painted with a break instead, so an unfixable window can never freeze an indicator. The hold is logged once per script per minute ("Engine series has an internal hole of N bar(s) — holding the last good plots while it repairs") and applies to both the interactive/bar-close path (runner) and the live path (apply-live). Pinned bytests/engine-hole-guard.test.ts(11 cases) and arunner-engine-owned-applycase that assertsskipped: 'engine-hole'with nothing painted. - A repair whose local upload cannot fill a hole now falls back to the engine fetch.
repairStaleEngineSeriesuploaded AXIS' local window and, when that added nothing (added <= 0— the local dataset has the same minutes missing, or the hole is older than the window), gave up and remembered the bounds as "not fillable". That is exactly the observed 40-bar case: the upload was a no-op and only the engine-side fetch (walking the venue) could close it. The repair now falls through toPOST /data/series/fetchinstead of returning, keeping the bounds memo so the same hole is not re-uploaded every tick. - The "no bar axis" warning no longer fires on ordinary session ticks. An unchanged tick carries no series at all (the client reuses its cached result), so
alignApplySerieswarned that the engine shipped no bar axis even though there was nothing to align — a warn-once line per script that read like a real problem in the log. The warning now requires a paintable series (orplots); the stale-window skip keeps its own warning. Pinned by tworunner-engine-owned-applycases. - A hole in the engine's bar axis no longer draws a straight line through the indicator. When the engine's own bar series is missing minutes, the apply emits a Pine
nafor every chart bar the run's axis skipped (correct: the panes stay aligned), but Lightweight Charts spans thosenarows with a connector — the "autoconnected ends" diagonal through bars the script never evaluated. The run now reports the skipped chart times (missingChartTimesinindicators/run-helpers) and the chart path breaks the stroke there with the existingLineBreakPrimitive(gapAwareLineBreakPoints): the native connector is hidden and each finite run is stroked separately.plot.style_linestill spans scriptna(interpolated inside a finite run, exactly the segment the library draws) — only a data hole breaks, so Pine parity is unchanged. With no axis to compare against, an internalnarun of 10+ bars (MIN_DATA_HOLE_BARS) is treated as the same hole; warmup and a not-yet-evaluated tail never break. The gap set is remembered per pane+series so a tip-only live apply keeps the breaks until a run reports a healed axis. Pinned bytests/line-break-primitive.test.ts,tests/run-helpers.test.ts,tests/runner-engine-owned-apply.test.tsand threepane-managercases (attach / heal / script-na spanning). - Chart screenshot no longer repeats the chart once per pane. In the v1 layout every logical pane is a Lightweight Charts pane on one chart, so
chart.takeScreenshot()returns the whole stack no matter which pane's facade calls it. The capture looped over panes and stacked the results, so a 4-pane chart (price + volume + two indicators) produced a PNG with the entire chart repeated four times. The capture now takes one chart-level shot and crops with the pane geometry (PaneManager.screenshotPlan):panes/workspacekeep the stack,pricekeeps the price pane and re-attaches the shared time axis below it, and the drawing SVG layers are composited at their own pane offsets. Pinned bytests/screenshot.test.ts(onetakeScreenshotcall for four panes +screenshotCropgeometry). - A shorter repaint no longer leaves a phantom empty right margin (price + indicators "shifted left"). A full-window paint with
fit: falserestores the pre-paint logical range so a live slide does not jump the viewport — but when the new window is shorter (anchored-window reset, a smaller engine/DSM window, a left-edge page the window cap sliced away) the old indices point past the new tip. The viewport then keeps extending into whitespace, the candles stop mid-pane and every indicator looks displaced. The restored range is now rebased onto the new candle axis (rebaseLogicalRangeinchart/v1/invariants): it shifts by the bars dropped from the head and its right edge is clamped to the new tip + the chart's ownrightOffset. The left-edge prefetch also paints the window-enforced view and shifts the viewport by the bars actually added. Pinned bytests/chart-time-axis.test.tsandtests/manager-access.test.ts. - Indicator panes no longer collapse to a sliver under the price pane. LWC distributes a chart's height by stretch factor —
setHeight(h)sets that factor — while the store mixed rendered pixel heights (persisted after a separator drag) with nominal defaults (140 for a new indicator). A price pane persisted at its rendered height (~800) dwarfed a pane created later with 140, and a pane persisted while squeezed (25) stayed a 13px sliver forever. Pane heights are now re-derived from the stored pixel targets on mount and on every resize: sub-panes render at their stored height with a readable 48px floor, the price pane takes the remainder, and a squeezed sliver is never persisted back.
Added
- All-data AXB1 (
axb1-full) is now the default.axis.engine.transport=workersendsencoding: 'axb1-full'by default, so the engine pushes script snapshots/deltas (SNAPSHOT_PLOTS/PLOT_TIP) and multi-bar candle deltas (APPEND_BARS, opcode0x0b) over AXB1 in addition to the existing OHLCV binary path.localStorage.setItem('axis.engine.encoding', 'axb1')(orsetEngineEncoding('axb1')) restores the legacy OHLCV + single-bar-tip-only behavior. The ingest decoder mapsAPPEND_BARSback to onedeltaplus onecloseframe; JSON fallback is still honored per subscription for any shape the engine cannot represent losslessly. The encoder, decoder, fuzz, codec round-trip, and the shared PYNE golden-vector suite all pin the new layout.
Changed
- Status bar is a scanline, not a hover HUD. The connection HUD no longer opens an info panel on pointer-enter (the hover-bridge, pin, and floating overlay are gone). Live readings stay in the strip; click or keyboard on a chip opens a
role=dialoginspector above the rail via the shared Popover (Esc / outside / second click). Compose uses the same placement. Pinned bytests/hud-inspector.test.tsande2e/statusbar.spec.ts. - Theme chrome — roll back window/panel/border contrast pass. Reverts
PANEL_LIFT/ELEV_LIFT/EDGE_MINforced lifts inbuildChromeCssVars, restores quieter default--color-border/--color-border-softand panel/elev surface tokens, and puts translucent float-panel / card fills back. Input and button control styling (including the 10px font floor on control chrome) stays. Dropped thechrome contrast floorssuite that encoded the reverted policy. - Data page defaults to the Datasets tab. Studio Data opens on cached series inventory; Backfill jobs is second. Composer open jumps to the jobs tab.
- Engine — keepalive heartbeat for the shared
/ws/runsocket. Theengine-wsclient reuses one socket per engine endpoint but never pinged it, so behind Cloudflare®'s ~100s WebSocket idle timeout (now in play sincepynescript.onlineis proxied) an idle eval socket got torn down and the next run fell back to REST for the dead cool-down. The client now sends the Pro API's{type:"ping"}keepalive (server answers{type:"pong"}) every 25s while the socket is open and idle, never mid-run, and tears the timer down on close. Newtests/engine-ws.test.tscases (pings an idle open socket,does not ping while a run is in flight,stops the heartbeat after close). - Results — commission model on the strategy report. The engine nets commission into its own
strategy.closedtrades.profit, but it never ships the fee to the host, so the host re-derived P&L from raw prices and silently reported gross results. The Strategy tab now has a Commission control (%of notional / cash per order / cash per contract) that charges both legs of every trade, persisted onstore.strategyUi.commission. Fees flow into net profit, win rate, profit factor, and the equity curve; the stats grid gains Commission and Gross P&L tiles, and the trades CSV gains acommissioncolumn. Partial exits bill only their share of the entry fee (pyramiding accumulates it), so a position is never charged twice. Newtests/strategy-commission.test.ts;e2e/results-overlay.spec.tscovers the toggle round-trip. - Results — Max DD reported in money, not a bogus percent.
stats.maxDDdivided peak-to-trough bymax(1, |peak| + 1), a base with no relation to capital, so a small peak against a large trough produced figures like216.00%. The report now also returnsmaxDrawdown(peak-to-trough in currency) and the UI shows that; the legacy ratio is clamped to[0, 1]and documented as relative only. - Script Settings (Inputs) — search, collapsible groups, and modified-state chrome. The modal now opens with a filter input (
/focuses it,Escclears) that narrows every Pinegroup=section by label, id, or type, plus a copy button that snapshots all inputs asLabel = valuelines and aN inputs · M changedsummary (values differing from the Pine declaration default). Groups collapse from their headings with live counts and persist across scripts inlocalStorage(axis.settings.collapsed); groups holding changed values carry an amber rail. Modified fields show an amber dot with a hover ↺ per-field reset to the Pine default, and an empty search result offers a Clear-filter CTA. Newsrc/ui/script-settings/prefs.ts+script-settings.css; covered bytests/script-settings-ui.test.tsande2e/script-settings.spec.ts. - Data — new studio page for the Data Source Manager. DSM now lives on its own studio page (
src/ui/dsm/DsmPage.tsx, left rail Data): a summary row of live counters (active jobs, bars/pages fetched, datasets cached, bars cached + storage estimate, issues note), a Backfill jobs tab (collapsible composer, filter chips, search, pause/resume/cancel-all, retry failed, clear finished) and a Datasets tab (search/source/interval filters, sort by updated/bars/symbol/span, per-dataset load-window, coverage map, load-to-chart, export CSV, backfill-to-now, delete, storage total). The Topbar Data button and the StatusBar dataset chip open the page; the floatable panel and cached-datasets modal stay functional. Registered as a primary rail page (axis-data-page); covered bytests/studio-pages.test.tsand updatede2e/datasets.spec.ts/e2e/datasource-panel.spec.ts. - Plugins page — single catalog with inline install. The Script Library tab is gone (the docked topbar Library panel owns that) and the Install tab merged inline: "Install from URL" (URL field + Load + same-origin examples) and the "Installed URLs" list sit on the catalog page. Slot cardinality is now explicit — Sources / Streams / Engines / Storage carry a Single slot · one active badge (Use ↔ Active rows), Components carry a Multi-slot · many badge and show a Registered chip instead of an activate button. Docs and
e2e/library.spec.tsupdated to the new structure. - Settings — Debug section removed from General; per-row input language. The General-tab "Debug logs" section is gone (the Debug studio page owns logging end-to-end; its test ids
axis-settings-debug*were dropped with it). Settings inputs now follow the Studio input language: unit suffixes ride in the field (bars,s,ms,%,ticks,×), numeric rows get leading icons andselectOnFocus, the Worker URL iscopyable. Same treatment rolled into Alerts composer (%,ssuffixes, copyable webhooks), Script settings (Pine inputs with steppers + mono, strategy properties with unit suffixes), and Notifications. - Studio secondary buttons read as interactive. The flat secondary voice is gone:
ax-btn--ghostand the AppButtonaxd-softdesign now carry a visible hairline border + quiet tint at rest, brighten edge + lift 1px on hover, sink on press, and show a focus-visible halo — used by "Export workspace", "Download log", "Test", and every ghost row action. Primary/danger identities unchanged; Storybook contract docs updated. - Panel chrome — contrast, typography, and UI scale that reaches the main app. Shared border tokens were lifted (
--color-border#1c2230 → #262d3d,--color-border-softaccordingly) so the recently redesigned panels, cards, modals, and floatable shells read against the void background; in-panel typography (sections, hints, kickers, rows) gained ~0.5px across studio.css; and the density tokens (--ui-gap,--ui-control-h,--ui-label,--ui-title,--ui-panel-pad,--ui-topbar-min-h,--ui-statusbar-min-h) plus ~70 chrome font sizes are nowcalc(px * var(--ui-scale))— the Settings scale option now visibly scales the topbar, statusbar, watchlist, HUD, modals, and panels, not only studio pages, with 1.0 identical to before. - Price & Time panels — copy price, live state, and candle countdown. The price panel leads with symbol · venue · stream state and the big price doubles as a copy button (
copiedflash); the time panel adds a candle-progress bar, the close-wall time, and a countdown that tints amber under a minute and red under ten seconds (barProgress/barCloseAtMsinsrc/ui/extras/timebadge.ts; covered bytests/extras-timebadge.test.ts). - Quote panel — hero price, 24h range bar, and OHLC tiles. The quote sheet now leads with a large trend-tinted price, a 24h change pill, the symbol/venue, and the live stream state, plus a 24h range bar that marks where the current price sits between the day low and high. Day open and 24h volume are stat tiles, the last bar is split into O/H/L/C tiles, and tick time / venue stay as compact meta rows. The copy button copies
SYMBOL price change.quoteStatsnow exposes the numbers behind the hero and range (sharing the row builder’s math); covered bytests/extras-quote.test.tsande2e/quote-panel.spec.ts. Newsrc/ui/quote/quote.css. - On-chain panel — mode counts, data-path chip, and richer series/event rows. The header now shows attached-series and event counts with a worker / direct proxy chip, and the TVL / DEX / Events tabs carry live counts. Attached-series rows gained a color swatch, provider · points · latest-TVL subtitle, and a compact eye/detach action pair; DEX result rows and search fields share the panel’s control styling. Event rows are tone-railed green/red for gains/drops and keep the date, severity, and TVL metadata. Jobs use the same status rails as the Data Source Manager. New
src/ui/onchain/onchain.css. - Data Source Manager — collapsible composer, date presets, and job-card polish. The new-job form now folds away once jobs exist (with a
+ New jobheader toggle) and gains 30d / 90d / 6m / 1y / 2y target-date presets plus a Chart reset that restores the active symbol/source/interval. The header summarizes active/done/issue counts and total bars/pages fetched. Estimate values are stat tiles that turn amber when a job cap trims the window; job cards get tone-coded rails/dots, a phase-colored progress bar, compact meta/window lines, and a Clear N action for finished jobs. Job search gains/focus andEscreset.CachedDatasetsModalnow mounts once at the app root instead of inside Topbar / StatusBar / Data Sources, fixing a duplicate-dialog strict-mode failure. Newsrc/ui/dsm/dsm.css; covered bytests/dsm-ui.test.tsande2e/datasource-panel.spec.ts. - Data Window — pin/step inspection, value search, copy, and collapsible groups. The panel header now shows symbol · interval and a UTC timestamp with bar index, plus pin, prev/next, and copy all (TSV) controls. Pinning freezes the bar when the pointer leaves the chart (the last hovered index is remembered), and stepping walks bars from the pin or the last hovered bar. The empty state offers Inspect last bar. OHLCV gained a Change row (absolute + % vs the previous close, toned up/down), every row copies from a hover icon, groups collapse with live counts, and a
/-focus filter narrows all groups by label or value. Newsrc/ui/dataview/dataview.css; covered bye2e/dataview-panel.spec.ts. - Alerts panel — collapsible composer, search, edit/duplicate, and bulk actions. The composer now folds away once alerts exist (and opens automatically on an empty book), with Delivery & cooldown behind a disclosure that summarizes
browser · webhook · L2 · cooldown. The header adds a/-focus search across name/symbol/kind/condition, a notification-permission bell, a+ Newtoggle, and a summary (N alerts · N armed · N fired · N off). Rows gained tone rails (armed / recently fired / off), kind-colored chips, interval + delivery glyphs, and relative last-fired labels (2m ago, backed by the newformatLastFiredRelative). Each row’s···menu offers Edit · Duplicate · Copy condition · Test webhook · Delete; editing reuses the composer and preserves the alert’s enabled state. Filter chips show live counts and the list menu enables/disables/deletes all. Newsrc/ui/alerts/alerts.css; covered bytests/alerts-format.test.tsande2e/alerts-panel.spec.ts. - Layers panel — one search, collapsible sections, and drawing management. The panel now opens with a
/-focus search that filters every section (scripts, panes, on-chain series, drawings, template names) plus a hidden-only toggle and a header summary (N scripts · N panes · N drawings · N hidden). Sections (Scripts, Panes, Overlays, On-chain, Drawings) collapse from their heading and persist inlocalStorage(axis.layers.collapsed); Scripts and Drawings headers carry show-all/hide-all controls. Rows gained kind glyphs, color swatches, hidden dimming, badges (est.,locked,hidden), and hover-revealed actions; the price pane stays visibility-locked. Drawing rows are now select-on-chart, hideable, lockable (padlock), and removable, and the four symbol actions moved into a···menu (Duplicate · Keep this symbol · Tag symbol · Clear) with the search box removed in favor of the panel-wide filter. Saved template packs got a compact header (save/import/export icons) and Load/Merge rows. Newsrc/ui/layers/prefs.ts+layers.css; covered bytests/layers-ui.test.tsande2e/layers-panel.spec.ts. - Scripts panel — search, filters, bulk actions, card menu, and plot disclosure. The applied-scripts panel now opens with a
/-focus search, kind/status filters (All · Ind · Strat · Err · Hidden), a compact summary (N scripts · ok · err · plots), and a bulk menu (Show all · Hide all · Remove all). Cards gained a kind-colored rail, a clickable run-status dot (opens Results), and a···row with Copy Pine, Move up/down, Results, and Remove. Studies with more than two plots start with the plot block collapsed, showing color swatches in the header; expanding restores the width stepper, line type,display.*, and per-plot visibility. The badge legend moved behind a?popover, and the built-in picker now uses the shared compact rows. NewmoveIndicatorstore action;scripts.cssowns the design. Covered bytests/store.test.tsande2e/scripts-panel.spec.ts. - Watchlist folders + full list/symbol management. The watchlist panel now has a manager drawer behind the current-list button: folders (
watchlist.folders) group lists, with create / rename / reorder / collapse (persisted) / delete — deleting a folder returns its lists to Ungrouped, never deletes them. Every list gets a···strip with rename, duplicate, move up/down, move-to-folder, and delete; the footer sorts the active list A–Z or clears it. Every symbol row gets a···strip with move up/down, move to another list, copy symbol, and remove, while alerts and the big-price view stay one click away. Toolbar shows the folder breadcrumb + symbol count; empty lists get a browse-symbols CTA. New store actions (createWatchlistFolder,renameWatchlistFolder,deleteWatchlistFolder,moveWatchlistToFolder,moveWatchlist,moveWatchlistFolder,setWatchlistFolderCollapsed,moveWatchlistSymbol,moveWatchlistSymbolToList,sortWatchlistSymbols,clearWatchlistSymbols) hydrate backward-compatibly from pre-folder blobs. Covered bytests/store.test.tsande2e/watchlist-manager.spec.ts. - Script Library panel redesign — command bar, pinned/recent scripts, and a compact personal workspace. The docked panel now opens with a sticky search + segmented Builtin/Personal tabs (
/focuses search,Escclears, matches are highlighted). Built-ins can be pinned into a Pinned group and reapplied from a Recent strip; category headers collapse with live counts, and cards show a kind-colored rail,on chartstate, and icon actions (Apply / Edit / Pin). Personal scripts get Recent / Name / Storage sorting, kind chips, pinned-first ordering, anin editordot, and a ··· row for Copy Pine / Duplicate / Delete. The save composer is one compact row, prefills the name from the editor'sindicator()/strategy()/library()title, and moves description + Publish behind a disclosure; import/export are icon actions next to the storage picker, and empty states carry Save / Import CTAs. New helpers:src/ui/library/prefs.ts(localStorage pins + recents),format.ts(suggestScriptName,splitHighlight,pinnedFirst, name sort), andlibrary.css. Covered bytests/library-ui.test.tsande2e/library-panel.spec.ts.
[2.28.0] — 2026-11-04
Added
- Results overlay — event filters, plot stats, and saved-run management. The Events tab gains a filter bar: a text search over kind/direction/id (
Escresets without closing the overlay), All / Entries / Exits and Both / long / short chips with live counts, and a header that readsN / M events · K positionswhile filtered. Plot cards gained a min / max / Δ% stat strip and now break the sparkline onnagaps instead of drawing through them. The Saved tab gains a sort select (Newest / Oldest / Net P&L / Win % / Trades), a run search (label, kind, symbol, timeframe), per-row checkboxes with select-all, and a bulk Delete N; savedMax DDno longer renders−0.00%. New pure helpers insrc/results/overlay.ts(filterEvents,countEvents,sortSavedRuns,summarizeSeries,sparklinePath),src/ui/results/results.css,tests/results-overlay.test.ts, ande2e/results-overlay.spec.ts. - Debug logging is a UI switch, plus a Studio Debug page. Chart/engine logs (
[axis:chart]/[axis:engine]+ the rotatingaxis.debug.log.*ring) no longer needVITE_AXIS_DEBUGand a restart: Settings → General → Debug logs turns them on immediately (localStorage.axis.debug), shows the ring size/lines, and can download or clear it. The new Debug page on the studio rail (Catalog → Debug, orOpen Debug Logsin ⌘K) adds a formatted live view — scope chips (chart / engine), text filter, pause, follow-tail, copy, download, and clear — with the env flag reported as Forced by .env instead of pretending the toggle can silence it.window.__AXIS_DEBUG__gains.set(on)and.enabled(). - Studio input system v2 + Storybook
Studio/Inputs. Every studio control now carries state in its chrome:StudioInputgains a shell with leading icon, prefix/suffix, clear, password reveal, copy, character counter, async status lane (spinner / check / alert), numeric stepper,⌘hint, Enter/Escape hooks, and the same focus halo as buttons.StudioFieldadds optional/required tags, a label-row counter, success messages, andaria-describedbywiring through a field context.StudioSelectkeeps the native list but gains a currentColor chevron and identical focus/invalid edges.StudioTogglegets sm/md sizes and a spring thumb;StudioColorInputdetects the format (HEX/RGB/RGBA/PINE/NAME), marks unparsable drafts, and supports Enter commit / Escape revert. NewStudioRange(filled track from one custom property, value chip,aria-valuetext) andStudioTextarea(autosize, counter) complete the set. Ten new stories cover anatomy, states, smart actions, validation, select/toggle, color, range/textarea, keyboard/a11y, a live playground, and the contract. - Watchlist: big price view + alert mini-modal. The
!row action expands a real big view: price, daily change, Prev close, spark, a 24h range bar (low → high with the last price marked) and 24h volume (base · quote). Tickers now carryhigh24h/low24h/volume24h/quoteVolume24hfrom Binance REST + WS, OKX, Bybit, MEXC, and the mock walk. The alert popover gained the last-price chip,Last/+1%/−1%quick fills, a non-blocking “already above/below — fires on the next tick” hint, an explicit disabled state for invalid input, and a link to manage existing alerts on that symbol. - Scripts panel: plot line type + Pine
display.*targets. Each plot row gains a line-type picker (solid / dotted / dashed / large dash) and adisplaycontrol.displayis the real Pineplot(..., display=)mask — the panel shows what the script declared (Pine: all), lets you override pane / Data Window / price-scale targets per plot, and resets back to the declaration; the override drives the chart apply, the Data Window, and the price-scale label without a re-run. Line type is explicitly an AXIS platform override (Pineplot()has no line-style argument), stored beside the script's own declaration and applied live viasetOverlayLineStyle. Both survive re-runs (plots[name].linestyle/.display,plots[name].pinekeeps the declaration). Helpers insrc/indicators/plot-prefs.ts; covered bytests/plot-prefs.test.ts,tests/store.test.ts, ande2e/scripts-plot-prefs.spec.ts. - Library: applied + placement chips. Builtin cards now say whether the study plots
overlayor in its ownpane, and mark scripts already on the chart with anappliedchip (Apply re-runs them). - Multi-chart grids 6 (3×2) and 8 (4×2). Layout menu, command palette, Alt-6 / Alt-8, and glyph cells. Growing the grid hydrates empty sibling slots without clobbering the focused OHLCV plane. While Live is on, up to seven secondary venue streams tick inactive markets into their slot caches.
- Footprint model + binary row pack (A1.3 foundation).
FootprintBar/FootprintRowaligned with Pine v6 / PYNE shapes, OHLCV approximation helper (source: 'approx'), Float32 pack/unpack for delta transport, and pure paint-cell geometry. - Footprint overlay UI (A1.3.2). Layers → Footprint paints per-candle buy/sell cells on the price pane from the OHLCV estimate (visible bars, capped). Badge marks
OHLCV est.until a real feed (P1.1), plus the densest row count and the last bar's delta arrow. Rows are sized to each candle's on-screen pixel height (numTicksForPixelHeight) and capped on narrow bars, so cells stay inside the bar instead of spilling over neighbours; each cell mutes the candle underneath and leaves a hairline row gap so the split reads even when the cell hue matches the bar. The buy/sell split follows where the candle closed inside its range (a red candle closing near its high still reads as buying) rather than colour alone. Pan/zoom repaints immediately via the time-scale visible-range subscription; a per-bar layout cache keeps keyed<For>from rebuilding every cell on each 400 ms poll. Covered bytests/footprint.test.tsand a Playwright smoke (e2e/footprint.spec.ts).
Fixed
- Failed engine
/healthno longer disables engine-owned live.getEngineCapabilities()cached whatever the engine resolved, andserverEngine.getCapabilities()returned the default (non-data_session) capabilities when the/healthprobe failed. A transient probe failure therefore overwrote the last known-good cache, andisEngineOwnedLive()flipped a live chart from engine-owned paint to the calculator path (re-enabling local dataset writes) until the next successful probe. The probe now rejects on failure sogetEngineCapabilities()keeps the cache while callers still get the documented fallback. Covered bytests/engine-capabilities-cache.test.ts; the 8multiplex-engine-owned-painttests that had been failing pass again. - Data Manager module could crash the module graph on load.
data/data-manager-sourceimported the store, which closed the cycledata-manager-source → store → storage/service → plugins/bootstrap → sources/catalog → expand-cache → data-manager-source.expand-cachereadsDATA_MANAGER_SOURCE_IDwhile it evaluates, so any graph that loaded the data-manager module first threwReferenceError: Cannot access 'DATA_MANAGER_SOURCE_ID' before initialization(16 test files failed to load, and a lazy chunk could fail the same way). The id now lives in a leafdata/source-idsmodule,expand-cachereads it from there, andresolveDataManagerVenueSource(streamId)takes the live stream id instead of reading the store — the data-manager module has no store dependency. Covered bytests/module-init-order.test.ts. - Watchlist
Prevwas a rolling 24h open, not a reference price. The column (and theChg% when available) now uses the previous daily close (UTC 00:00), fetched once per symbol per day from daily klines (data/watchlist-daily.ts) and cached for the day — so it no longer changes with every tick. Venues without daily klines (OKX/Bybit/MEXC/mock) fall back to the 24h open and the tooltip says which reference is in use. Row actions also no longer fight the quote columns for width: the alert / big-view / remove cluster overlays the row edge on hover (with a gradient backdrop, always visible on touch) and the grid is four columns again. - Scripts panel line-width “+” stepped by two. An unset width was labelled
1but the stepper read it as2, so the first click jumped 1 → 3. Width now resolves througheffectivePlotWidth(panel override → Pinelinewidth→ 1), so+/−move one step from the number shown. - Strategy events, debug pins, and Pine drawings survive the rolling window.
bar_indexis a dataset coordinate (Pine: 0 = first bar of the evaluated dataset), but AXIS re-evaluates the newestrollingWindowBarson every candle, so the head drops a bar per tick and the same index names a different bar on the next run. Every run now stamps its evaluated window (meta.windowT0/windowSpan/windowCount/windowUniform,src/results/window-axis.ts) — single runs,runBatchentries, and streamfullpayloads (bar_timeswhen shipped, else the chart window) — and every consumer resolves an index against its own run's axis instead of the live bar array: index-only events are converted to absolute time at ingestion (normalizeEventsArray; previously they were dropped), strategy fills / PnL / trade markers (signalBarIndex), debug pins (resolveDebugPinTarget,pinsFromLastRunauto-derives the axis from the payload), and Pineline/box/label/polylineanchors withxloc.bar_index(converted to absolute times at ingestion — thexloc.bar_timelesson). Events and drawings whose bar already slid out of the window are skipped rather than mis-placed, and off-axis pin times no longer reach LWC markers (a single unknown time cleared the whole marker plugin). Legacy payloads without a stamp keep the previous index behavior. - Data Manager empty-cache loads auto-start DSM. With chart source
data-manager, a symbol that has no cached dataset (e.g. ETHUSDT after BTC) no longer stops on “No cached dataset…”. AXIS resolves the venue from Dataset selection → live stream pair (binance-ws→binance-rest) → Binance REST, starts a background backfill to Settings → Historical bars, paints once the rolling window is covered, and keeps filling in the background. Mid-jobapplyWhenCompletepaints at the rolling-window threshold; progressive DSM repaints continue afterward. - Period-anchored loads reach the chart.
loadBarsnow receives the full fetched / cached dataset and returns the painted window, so the TradingView®-style anchor (week start for intraday, month for daily, year for weekly/monthly) is computed from real history instead of an already-truncated slice — previously every load path pre-sliced tochartWindowLimit(), so the anchor silently fell back to the base Rolling window.paintDataset, the direct venue fetch, the engine read, andapplyCachedToChartpaint the returned view; an explicitmaxBarswindow (dataset modal / status-bar edit) still wins. Covered by the newpaintDatasetcases intests/anchored-window.test.ts. - Inactive multi-chart slots no longer move the focused chart window.
applyBarToSeries(the inactive-slot live path) calledadvanceChartWindowForAppend(), which mutates the module-level window plan: with a 6/8 grid and Live on, a sibling slot's tick could grow or reset the focused chart's window, skip its ceiling reset (growing toceiling + 1bars), or drop the period anchor. Slots now read the current limit and slide at it; the grow/reset cycle belongs to the active chart only, and focusing a slot reloads it anyway. Covered by a cross-test intests/slot-live-append.test.ts. - Data Window honours the panel
display.*override.DataViewPanelmerged the override by assigning intolastRun.meta.plot_meta, which is a read-only Solid store proxy — the write was silently dropped, so unchecking Data window on a plot never removed it from the Data Window. The merge is now a puremergePlotDisplayOverrides()copy (src/results/dataview.ts) with the same precedence as the chart apply path (panel → script →display.all). Covered bytests/dataview-display.test.ts. - Plot display toggles respect the eye toggle.
setOverlayLineDisplaywrotevisiblefrom thedisplay.panebit alone, the same option the eye toggle uses, so changing any display target (e.g. Price scale) re-showed a plot the user had hidden. The pane bit can now only hide;IndicatorCardpasses the storedplots[name].visible.setOverlayLineStylealso resolves the series kind from the key it actually applies, so the histogram/marker guard works for owner-scoped series. Covered intests/pane-manager.test.ts. - Polyline
xloc.bar_indexanchors no longer slide.normalizeScriptDrawingsconverted line / box / label anchors through the run axis but the polyline branch calledalignDrawingTimeToBarsdirectly, so compile-modepolyline.newvertices (carried asx/index) kept window-relative numbers and moved with the chart window. Vertices now use the samealign()conversion. Covered intests/window-axis.test.ts. - Declared plot style no longer goes stale. A re-run merged
plots[name].pineonly when the new payload declared a style, so editing a script to dropdisplay=/linewidth=left the old declaration in place (the panel kept showing the previousPine: …and Reset restored it). A run that declares nothing now clears the key; panel overrides (display,linestyle,width,visible) survive. Covered bytests/plot-pine-meta.test.ts. - Footprint cells update on a volume-only tick. The per-bar layout cache keyed on bar time + geometry only, so the forming candle's cells and delta badge stayed frozen until the price moved a full pixel.
footprintLayoutKeynow includes OHLCV (the buy/sell split follows the print, row volumes followvolume); closed bars still reuse their layout. Covered intests/footprint.test.ts. - Watchlist daily closes follow the venue. The previous-daily-close map was keyed by symbol only and never cleared, so switching the data source (e.g. Binance → OKX) kept showing the old venue's
Prev. The panel now tracks the resolved quote venue (dailyCloseSourceKey) and clears/reloads on change. A symbol the venue has no daily kline for is negatively cached for the UTC day instead of being re-requested every 60 s. Covered intests/watchlist-daily.test.ts. - Run cache invalidates on live appends. The key used the bar-plane generation plus the engine bar count, but anchored appends no longer bump the generation and the engine depth is chunked in 100-bar steps — so an interactive Run could be served a result computed before the latest bar (bounded by the 30 s TTL). The evaluated window's first / last bar time are now part of the key. Live ticks still bypass the cache. Covered by
tests/run-cache.test.ts. - Numeric stepper fires callbacks once.
StudioInput.bump()calledstepUp()/stepDown()(which dispatch nativeinput+change) and then invokedonInput/onChangeagain, so every stepper click ran the handler twice. The step is now astepNativeInput()helper and the props are forwarded by the native handlers only. Covered bytests/studio-input.test.ts.
Changed
-
AXIS IDE extracted to
hoox-sh/axis-ide-vsce.packages/vscode-axis-ide,packages/vscode-axis-pack, the VS Code CI workflow, and the rootvscode:*aliases moved out of this repo with their commit history preserved. The standalone repository now owns the extension, the AXIS Trading Pack, its CI/release workflows, and Marketplace / Open VSX publishing. -
Anchored live window (TV-like
bar_indexstability). Live charts no longer slide the dataset head on every candle. The chart anchors at load to the TradingView®-style period boundary — week start for intraday, month start for daily, year start for weekly/monthly (UTC) — grows from the base Rolling window up to +20% (hard ceiling 5000, the engine free-tierFREE_MAX_BARS), then resets to the period boundary when it fits inside the cap, otherwise to the base window. The first strategy event,varinitialization, andbar_indexstay on the same bar for the whole grow cycle; the reset is the only point where indices shift. The engine window is a 100-bar-chunked depth so live script subscriptions resubscribe on chunk growth/reset, not every tick;applyRollingWindow,scriptHistoryBars, and engine series drive read the same plan; multi-chart slot appends only read its limit (they never advance it). Newwindow.grow/window.resetdebug events expose the transitions (reset includes the period-alignedanchor); covered bytests/anchored-window.test.ts. -
Clearer live debug logs.
wire.script.evalwas renamed towire.script.paintand now reportswindowBars+mode: subscribe|delta, so a 2-second line means “paint the cached delta onto the chart window”, not “re-evaluate 4,500 bars”. The actual engine subscription is full on subscribe and delta frames afterwards (wire.batch/wire.series.delta);live.rerunOn: every-tickcontrols the venue tick cadence, whilebar-closeevaluates once per closed candle. -
Studio inputs migrated to the v2 primitives. Extras ticker/price-card sliders, motion-effect parameters, Settings → UI scale, plugin config rows, the Workers → PYNE Agent endpoint/API key, and the Results → Raw filter now use
StudioRange/StudioInputinstead of rawax-range/ax-inputmarkup, so they inherit the filled track, clear/reveal/copy actions, and the shared invalid/focus language. -
Theme refine (brand packs + shortlist). Chart presets lead with AXIS Void (
void-dark, aliasaxis), PYNE Volt (volt, aliaspyne), and HOOX Signal (signal, aliashoox) matching the pynescript VS Code themes. Community catalog culled to Ayu / Tokyo Night / Nord / One Dark / Rosé Pine with mutedscale.borderedges. Chrome softens loud/accent borders; brand syntax packs drive editor CSS vars; builtin candle packs appear in the Theme library beside saved bar themes. -
Studio Settings polish. Shared studio primitives (
AppButton+useMicrofeedback, section chips/status, live-apply footers) across General, Data, Editor, Theme, Topbar, Keyboard, and Notifications. Keyboard recorder ignores modifier-only keydowns (Mod-Controlno longer crashes Settings); Restore deletes the override key; scope filters and conflict chips are live. Notifications folds browser-alert permission into Settings alongside toasts/flood control. -
PYNE Agent is an Agent panel. The bottom-right Agent launcher and floating chat are gone. Plugin 0.1.9 fills the Agent dock panel. The module bar has an AI button next to the other panel toggles (Settings → Topbar can hide it). Command palette “Toggle Agent”, Extras, and mobile Panels still open it. Closing the panel aborts the chat request.
-
Equity curve market colors. The zero line uses
--color-border. The curve, fill, and drawdown use--color-upand--color-down. Axis labels use--font-mono. -
VS Code AXIS IDE Plugins WebviewView (
packages/vscode-axis-ide). Replaces split plugin TreeViews (axis.plugins.{source,stream,engine,storage,dataset,component}) with a single detailed Plugins WebviewView (axis.plugins,type: webview,retainContextWhenHidden: true) using VS Code theme tokens (not webview-ui-toolkit). Activity order: library → layers → indicators → watchlist → alerts → plugins → workers. Commandaxis.plugins.activateremains (palettewhen: false). -
Shell chrome hairlines and market color aliases. Command-bar groups use a short centered rule instead of a full-height divider. Vertical resize handles keep a 1px resting mark.
--color-up,--color-down,--color-warning, and--color-infofollow the theme greens, reds, amber, and cyan (including light).--font-sans/--font-mononame the chrome and figure stacks. Command palette rows put the category beside the title, with a 2px accent bar on the active row. -
PYNE Agent plugin 0.1.8 vendored at
/plugins/axis-pine-agent.js(sync fromhoox-sh/pyne-agent-worker). Always sendsorchestrate: trueand a namedpyne_backendkind; Workers Manager → PYNE Agent gains a PYNE backend select (auto/pro/local/worker/container/none). Install/sync helper:bun run sync:pyne-agent-plugin(scripts/sync-pyne-agent-plugin.sh).
Added
-
Chart Generation Readiness (PR-A–D). Symbol/interval/source loads claim a generation and show the full-chart
HooxLoadersplash until OHLCV is painted and visible scripts have been reapplied. Splash caption showsk/nscripts as each id settles. Binding-engine drops old data/live sessions and/ws/livebefore scripts; live venue/engine paints stay gated until reveal arms generation G. MCPchart.getandchart.lagexpose areadinessstrip (generation,phase, script settle,livePaintAllowed). Modules:src/chart/readiness.ts,src/chart/engine-bind.ts. -
VS Code AXIS IDE sidebar studios, profiler gutter, panel polish, Copilot tools, HPO removal (
packages/vscode-axis-ide). WebviewViews for Library/Layers/Scripts/Watchlist/Alerts/Workers; profiler percent chips beside Pine line numbers and end-of-line debug chips; Colors/Symbols/Logs bottom tabs; richer Copilot LM tools (#axisRun/#axisResults/#axisChart/#axisLogs/#axisProfiler); Layers live-doc bus for unsaved drawings; drop HPO builtin and PWAsrc/optimize/HpoPanel. -
VS Code AXIS IDE view polish + Profiler panel (
packages/vscode-axis-ide). View polish (icons, tooltips, micro-feedback); Profiler panelaxis.profiler. -
VS Code AXIS IDE PYNE marketplace icon, view context menus, on-save run (
packages/vscode-axis-ide).media/icon.pngmatcheshoox-sh.pyne. Right-click menus on editor/explorer/tree views/plugins webview. Settingaxis.run.onSave:off|evaluate|run. -
VS Code AXIS IDE chart beside Pine, no TV logo, script plots overlay (
packages/vscode-axis-ide).axis.openChart/ Run openaxis.chartinViewColumn.Beside(preview off). Lightweight ChartsattributionLogooff.chart.full.plots/seriespaint as line overlays. -
VS Code AXIS IDE plugins studio polish (
packages/vscode-axis-ide). Plugins studio polish (search, chips, copy id); status-bar plugin menu; chart HUD follows active source/stream/engine/storage. -
VS Code AXIS IDE per-kind plugin views + HOOX activity icon (
packages/vscode-axis-ide). Activity Bar replaces lumpedaxis.pluginswithaxis.plugins.{source,stream,engine,storage,dataset,component}(thenaxis.workers); commandaxis.plugins.activate(tree context; hidden from palette); HOOX monochrome mark atmedia/axis-activity.svg. Persist keyaxis.plugins.v1unchanged. -
VS Code AXIS IDE MCP + LM tools, extension pack, CI, NOTICE/PUBLISH, Pyodide flag (Tasks 21–27) (
packages/vscode-axis-ide+packages/vscode-axis-pack). MCP stdio provideraxis.mcpsoft-fails unlessaxis.mcp.command/ PATHaxis/axis.mcp.allowNpx;axis.mcp.showConfigsnippet; SecretStorageAXIS_API_KEYenv only (never--key). Language model toolsaxis_run_script(prepareInvocation confirm) /axis_get_results_focus/axis_chart_snapshot_metareturn summaries only. Packhoox-sh.axis(extensionPack: hoox-sh.pyne + hoox-sh.axis-ide); root scriptvscode:pack:package. GHA.github/workflows/vscode-axis-ide.yml(coverage gate + electron + VSIX artifact). NOTICE (LWC Apache-2.0 + fancy-canvas MIT), PUBLISH.md (vsce/ovsx dry-run; IDE then pack). Optionalaxis.engine.allowPyodide(default false) + chart CSP widen stub (wasm-unsafe-eval/worker-src blob:) locked to Pyodide 0.29.5 (Wasm assets not shipped). -
VS Code AXIS IDE MVP drawings, alerts, watchlist+quote (Tasks 18–20) (
packages/vscode-axis-ide). Chart webview Trendline tool postsdrawing.setTool/drawing.commit; host appends into*.axis.jsondrawings[](content-change dirty; save/reload viachart.init). Activity Bar TreeViewsaxis.alerts(workspaceStateaxis.alerts.v1add/remove) and first-timeaxis.watchlist(axis.watchlist.v1+ StatusBaraxis.watchlist.quotevia puredisplayedLastPricemock feed until PR-09). Palette/commandaxis.chart.drawing.trendlinedocuments the in-chart tool. -
VS Code AXIS IDE builtins apply, live tickets, chart.delta resume stub, plugins/workers (Tasks 14–17) (
packages/vscode-axis-ide).axis.applyBuiltinQuickPick opens untitled pinescript from offlinemedia/builtins/catalog.json(111 AXIS builtins). Live path mintsGET /api/ws/live?issue=ticketwith SecretStoragepn_Bearer, upgrades WS on?ticket=only, StatusBar HUD live/degraded/off (axis.liveStart/axis.liveStop, settingsaxis.worker.url/axis.live.enabled). Host fan-out applieschart.full/chart.deltawith an MVP seq-gap resume stub. Plugins registry lists builtin engine/source ids;axis.plugins.v1globalState metadata strips secrets;axis.workers.probeAll+ TreeViewsaxis.plugins/axis.workers(noaxis.watchlist). -
VS Code AXIS IDE Results/Strategy panels + library TreeViews (PR-07/08) (
packages/vscode-axis-ide). Panel WebviewViewsaxis.results/axis.strategywith CSP shell andresults.set/results.ready/results.focusoveraxis.webview.v1; normative §7.1 focus store (focus:truepublisheslastRun, silent does not steal focus, first run always publishes,axis.focusResultsretargets). Activity Bar containeraxiswith empty MVP TreeViewsaxis.library/axis.layers/axis.indicators(noaxis.watchlistyet). -
VS Code AXIS IDE HistoryClient + canonical seriesKey (PR-06b) (
packages/vscode-axis-ide). Hostsrc/host/history-client.tsfetchesGET {axis.datafeed.url}/datafeed/ohlcv(defaulthttp://127.0.0.1:5003, loopback-gated like the engine endpoint) with injectablefetch;src/host/series-key.tsbuildsexchange|market|SYMBOL|intervalwith pair-form via a compact majors catalog (BTCUSDT→BTC/USDT). Whenaxis.chart.useMockBarsis false, chart handshake clears then paints fetched bars at seq above handshake (host.error+ OutputChannel on failure — no silent mock); runs fetch the same window and fail clearly instead of POSTing empty data. Settingaxis.datafeed.urlcontributed; mock default stays true until dogfood flips it. -
VS Code AXIS IDE RunOrchestrator +
axis.runScript(mock bars) (packages/vscode-axis-ide). Hostsrc/run/posts ProPOST {axis.engine.endpoint}/runwith SecretStoragepyn_…Bearer (no plaintext settings fallback), monotonic epoch drop for stale completions, OutputChannel AXIS Run start/end/drop, DiagnosticCollectionaxis-run, andchart.fullpaint (mock bars + plots) via a chart notify bus. Commandsaxis.runScript/axis.runScriptSilentwith Ctrl/Cmd+Enter keybinding and editor title menu wheneditorLangId == pinescript. Settingsaxis.engine.id(defaultserver) andaxis.engine.endpoint(defaulthttp://127.0.0.1:5002). In-memory Results focus stub for §7.1. No HistoryClient / venue bars yet.
Fixed
-
VS Code AXIS IDE Run path hardening (PR-06 review). Run
chart.fullseq is seeded above handshake (baseSeq+1) so the webview painter applies; chart-bus filters byseriesKey; diagnostics clear only the run URI;scrubSecretsstripspyn_/pn_/Bearer from OutputChannel/Problems/toasts; engine endpoint defaults to loopback unlessaxis.engine.allowRemoteEndpoint; AbortControllers abort prior/runon supersede and deactivate; runs always force mock bars until HistoryClient; engine id enum isserver-only for this release. -
VS Code AXIS IDE mock bars +
chart.fullpaint +axis.openChart(packages/vscode-axis-ide). Hostchart-session/ deterministic mock OHLCV ({ time, open, high, low, close, volume }, seriesKeyexchange|market|SYMBOL|interval) postschart.initthenchart.fullafterwebview.ready(useMockBarsdefault true; mock-off still posts empty full to clear). New sibling docs copyaxis.chart.historyBars(clamped to 20000). Webview reducer/LWC apply full snapshots.axis.openChartopens or creates sibling*.axis.jsonbeside the active Pine buffer (existing not rewritten) with editor/title menu forpinescript. No venue/fetchHistorical. -
VS Code AXIS IDE
axis.chartCustomEditorProvider + CSP webview shell (packages/vscode-axis-ide). Editable custom editor (not Readonly) for*.axis.json(schema: "axis.chart.v1"), dirty/save/revert/backup stubs viaCustomDocumentContentChangeEvent, Vite chart webview underdist/webview/chart/withwebview.ready→chart.inithandshake overaxis.webview.v1, and CSPdefault-src 'none'/script-src ${webview.cspSource}/connect-src 'none'(no inline script; host proxies network). Fixturefixtures/sample.axis.json. -
VS Code AXIS IDE SecretStorage for Worker/Pro keys (
packages/vscode-axis-ide).src/secrets/façade overExtensionContext.secretswith slotsaxis.secret.cloud.apiKey(pn_) /axis.secret.engine.server.apiKey(pyn_), validators matchingWORKER_API_KEY_RE/^pn_[a-f0-9]{48}$/and Propyn_+ ≥20 chars, redaction helper, commandsaxis.secrets.setWorkerKey/setProKey/clear(InputBox password; noaxis.worker.apiKey/axis.pro.apiKeysettings), and StatusBar set/missing tooltips. Bun unit tests use an injectable Map store. -
VS Code AXIS IDE
axis.webview.v1protocol + coverage ratchet (packages/vscode-axis-ide). Typed envelope codecs for every design §6 chart/results message (encode/ never-throwdecode, direction helpers), thinsrc/host/logger.ts(injectable sink,axis.debug.protocolgate), 100% protocol unit coverage, andscripts/check-coverage.mjswith measured BASELINE 100% (fail on regression), hard protocol 100%, TARGET_HOST 90 / TARGET_PACKAGE 80 printed only. Scripts:test:unit,test:coverage,test:coverage:gate. -
VS Code AXIS IDE scaffold (
packages/vscode-axis-ide, marketplace idhoox-sh.axis-ide0.1.0). Sibling package (not a workspace member) with esbuild host bundle, AGPL-3.0-only headers, P0 commands (axis.showRunOutput, stubaxis.openChart),axis.telemetry.offline, and electron + Bun unit test harness. Root scripts:vscode:ide:compile/vscode:ide:test/vscode:ide:package. Chart custom editor and webviews still land in later work.
Fixed
-
Docked panel double hairlines. Side/bottom shells drop every edge so the dock column owns the shared border; absolute panel-edge resize handles no longer draw a second
::aftermark on that edge. -
Live stuck on calculator / Degraded scripts.
startLivewarms/healthcapabilities solive_streamflips the stream route without waiting for the Scripts panel. When/ws/livealready owns the series subscription, venue ticks no longer dual-writePOST /data/session/.../tick(WS deltas own the tip; REST drive remains freeze recovery only). -
Drawing-tool titles follow the shortcut registry. Rail, flyout, magnet, and delete hints use the real chord, and a cleared override drops the hint. The selection cursor no longer shows Q (that key toggles the crosshair).
-
Live
/ws/liveinbox no longer storms full snapshots. Applied frames skip the inbox (closes used to fill the cap of 32 andresumeevery ~32 bars). Overflow resume is backoff-gated and not cleared by every full. Script sliding fulls paint as tip deltas (same as series). A resume/reconnect full that restarts seq at 1 is accepted, so the series sub and a stickyreplay unavailablechip recover. -
Pyodide numpy SRI failures on axis.hoox.sh.
loadPackage('numpy')for compile/auto fetched/pyodide/v0.29.5/numpy-*.whl, which was never vendored; Cloudflare® Pages SPA-fallback returnedindex.html(200text/html) and Pyodide’s integrity check failed with a different hash every retry. Vendored the lock-matched numpy 2.2.5 wheel (~2.8 MB), extendedscripts/fetch-pyodide.shto pull numpy with micropip, and addedpublic/_redirectsso missing/pyodide/*//vendor/*//assets/*//plugins/*return 404 instead of the app shell.
Added
-
On-chart on-chain legend. Visible attached series show in the chart corner (up to six) with a color swatch and the last value. Hidden series stay off the legend. No extra fetch.
-
Mobile header last price. The phone header shows the painted close and the signed percent versus the previous bar, using
--color-up/--color-down. No extra quote. -
Data window close tone. Close is colored
--color-upor--color-downagainst the previous bar. A flat print and the first bar stay neutral. Open, High, Low, and Volume stay neutral. -
Layout glyph and matching saved name. The layouts button draws the current 1 / 2H / 2V / 4 grid. When a saved layout matches the live slots, its name is shown. Grid hints include the Alt-1…Alt-4 chords.
-
Alert row tone. Enabled rows that fired within 15 minutes get an up-color edge. Armed rows use the accent edge. Disabled stays faint and wins over a recent fire.
-
Editor shortcut on the topbar title. The docked-editor button title appends the registry chord (
Mod-\, formatted for the platform). Other topbar titles stay prose. -
Watchlist session sparkline. Each compact row draws a 48×10 spark under the symbol from the 24h open to the last price, then extends it with live prints (24 samples, tip updates inside 1.5s). No extra quote request. The stroke uses
--color-up/--color-down. The expanded row shows a 72×16 spark on the detail line. LAST and CHG stay in their columns. -
Symbol picker recents and favorites. The symbol modal stores a per-venue recent list (8) and favorite list (24) at
pynescript.axis.symbol-picks.v1(CCXT scopes by exchange). Favorites and recents show as chips until the search query changes. A star on each row toggles a favorite without selecting the symbol. Picking a symbol records it as recent. -
Performance & architecture audit roadmap (
docs/architecture/perf-audit-2026-11.mdx). Documents the baseline test health (4708 pass / 0 fail), the strongest remaining architecture/performance gaps (coarsestore.barsreactivity, denseChartHosteffects, main-thread engine JSON parse, overlay rewrite cost, live-tick path thrash, UI theming/animation inconsistencies), and a 6-phase plan to reach world-class responsiveness. -
Phase 0 performance telemetry helpers (
src/perf/telemetry.ts). AddsperfMark,perfMeasure,perfMeasureSync,perfMeasureAsync, andsetPerfEnabledfor dev-onlyperformance.mark/measureinstrumentation around the hot paths. -
Narrow bar-plane store (
src/stores/bars.ts). IntroducesbarPlane(bars,tip,length,gen) for fine-grained OHLCV reads that do not drag the monolithicstore.barsarray through every reactive subscriber. Wired intoloadBars,appendBar,restoreSlotBarsToActivePlane, andflushLiveFrame. The plane tip is cloned on full-window updates to keep the new store isolated from the app-store proxies. -
bun checkas the canonical typecheck alias. Root, worker, and CLI typecheck scripts now all usebun check;packages/cli/tsconfig.jsonreplaced the deprecatedbaseUrlwith apathswildcard so the CLI typecheck passes again.
Changed
-
Vendored Pyodide PYNE wheel bumped to
pynescript-0.6.29. Built from the sisterpynescripttree (scripts/sync-pyne-wheel.sh); hard-coded/vendor/paths insrc/engines/catalog.ts,src/workers/pyodide-runtime.ts, andtests/pyodide-worker.test.tsupdated; editor builtins catalog re-synced from PYNEbuiltin_metadata.json(941 entries). Brings strategy next-bar market fills,na/fixnanparity, SAR incremental fix, and the TradingView® loop-args SMA call-site overwrite into the offline Pyodide engine. Self-hosted Pyodide runtime stays on 0.29.5 (latest full CDN release). -
Phase 1 reactivity migration for the hottest
store.barsUI readers. MigratedApp.tsx(document title),StatusBar.tsx,Topbar.tsx,PricePanel.tsx,DataViewPanel.tsx,QuotePanel.tsx,VolumeProfileOverlay.tsx,TimePanel.tsx,CurrentPriceCard.tsx,HpoPanel.tsx, andBarReplayControls.tsxto read frombarPlanesignals instead ofstore.bars. The document-title effect is now also rAF-throttled so rapid ticks do not churndocument.title. -
Instrumented the main paint and parse hot paths. Wrapped
setDataToChart(src/chart/manager-access.ts) withperfMeasureSync('axis:setDataToChart', …)andparseEngineJson(src/engines/json-sanitize.ts) withperfMeasureSync('axis:parseEngineJson', …), plusperfMark('axis:loadBars:setStore')andperfMark('axis:appendBar:store'). This gives a baseline before incrementalseries.update/Worker deserialization work.
Added
- Phase 2 overlay identical-paint caches.
src/chart/compare-overlay.ts,src/chart/onchain-overlay.ts, andsrc/chart/onchain-events.tsnow cache the last applied data and skip redundantseries.setData/ marker-plugin rebuilds when ChartHost re-runs for unrelated signals (chartDataGenbumps, label preferences, etc.). - Reactive ranging signal in ChartHost.
src/chart/ChartHost.tsxexposes a poll-drivenisRangingsignal fromPaneManager.isRanging(). Compare, on-chain scalar, and on-chain event effects suppress full overlay rewrites while the user is panning/zooming and flush the latest state once the gesture ends.
Changed
- Phase 2 data-plane and paint-path reductions.
setBarPlaneAppend(src/stores/bars.ts) avoids copying the entire 4.5k-bar window on every live tick;setDataToChart(src/chart/manager-access.ts) now skips volumesetDatawhen the candle series paint is skipped for an unchanged window. Validation remains 4708 pass / 10 skip / 0 fail.
Added
- Phase 3 off-thread engine JSON parser. New
parseEngineJsonAsync(src/engines/json-sanitize.ts) andsrc/engines/json-parse-worker.tsmoveJSON.parseof engine responses above 256 KB into a dedicated module Worker. Wired intolive-sessions.ts,engine-ws.ts,catalog.ts(REST/run),batch.ts, and the Pyodide result path so the main thread no longer blocks on multi-megabyte series payloads. - Phase 3 typed-result contract. The Pyodide result path now normalizes through
normalizeEngineResult(src/indicators/run-helpers.ts) so REST, WebSocket, and local engines all present the sameNormalizedRunResultshape to the apply pipeline. - Phase 3 in-memory result cache. New
src/indicators/run-cache.tsmemoizes successful non-live runs keyed by(engineId, scriptId, scriptHash, inputs, config, barsGen, barsLength, libraries). Bounded at 16 entries / 30 s TTL, clones values on store and retrieve, and auto-disabled in tests to avoid cross-test pollution. Wired intosrc/indicators/runner.ts.
Changed
- Phase 3 engine-boundary wiring.
src/engines/live-sessions.ts,src/engines/engine-ws.ts,src/engines/catalog.ts, andsrc/engines/batch.tsnow useparseEngineJsonAsyncfor large payloads while keeping small 413-peek / status parses synchronous. Validation remains 4708 pass / 10 skip / 0 fail.
Fixed
-
Adding an indicator no longer shows "Run superseded". Catalog adds and applied-script runs ignore the global run epoch, and live overlay ticks no longer bump it. A later live delta cannot cancel the first paint.
-
SMA / catalog add store commit survived a live epoch bump.
updateIndicatorStore, overlay build, and drawings still used rawisRunEpochCurrent, so a tip tick afterskipEditorDraftcould returnsuperseded: trueand skipaddIndicatoreven thoughrunEpochInvalidatessaid keep going. Those stages now userunEpochInvalidates; the status bar never shows "Run superseded". -
Live overlay SMA/Donchian vs candles. A 1–8 bar stream shortfall no longer head-pads (that shifted Donchian onto newer candles). The last finite value is held forward instead.
appendBaralso extends idle overlay tips onto the new candle so a released SMA keeps covering the forming bar. -
Painted chart no longer keeps multi-bar internal holes after Dataset complete / live ticks.
expandCachedSeriesToNowused to return on a current tip withoutfillInternalGaps, so a live append that jumped closed slots (e.g. 23 missing 1m bars) stayed blank while venue REST still had them. Expand, background catch-up, and live jump detection now REST-fill those holes and repaint the window. -
Released overlay scripts rejoin the live wire. A sticky
message: 'released'aftersetScriptStatus(id, 'live')kept multiplex treating the sub as gone; duplicate PYNE idle/released acks afterrelease()overwrote degraded backoff and looped. Status clears the message on live, duplicate release acks are ignored, and an idle/released ack kicksscheduleLiveRerunimmediately.
Added
-
Phase 4 UX polish. Shared
Spinner, theme tokens onChartScaleControls, safe-area / motion CSS variables, coarse-pointer topbar targets, compositor-only drawing-rail fade, hover-slide gated onprefersReducedMotion, andinstallFocusTrapreturnElementplus aria-hidden sibling hiding (retries on a microtask so Solid refs that fire before insert still hide the background). -
Phase 5 quality gates.
bun run perf:audit(scripts/perf-audit.mjs) runs typecheck, hot-path unit tests, andAXIS_BENCH=1benches, then writesreports/perf-audit.json.--fullalso runs the unit suite and Playwright@smoke. CI gains a dedicated Firehose budget job (bun run test:bench, 120 s wall). ADR-017 (narrow bar plane) and ADR-018 (off-thread engine JSON parse + typed result contract) are recorded indocs/architecture/adrs.mdx.
[2.27.0] — 2026-11-01
Changed
-
Pre-eval call scanning is single-pass (O(n) instead of O(n²)).
scanAllCallSites(src/editor/pine-call-params.ts) used to callfindCallSiteper(, andfindCallSitere-scanned from offset 0 — so idle pre-eval stalled for seconds on real scripts (measured ~2.9 s at 2 000 lines, ~22 s at 4 000). Open parens are now stacked and each call is parsed when its closer is reached. Same source, both lint passes on: 5.9 s → 43 ms at 2 000 lines and 22.3 s → 120 ms at 4 000. Unclosed calls are skipped (mid-edit input is not linted). -
Vendored PYNE wheel bumped to
pynescript-0.6.28. PYNE 0.6.27 / 0.6.28 only touch the VS Code extension and the version string, so the Python runtime, compiler, and language server are identical to 0.6.26; the wheel, the three hard-coded/vendor/paths, and the engine docs now agree. The builtins catalog (src/editor/data/pyne-builtins.json, 941 entries) is byte-identical to PYNE'sbuiltin_metadata.json.
Added
-
Pine symbol navigation: definition, references, rename, occurrence highlight. One local index (
src/editor/symbol-index.ts) parses functions / methods, parameters,typeandenumbodies, variables (incl.var/varipand tuple destructuring), loop bindings andimport … as alias, then answers all four features without a Pro API round trip — so they work in Pyodide and offline.F12(orAlt-click) jumps to the declaration,Shift-F12lists every reference in a CodeMirror panel,F2renames the declaration plus its references, and the cursor symbol is highlighted automatically. Scope is indentation-based like Pine, sof(n)andg(n)resolve separately and a parameter rename stays inside its function. Rename refuses builtins, builtin namespaces, keywords, invalid identifiers, names that would collide or shadow in an affected scope, andtype/enummembers. Also in the command palette. -
Offline PYNE lint parity:
W002andW104.localPreevaluatenow warns on a pre-v5//@versionpragma (W002) and on kwargs Pine v6 removed (W104) —when=onstrategy.*order calls andtransp=on drawing / plot calls (plot*,fill,bgcolor,barcolor,hline,line.*,box.*,label.*,table.*,polyline.*,linefill.*).color.new/color()keep a validtranspand are never flagged. Previously these came only fromPOST /lsp/diagnostics, so Pyodide / offline mode missed them. New settingpreevalV6Kwargs(default on) with a Settings → Editor toggle;preevalVersionWarnnow covers bothW001andW002.
[2.26.0] — 2026-10-31
Changed
-
While ranging, chart paints stay tip-
updateonly. Overlay/bgcolor/OHLC smart-apply skips full-seriessetData, markersetMarkersdefers until the gesture ends,PaneManager.setDataand non-fitsetDataToChartno-op, and the existing slide-replace deferral stays. Tip updates continue. Cuts the post-slide-deferpanFramemax spike (~133 ms at 15k bars) from engine plot/marker writes during pan. -
Live forming-tip apply skips O(n) align / overlay rebuild and store sync. When
executionContext === 'tip',applyPrecomputedChartreuses the last aligned overlay payloads (tip-patch only), skipssetLastRun/updateIndicatorStore/ drawings when unchanged, and reusesstore.barsfor the cached OHLCV axis. Append and snapshot still take the full path. Bridge tip deltas reuse cached input descriptors. Aimed at G1 apply ≤ 8 ms on a 5k-bar tip. -
Live window-slide
setDatadefers while the chart is ranging. Tipseries.updatestays on; a full-window replace during pan/zoom was the ~84 mspanFramespike under live ticks. The pending slide flushes on the next frame after the gesture.PaneManager.isRanging()exposes the gesture for the paint path. -
chart.lagv1 latency rings are a recent window of 120 samples and skip the first 16 after connect/reset. Cold-start / one-off spikes no longer pin live p95 forever in the old 240-cap ring. Field names (queueWait,tickToApply, …) stay stable. Pan and long-task rings share the 120 window but not the warmup skip. -
Rolling-window slides defer the full-series
setDatato one animation frame. Forming ticks stay onseries.update. A slide still needs a replace (LWCupdatecannot drop the rolled-off head), but the replace is coalesced to one rAF and no longer flushes a wasted tipupdateimmediately beforesetData, so the per-minute 5k-bar hit leaves the tick/apply path. WithoutrequestAnimationFramethe replace stays synchronous.
Added
-
MCP
logs.debug.get/logs.debug.clear. Reads or clears the rotatingaxis.debug.log.*localStorage ring written when AXIS debug mode is on (VITE_AXIS_DEBUGorlocalStorage.axis.debug). -
chart.lagv1.queueWaitis recv →apply:start. On the worker path that isbatch:post→ bridge entry, so a live G1 read can separate main-thread queueing from the apply span. Live ticks also skip the O(n) internal-hole scan and debug-pin clear when pins are off. -
CLI 0.3.6.
@hoox-sh/axis-clidefault PYNE URL is the hosted Pro API (https://pynescript.online);AXIS_PYNE_URLstill overrides it. The staledefaultPyneUrltest now matches the shipped default. -
Pine hover/autocomplete refreshed against PYNE 0.6.25.
src/editor/data/pyne-builtins.jsonre-synced frompyne(builtin_metadata.json, 88 improved docs incl.line.fill/linefill.new). Library annotation cards now render the description verbatim as Markdown (headings, code fences, blockquotes, tables, links, images, italic/strikethrough and two-space line breaks), matching PYNE's hover.//@descriptiononlibraryshows a library card. Card sections use PYNE's**Fields:**/**Parameters:**/**Returns:**shape with- \name`: desc` bullets.
Fixed
-
Rolling-window bounds now match the 4,500-bar engine cap.
ROLLING_WINDOW_MAXwas stillHISTORY_BARS_MAX(100,000) whileclampRollingWindowBarssilently capped at 4,500, so Settings and the status-bar editor advertised a range the store would never accept. The constant is now 4,500 and the clamp uses it; the default stays 4,000. -
Test suite isolation:
bun run testand the coverage gate run each file with--isolate. Bun's process-widemock.module/ store state letmultiplex-stream-retry's partialstream-livemock leak into later suites (and stale chart/store state into others). Updated the affected suites for intentional behavior changes: Pro API default endpoints, WS-first engine runs (REST tests pinpreferWs: false), conflation-off chart options, venue OHLC ownership on forming tips, MCP bridgehelloframes, and removed coverage for retiredsanitizeSeriesDataForSetData/_setV1FrameScheduler. -
Symbol navigation hardening: named arguments, shadowing, block comments, member types. Call named arguments (
qty = qty) are no longer indexed as variable writes or declarations, so renaming a same-named variable cannot corrupt a call. References and rename resolve each occurrence to its declaration, keeping a script-levelfand a parameterfapart, and rename rejects a new name that would shadow a reference in an affected scope.obj.fieldresolves through the receiver's declared (orType.new()-inferred) type;type/enummembers with trailing comments are indexed, commented-out declarations are ignored, fields with defaults are indexed once, numeric literal tails (1e10,0xFF) are no longer indexed as identifiers, and a cursor at EOF in the last function stays inside that function. -
Shortcuts help no longer steals typing keys.
app.show-shortcutswas bound to bareShift-?/Shift-/, so typing/(Shift+7 on many layouts) or?opened the shortcuts overlay — the editor could not type//. The binding now requires a real modifier (Alt-Shift-?/Alt-Shift-/) and is skipped while focus is inside an editor/input/dialog. -
Series live no longer storms
fullsnapshots (~6/s) into chartsetData. Debug ring showed 228wire.series.full+ 200setDatain ~40s: gap recovery cleared its backoff on every full, then unsubscribe+subscribed again. Series gaps now resume (scripts still fresh-subscribe); sliding fulls after the first snapshot tip-merge only; backoff clears only after a continuous delta. -
Live candle paint no longer clamps or repairs OHLC.
PaneManager.appendBarno longer callsrepairOhlcon forming ticks — exchange OHLC is clean ~99.9% of the time, and clamping could alter real values. Non-finite ticks are dropped instead. -
sanitizeSeriesDataForSetDataremoved entirely. No donor fill, no OHLC fill-from-neighbor, no clamping — bad rows are simply dropped beforesetData. A minimalfilterValidCandleRowsguard drops rows with non-finite time/OHLC or duplicate times (last write wins) so LWC never throwsValue is null.repairSeriesPayload,setCandleData, andsetVolumeDatapass data straight to Lightweight Charts. -
Live script input-change resubscribe no longer stays
idle/released. An input edit (e.g. Momentum Length 10→100) unsubscribes then re-subscribes on the same id; the PYNEidle/releasedack arriving duringawaitSnapshotused todropScriptLocalthe half-open replacement (wipingappliers/ ctx), so the next eval re-subscribed forever and never rejoined while Bollinger/SMA stayed live. In-flight script subscribe now ignores that ack (same idea as gap-resyncholdUntilFull), clears the inbox on release, and re-binds applier state when the new full lands. -
Venue live owns candle paint — engine series snapshots/deltas no longer
setDataor overwrite forming bars. Worker tips were painting stub OHLC (tiny volume, disconnected opens) on top of Binance candles — visual gaps — and 5ksetDataon every engine full stalled scripts (websocket · cooldown/ stop updating). Replica stays on the wire for indicators; chart tail stays venue. -
Engine-owned mode no longer repaints the full window from PYNE on every tick. The
fulldiff branch calledsetDataToChart(proBars), replacing every candle — including venue-closed bars whose OHLC PYNE could report collapsed or stale. Now engine bars are merged into the venue window viamergeEngineSnapshot: existing times keep venue's OHLC, only new times from the engine are appended, and the chart is only repainted when the merged window actually changed. Closed candles no longer get collapsed on a new candle. -
Live start / scheduleRerun re-subscribes every visible non-library script, including
idle/released. PYNE unsubscribe acks (idle+ messagereleasedvia live-wirerelease→unsubscribe) no longer leave EMA/Momentum blank while Bollinger stays on the wire: multiplex bypasses stream-eval backoff for released scripts, clears their retry state, and on stream-route live start kicks an immediate (0-debounce) scheduleRerun.scriptHistoryBarsuses the stable chart window so a 1-bar length flicker cannot changehistory_barsand bounce subs. -
Price chart printing pipeline refactored.
store.appendBarsplits intoapplyCandlePoint/applyPriceLineTint/updateVolumeBarfor a simple O(1) live-tick path;setCandleData/setVolumeDataextract full-history setData out ofmanager-access.setDataToChartso both full loads and live slides share one code path. Test doubles / managers without the new methods fall back to inline painting.appendBarno longer bumpschartDataGenon a live slide — ChartHost'schartDataGeneffect responded withfit:true→fitContent(), resetting zoom/position on every new candle.appendLiveBaralready paints slides withfit:false, so ChartHost does not need to repaint. -
Live candle open no longer jumps when historical data arrives.
store.appendBarpreserves the candle's open once it has started forming: a historical close for the same bar updates high/low/close only, so the open set by the venue's first tick is not overwritten. -
store.appendBarnow writes live-tick OHLC tostore.barsinstead of a discardedunwrap()copy.Object.assign(unwrap(tip), payload)mutated a deep copy returned by Solid'sunwrap(), so live ticks never persisted updated OHLC/high/low/close intostore.bars. A fullsetData(window slide at capacity, enginemergeEngineSnapshot, etc.) then painted stale OHLC from the store — old candles collapsed/lost their OHLC when a new candle arrived. Both the OHLC write and theclosed = trueflag now go throughsetStore('bars', index, ...)so the store is the source of truth for bothseries.update(live tip) andsetData(full window). -
Engine deltas no longer overwrite venue OHLC on forming candles.
store.appendBarnow accepts aviaparameter ('worker'|'venue'). When the engine (via: 'worker') updates a forming tip (same bar time, tip not closed), venue's OHLC values are preserved — engine deltas may have slightly different values due to computation/rounding, and overwriting them caused candles to collapse/change OHLC on every engine tick. Venue ticks (via: 'venue'or undefined) always overwrite — they are the source of truth for forming candles. Threaded throughappendLiveBar→appendBar→store.appendBar. -
Indicator live pushes no longer shift right on a new bar.
alignApplySerieshead-padded shorter series with nulls when the engine evaluated the previous window, shifting every indicator value right by one position instead of extending onto the new bar. On venue live pushes (non-session, no engine clamp), shorter series now tail-extend by holding the last value forward (extendSeriesToWindow), so plots like MA ribbons extend onto the new candle instead of shifting. Session runs and free-tierwindowBarsclamps keep the existing head-pad / tail-pin behavior. -
Chart view no longer resets (zoom/position) on each new candle.
appendBarno longer bumpschartDataGenon a window slide. ChartHost'schartDataGeneffect previously responded withfit:true→fitContent(), which reset the viewport on every new candle.appendLiveBaralready paints slides withfit:falseviascheduleSlideReplace, preserving the view. -
Candle rendering no longer merges bars or hides bodies. LWC conflation (
enableConflation) andminBarSpacing: 0.5were merging sub-pixel bars at 2500+ bar histories, rendering candles as dojis with gaps between them. Conflation is now disabled (enableConflation: false,minBarSpacing: 0.001) in bothseries-factory.tsbase config andheavy-data.ts, and time scale options are re-applied on every live tick so existing charts pick up the change without a reload. Viewport auto-scrolls to follow new candles (shiftVisibleRangeOnNewBar: true). -
Live candle debug logs include full OHLC.
live.appendentries now recordopen/high/lowalongsideclose, so MCPlogs.debug.getshows each candle's complete OHLCV for verifying forming-tick and historical-bar values. -
Default rolling window lowered to 5,000 bars to match PYNE free tier limit.
ROLLING_WINDOW_DEFAULTwas 10,000 — the free tier engine refuses windows above 5,000 with HTTP 413, surfacing “Live re-run failed: OHLCV exceeds free-tier limit (5000 bars)” on boot. Pro tier users can still raise the window in Settings → Chart → Rolling window. -
Adding a second indicator no longer supersedes the first.
runAndApplyreused the current epoch when an interactive run was already in flight, so rapidly added indicators no longer mark each other “Run superseded” before either engine evaluation completes. -
Comprehensive debug logging added to chart paint path.
chartDebugentries added tostore.appendBar,normalizeBarPayload,repairOhlc,mapBarsToPriceData,toOhlcData,isFinitePriceBar,mapBarUpdate,mapHeikinAshiUpdate,sanitizeSeriesDataForSetData,findOhlcDonor,setCandleData,PaneManager.appendBar,applyCandlePoint, andupdateVolumeBar. Use MCPlogs.debug.getto trace OHLC values through the entire paint pipeline and identify where candles collapse to lines or lose their OHLC values. -
sanitizeSeriesDataForSetDatano longer overwrites all four OHLC fields from a donor. Previously, when any OHLC field was missing/invalid, all four values were replaced from a single donor neighbor — making multiple bars identical (collapse to lines). Now only the fields that are actually non-finite are repaired from the donor; valid fields are preserved. Look forsanitizeSeriesDataForSetData { verdict: 'filled-fields-from-donor', repaired: [...] }in MCPlogs.debug.get. \n\n-repairOhlclogging reduced to actual clamping only. Previously loggedopen-eq-closefor every doji candle (thousands of noise entries). Now only logs when high/low are actually clamped. Raw venue OHLC is logged before repair viasanitizeStreamBar { verdict: 'pre-repair' }andsanitizeBar { verdict: 'pre-repair-array|object' }, so you can compare venue OHLC against stored OHLC to find where corruption happens. -
Indicator overlays update on the live forming candle again.
alignApplySeriesstaleNoAxisskipped every stream live push whose engine series was shorter than the chart window — which is the steady state, because script snapshots are capped atLIVE_SNAPSHOT_BARS(500) while the rolling window is typically 5000. Tail-clamp shorter series onto the chart tip (head-padded withna, newest values on newest bars) instead of skipping, so indicators repaint every tick instead of freezing until the snapshot outgrows the window (skipped: 'stale-no-axis'). -
Live-wire seq-gap thrash no longer blanks indicators. A script/series gap now does one backoff-gated unsubscribe+subscribe for a fresh full (instead of resume-loop forever), honors
seqAckFromas continuous, paints when that full lands, and clearsholdUntilFull.history_barsshrink/flicker no longer bounces the sub; engineidle/releaseddrops local state so the next eval rejoins; script HUD live/connecting clears a stale “delta gap — resubscribing” message. -
Live stream overlays paint when the engine series is longer than the chart window without a bar axis. A 20k replica vs 5k rolling window used to hit
staleNoAxisand skip forever (tip cache never seeded → blank indicators). Live pushes now tail-clamp onto the chart tip; wire snapshots synthesizebar_timesfrom the subscription window when the engine omits them. Shorter-than-chart axis-less pushes still skip. -
Main-thread live WS shares the soft
UNAUTHORIZEDauthBlock policy with the ingest worker.shouldAuthBlocklives inframes.ts; JSON and worker transports only permanently block on real bad-key errors, and otherwise close to remint/re-auth. -
Stream script eval retries with backoff after
StreamFallbackErrorinstead of stayingdegraded · websocketforever. Multiplex records a per-script retry-after (1s → 2s → 5s … cap 30s), skips mid-backoff ticks (still no REST full-window fallback), and clears backoff whenstreamEvalScriptsucceeds so status returns to live. -
Live-wire auth-race subscribe and reconnect cooldown. Series/script subscribe errors with
UNAUTHORIZED(or "Authenticate first") retry once after a short delay instead of immediate fail/cooldown; a successful reconnect (resumeAll) clearscoolUntilso a transient fail cannot keep rejectingensureSeries; a failed script subscribe fullyrelease()s the applier so the nextstreamEvalScriptcan try again. -
Ingest worker no longer permanently
authBlocks on PYNEUNAUTHORIZED. Connection-levelUNAUTHORIZED/ "Authenticate first" means not-yet-authed (pre-auth race), not a bad API key: while waiting forauth.okthe worker resendsauth; otherwise it clears the auth wait and reconnects. Permanent blocks stay for other non-retryable connection errors (e.g. invalid/missing API key). -
Tick→paint p95 no longer absorbs hidden-tab stalls or same-frame slide
setData.paint:rafdrops bursts armed while hidden or suspended mid-flight (rAF occlusion was pinning live p95 ~177 ms / max ~300 ms while apply stayed ~30 ms). A window slide tip-updates the new bar immediately and yields the head-dropsetDatato a microtask after rAF so siblingpaint:rafcallbacks in that frame are not queued behind a 5k-bar replace. Snapshot field names unchanged. -
Live-wire same-slot series tips close
queueWaitsynchronously. Metrics-onlyapply:start/endno longer waits on a kick microtask (the ~16 ms p50 after the prior microtask move). A new tip time still coalesces one cached-script paint on a microtask so a real script frame in the same turn can cancel it.holdUntilFull, seq, and one-tip inbox unchanged. -
Live tip-cache now hits on forming deltas that lacked a tip stamp.
stream-deltacoerces a leftoversnapshotcontext (series→script kick afterapplyFull) andforming: truewithoutexecutionContexttotip, soapplyPrecomputedCharttip-patches instead of re-aligning ~5k bars every tick (live apply was p50 ~6.4 ms / p95 ~10.6 ms; cached tip microbench ~0.03–0.3 ms). OHLC overlays tip-patch the last candle; lean tip hits sync lines/OHLC only. Append/snapshot full path unchanged. -
Live-wire inbox and series kick cut residual
droppedTip/ panqueueWait. Live forming tips (already applied viarouteFrame) skip the waiter inbox once the sub has an applied seq, so same-slot replaces no longer inflatedroppedTip(was 85–239 under load). The series→script kick closesbatch:post→apply:starton a microtask instead ofrAF, so pan/zoom at ~15k bars cannot stall queueWait behind a delayed animation frame (~430 ms p95). TipCoalescer forwards contiguousUNCHANGEDkeepalives (after flushing a pending tip) so the applier advances seq withoutseqAckFromholes thatclient-applycounted as drops; gap keepalives stay observe-only. Snapshot-waiter inbox still keeps ≤1 forming tip; same-slot kick stays metrics-only. -
Live-wire series→script kick closes
queueWaitwithout flooding reapply. Series-only tips still endbatch:post→apply:startonce per frame, but same-slot forming OHLC only stamps metrics (no script reapply); a new tip time paints at most one cached script; a real script paint cancels a pending kick. Fixes the post-4df26790queueWait p50 regression (~426 ms ← ~16–30 ms) from reapplying every live script on every tip. Mixed batches,holdUntilFull, seq gates, and one-forming-tip inbox stay unchanged. -
Ingest host subscribe/resume omit
encodingunless the caller asks, and misaligned AXB1 views no longer decode as malformed.IngestHostClienthad hardcodedencoding: 'axb1'on every subscribe, so live-wire's JSON fallback (snapshot timeout /jsonSubs) still negotiated AXB1 and could not leave the binary path — matching livechart.lagjsonFallback: 3withunknownOpcode: 0. Resume now matches the JSON client (encoding only when set). A sliced AXB1 frame whose bufferbyteOffsetis not a multiple of 8 is copied before decode so absolute i64 alignment checks cannot drop a valid snapshot. -
Single-chart layout arms
chart.lagpanFrameon pan/zoom.createSharedPanenever setrangingor wired wheel →v1NotePanActivity, so the default one-chart host leftpanFrame.n = 0after user gestures. The shared price host now mirrors the multi-pane listeners (pointer ranging + wheel). -
chart.lagv1 is counters only. The per-tick latency rings (tick→apply / tick→paint / apply / queueWait / panFrame / longTask), the rAF paint arming, the pan-sampling loop, theconflation/visibleBarsgauges and the long-taskPerformanceObserverwere removed from the hot path — they sampled every tick and kept 120-sample windows just to report p95.v1Countcounters (seqGap,resync,droppedTip,jsonFallback,unknownOpcode,longTask) stay;chart.lagv1 now reportscounts+transport+layout.chart.v1/invariants.ts1-in-32 sampling andv1BudgetRegressedare gone. -
Live-proxy buffers client frames until the engine upstream is open, and the ingest worker resends
authonlive-relay. Browseronopencould sendauthbefore the Worker’s upstream socket was ready; the frame was dropped and subscribe then failed withUNAUTHORIZED. The proxy queues early client→engine frames; the ingest worker also resends auth when it seeslive-relayand never signals hostopenwithoutauth.ok. -
Ingest tip coalesce and send-buffer backpressure cut tip delay under load. A second forming tip for the same subscription folds and releases immediately instead of waiting out the 16 ms coalesce window, so
batch:postis not stuck behind a quiet-period timer. Send-buffer hold uses hysteresis (enter above 1 MB, clear only below 256 KiB) so a buffer that jitters around 1 MB cannot resume every live sub in a loop. UNCHANGED keepalives are no longer treated as droppable forming tips under send hold, so their seq still reaches the coalescer and is acked. Closes and snapshots stay immediate; the pending queue remains one tip per subscription. -
Ingest worker waits for
auth.okbefore signaling hostopen. With the Worker ticket proxy, subscribe was racing ahead of PYNE auth (UNAUTHORIZED→subscribe-errorcooldown → venue+REST).INGEST_AUTH_WAIT_MS(5s) still opens on timeout so a slow key hash cannot wedge the socket.
Added
-
P6 flag flip proven in unit tests. The test switches worker+single to json and back: the live client follows both flags together, but the chart stays the shared single layout — the
axis.chart.layout=multirollback is gone (see Removed below). -
Inactive chart slots paint without stealing the focused manager.
setDataToCharttakes an optionalmanager/slotIdso a 2h/2v/4 cell cansetDataon its own PaneManager. Drawings and selection stay on the focused slot. -
chart.lagv1names the live transport and chart layout. The snapshot already had tick→apply / tick→paint. It now also reportsaxis.engine.transportandaxis.chart.layoutso a live G1/G2 read can tell worker+single from the JSON/multi rollback. -
The one-chart layout resizes panes with Lightweight Charts
layout.panes.enableResize. Separator colors follow the void theme. A drag writes pixel heights into the store.PaneManager.resizecallsIPaneApi.setHeighton that layout so a sub-pane drag does not resize the whole chart. The multi-chart rollback keeps the custom handles and turns LWC separators off. -
AXIS debug mode (
VITE_AXIS_DEBUG) — detailed chart-action and engine logs. SetVITE_AXIS_DEBUG=1(alsotrue/yes; aliasVITE_DEBUG) in.envand restartbun run dev. Boot prints[axis:debug]; DevTools filter[axis:chart]/[axis:engine]. Lines also persist in a rotating localStorage ring (axis.debug.log.0….3, 64 KiB each, oldest dropped). Download the ring asaxis-debug-*.logfrom System Logs (download icon) orwindow.__AXIS_DEBUG__.download(). Forming ticks and live deltas are sampled (~1/s). Optional overlay:localStorage.axis.debug=1then reload. e2e forces the flag off. -
The engine socket mints a Worker live ticket instead of putting
api_keyon/ws/live. When a cloud Worker key is set and neither the engine nor the Worker is loopback, the PWAGETs/api/ws/live?issue=ticketwith Bearer and upgrades?ticket=. The PYNEauthframe still carries the engine key. Direct local/ws/livestill uses?api_key=because the browser cannot sendAuthorization. A 401/403 mint does not reconnect; transport failures remint. -
One live batch commits price, plots, markers, then alerts.
commitLiveBatchis the §6.1 transaction. A workeronFramesbatch of mixed series+script deltas paints price then plots in the same task. Stream deltas apply through synchronousapplyStreamDelta/applyPrecomputedChart(snapshots stay on asyncapplyStreamPayload). -
v1 concept tests cover pane/crosshair identity, drawings through a window slide, the 20% p95 budget, and pan/zoom e2e. Playwright
@v1loads 10k mock bars while mock-poll is live and bounds pan/zoom frames; applies RSI + MACD + ATR for a price+3-indicator pane-parity screenshot; and keeps a trend line through pan, zoom, and a clock-advanced window slide.v1BudgetRegressedfails review when p95 grows more than 20% vs the fixture. -
A tab keeps at most two engine sockets. Chart slots share one WebSocket per engine endpoint (
acquireSlotWire/releaseSlotWire). Same endpoint from N slots is one live client with N refs. A second socket opens only for a different endpoint. A third waits until a slot is released. The primary chart drops its previous fingerprint on engine change so a single chart does not pin two unused sockets. -
Each chart host pins that pool for its slot.
ChartHostcallspinChartSloton mount andunpinChartSloton cleanup. Four hosts on one endpoint share one client (refs 4); unmounting three leaves one. Unpin remembers the fingerprint from pin, so a later engine change still releases the original socket. A second pin of the same slot id does not bump refs.
Removed
-
The shared-memory ring, GPU candle overlay, and HP flags are gone. Lightweight Charts paints candles, indicators, and fills. The ingest worker still owns
/ws/liveand decodes AXB1 whenaxis.engine.transportisworker(the default).axis.engine.transport=jsonkeeps the main-thread client. Settings no longer has a high-performance section.hp.get,hp.sample, anddev.traceare no longer MCP capabilities. Isolation headers stay. -
The
axis.chart.layout=multirollback is gone.axis.chart.layoutis no longer read or persisted — stored values are ignored likeaxis.engine.hp.*. One Lightweight Charts chart per slot only:ChartLayoutMode/chartLayoutMode/setChartLayoutMode/resetChartLayoutMode/usesSingleChartLayout,CHART_LAYOUT_KEY,SlotChart, shared-pane chrome, multi-chart slot-bar caches, custom pane-resize handles (pane-handle-*), crosshair mirroring across panes, and themap/mirrorbranches insyncTimeScales,alignTimeRangesFromPrice, and crosshair rewiring are removed.chart.lagv1 always reportslayout: 'single'.
Fixed
-
MCP
stream.resetre-pins mounted chart hosts.resetStreamWirekeeps live ChartHost slot ids, drains the pool, then pins those hosts onto the current engine fingerprint (new sockets). Unpin still drops a slot so reset cannot resurrect it. With no hosts mounted the pool stays empty untilgetWire. -
streamStopkeeps mounted ChartHost slots. Stop drains and disconnects sockets and clears remembered fingerprints, but still-mounted slot ids survive. The nextgetWirere-pins those hosts onto the current engine. Unpin still drops a slot so stop cannot resurrect it. With no hosts mounted,getWireopens one primary socket. -
A pane whose visible logical range disagrees with the price axis is reported.
alignTimeRangesFromPricesampleslogicalRangeSkewonce against the one shared chart (no per-pane mirror). Dev warns on every new skew size. Production warns about 1 in 32 times, once per size. Matching ranges are silent. -
A plot column shorter than the price axis is reported.
applyChartMutationssamplesplotLengthSkew. Dev warns on every new absolute size. Production warns about 1 in 32 times, once per size. A longer column still uses the overhang text and is not warned twice. -
Crosshair stays on the shared single chart. Data-driven (no
sourceEvent) moves no longer attempt a cross-mirror that never had a second chart to write to;syncCrosshair/rewireCrosshairsubscribe once perIChartApiinstead of per pane, andapplyCrosshairToPane+ the multi-pane resize handle are gone. Hovered-pane gating only gates the host callback now. -
The single chart wires one crosshair listener. Sub-pane facades are different Proxy objects around the same Lightweight Charts chart. Once the slot exists, later panes do not subscribe again.
-
A series that ends after the price tip is reported.
applyChartMutationssamples overlay and plot times against the candle axis. Dev warns on every new past time. Production warns about 1 in 32 times, once per time. A shorter series is warmup and is not a warning. -
Inbound live frames as
Blobdecode likeArrayBuffer. Cloudflare® compat ≥ 2026-03-17 delivers binary WebSocket frames asBlob.decodeInboundstays sync for string /ArrayBuffer/ views;decodeInboundAsyncawaitsblob.arrayBuffer()then decodes. AXB1 is detected on the bytes and is neverTextDecoder'd. Malformed Blob bytes staymalformedand do not throw. The ingest worker callsdecodeInboundAsyncfor Blob instead of unwrapping itself. -
Strategy and plotshape markers commit with the plot series. Marker lists are built before the chart mutates. One
runApplyTransactionpaints markers then plots, so a throw insetShapeMarkers/setTradeMarkersdoes not leave a new plot generation on the chart. The 2500 marker cap is unchanged. Live ticks still update that bar's markers. -
A price seq gap waits for a snapshot. A live series delta that skips
seqno longer paints. The subscription resumes once, later ticks stay dropped, and the nextfullpaints. -
The first live script snapshot uses the same paint pump as later ticks.
evalScriptno longer paints off to the side of the chain, so a delta cannot race the snapshot. -
A live stream snapshot paints the whole indicator. Stream deltas still update the tip only. Treating the first snapshot as a live tick skipped the apply when the engine window and the chart window disagreed and there was no
bar_timesaxis, so the pane stayed empty once live started. -
A newer snapshot is not painted under a late forming tip. Script paints wait on a per-subscription chain and always apply the newest payload. The last paint is the snapshot, then later deltas.
-
APPEND_BAR flushes a pending forming tip then the append. Same as a
close: a confirmed (forming: false) delta for that subscription does not wait for the coalesce timer. -
AXB1
SNAPSHOT_PLOTSkeepsbar_timesaligned withcount. A plots snapshot with times round-trips exactly. Encode throws when times or a plot column is the wrong length. The ingest mapper copies those times onto the JSON full asbar_times. Times stay optional so a frame without them still encodes. -
A refused replay stays on the socket. After a gap, an error from the engine asks once more without AXB1. A second error leaves the subscription stale and does not paint later deltas. There is no REST repair.
-
An inbox full of closes asks for one snapshot. Forming tips are still the only frames dropped. When the inbox is over its cap and every frame left is a close, a snapshot, or a confirmed delta, those frames stay and the subscription resumes once. The next snapshot allows another resume.
-
A full send buffer drops forming tips and resyncs when it drains. Past 1 MB of unsent socket data the socket stays open, forming deltas are not applied, and each live subscription is marked resyncing. When the buffer drains, those subscriptions resume once. Snapshots and closes still pass.
-
A forming tick notifies Solid once per frame. The bar value is written immediately. The quote tick and the tip subscription flush together on the next animation frame, so a burst of ticks does not re-render the price chrome once per tick. A new bar and a window slide still commit immediately.
-
Pane badges sit on the shared chart's plot cell. On the one-chart layout, a volume or indicator badge was rebuilt onto the hidden placeholder the next time badges refreshed. The badge now stays on that pane's plot cell, and a refresh finds it there. The multi-chart rollback still badges its own pane div.
-
Script and strategy runs stay on the WebSocket. A failed socket run does not POST
/runor open a REST data session. Live scripts that miss the stream wait on the socket instead of falling back tovenue+REST. REST/runremains only when WebSocket is explicitly turned off. -
The live inbox drops extra forming tips. Each subscription keeps at most 32 frames. Only a forming delta is dropped. A
close, afull, an error, and a non-forming delta stay, and the snapshot waiter still receives thefull. -
The quote card stays on the candle while the stream is down. The quote sheet, the float price panel, and the chart price card use the last bar close when the stream plane is connecting, degraded, closed, or in error. An open stream still shows the live tick.
-
The single chart does not copy its time range or crosshair onto a second chart. Volume and indicator panes share the price chart's time scale. Range mirroring and crosshair mirroring run only for the multi-chart rollback.
-
A subscription that is waiting for a snapshot does not paint deltas. After a gap or a failed apply, further deltas are dropped until the next
full. That snapshot clears the wait and paints once. -
A late snapshot does not paint over a newer sequence. A full whose
seqis behind the cursor already applied for that subscription is dropped. The newer bars and the newer seq stay. -
A closed bar's OHLC is not rewritten after commit.
appendBarwarns (noteClosedRewrite) and returnsignored. The store keeps the committed close. Closing the forming tip is still an update. REST/dataset merge is unchanged. -
A forming tick does not change the closed-bar hash.
closedBarHashhashes only bars that are closed (closed !== false). History rows that omit the flag still count as closed. Closing the tip still changes the hash. A closed OHLC restatement is dropped and does not. -
Engine series closed-bar alerts run in the live apply.
onSeriesDeltaevaluates alerts after a non-ignored append of a closed bar. The venue multiplex skips that evaluation while the stream route is active so the same close does not fire twice. -
A closed bar whose OHLC changes after commit is reported and dropped. Dev warns on every new bar time. Production warns about 1 in 32 times, once per bar. The committed close stays.
-
A plot column longer than the price axis is reported.
applyChartMutationssamples the column lengths. Dev warns on every new overhang. Production warns about 1 in 32 times, once per size. A shorter column is sampled as length skew. -
Several live ticks in one turn paint the chart once. The store still updates immediately. Same-time ticks keep the latest bar. A newer time is applied after the previous bar in that same flush. A window slide still replaces the series immediately, with
fitoff. -
An open stream with no tick for 3 seconds says
delayed Ns. Reconnecting keeps its own text. The live tooltip on the top bar, the connection HUD, and the mobile live button reads the stream plane's last event time. -
Sync checks for the price tip, plot length, and closed bars.
paneAxisSkew,seriesPastTip,plotLengthSkew, andclosedBarHashare pure helpers. They do not paint. -
A dropped socket waits with jitter and does not retry a rejected login. The reconnect delay grows from 500 ms and stays at or under 30 s, with a little downward jitter. An error frame with no subscription and
retryable: falsestops the loop until the nextconnect(). -
Only a close marks a bar closed. An AXB1 tip is always
closed: false.APPEND_BARandCLOSE_VERIFYsetclosed: trueon the committed candle. A later forming tick cannot reopen it. -
Alerts wait for a closed bar. A forming tick no longer evaluates price, drawing, or indicator alerts. The live hook runs only when the bar is marked closed.
-
A live bar time comes from the server, not the local clock. AXB1 snapshot, tip, close, and append times go through the same unix-second normalizer as venue bars. A tip that omits the bar open stays on the previous server bar. The frame's
tsMsis not used as the candle time. -
A live
setDataputs the visible range back. Window slides and left-edge prefetches replace the series withfitoff.setDatacan still jump the logical range, so the range from before the replace is written back. A full load still fits. A tick still usesupdateand does not callfitContent. -
A snapshot that never arrives retries twice, then that subscription asks in JSON. The waits stay bounded (the same snapshot deadline, then 50 ms and 200 ms). An AXB1 socket resubscribes once without
encoding. A JSON socket just fails. The series socket stays open. -
An unknown AXB1 opcode does not close the socket. The decode counts
unknownOpcode, names the subscription, and that subscription resumes withoutencoding: 'axb1'. Other subscriptions stay on AXB1. A second unknown opcode for the same subscription does not resume again. -
A hidden tab does not replay every missed tick. The live socket stays open. Forming ticks fold into one slot per subscription. When the tab is shown, a
closed_uptothat moved resumes once from the last painted close. Otherwise the latest tip is applied once. A seq that never arrived still resumes. -
A dead ingest worker is replaced.
onerrorterminates that worker, starts another, and connects again. The next open resubscribes through the same reconnect handlers as a socket drop. After five crashes in oneconnect()the host stays down. It does not switch the socket back to main-threadJSON.parse. -
A dropped keepalive no longer looks like a missed frame. The tip coalescer still drops
UNCHANGEDframes and still folds several forming tips into one. Those seqs are now acknowledged on the next frame that is actually released (seqAckFrom). The applier and the apply transaction advance through that run. A seq nobody saw is still a gap and still resubscribes. JSON decode strips a forged ack, so a socket payload cannot hide a real hole. -
Tail-align pads at the head again, so a clamped series no longer paints early.
tailAlignSeriesToWindowwas flipped to pad at the end by the P03 perf commit (33cc4269) without a test or changelog change, contradicting its own doc block ("a shorter one head-padded withnull") and thetailAlign*contract. Consequence: ameta.windowBars: 3free-tier clamp ([11,12,13]on a 5-bar chart) painted 11/12/13 on the three oldest bars instead of the newest three — every value shifted two bars early. Restored head-padding (the newest engine value belongs on the newest chart bar); the engine-evaluated-fewer-history-bars case stays the job of the bar-time join (alignSeriesToTimes). Added a unit regression guard so a future "pad at the end" edit fails loudly, and corrected the stale comment inapply-context.tsthat described the flipped behaviour.tests/runner-engine-owned-apply.test.tsgoes 22/7 → 30/0. -
fillVisualAttrFromViewportno longer spins forever (GPU overlay / test-suite hang). The P03 single-pass rewrite leftconst len = 0with no increment in the merged hot+page fill loop, andlenwas returned as the fill length. Any viewport that touched the page slot — a zoomed-out chart on HP-3, ortests/hp/visual-attr.test.ts— wedged the thread in an infinite loop; because that file never returned,bun run teststalled after its last completed test and only exited on timeout. Rewritten as the same two-pass shapefillVisualAttrFromHotuses: pass 1 walks the contiguous covered prefix and tracks the visible volume maximum (a gap ends the run), pass 2 writes the records. Also removed the duplicatedopen/highNDC writes the merge left behind. -
ColdStorecap test reads the absolute index it documents. AftersetBars(bars(20))withmax: 10,baseAbsis 10, soread(0, 1)is before the retained head and correctly returns empty (the A18/A23 contract). The test asserted the oldest surviving bar through relative index 0; it now readscoverage().baseAbs. -
Session overlays no longer tail-pin a window they never evaluated. A session/stream run whose engine shipped no bar axis was tail-pinned onto the chart even when its series length missed the window by thousands of bars (a 9577-bar engine window on a 5000-bar chart shifts every value and marker by 4577 slots — the SOLUSDT desync). The
staleNoAxisskip now also covers full runs whose length misses the window by more than a few mid-run appends (PIN_TOLERANCE_BARS = 8); the deliberate free-tier clamp (windowBars) keeps pinning, and equal-length runs keep the index zip. The WS/ws/runpath now also carriesmeta.engineTimeslike REST, so WS session runs can time-join instead of pinning. And a series repair now invalidates memoized data sessions + live tick/append sessions for that key, so the next run creates a fresh session over the repaired window instead of evaluating (and pinning) the stale one. Pinned by four newalignApplySeriesguard tests (gross mismatch skips, plots-array mismatch skips, clamp still pins, small race still pins) plus data-session / live-session invalidation tests. -
The WebGL2 overlay engages on HP-3 selection (hp2 D1). The overlay was a forced-
gpu=webgl2instrument;autostayed dormant even where the rung selector already landed on HP-3.startGpuOverlaynow gates on the same selection the HUD chip reports —probeRendererKindwebgl2 plusselectHpRung() === 3— soautoon a WebGL2-only machine paints the parity overlay.off, GPU-lessauto, the painterless HP-4 rung, session-cap clamps below 3, and HP disabled all stay dormant. The hide-LWC switch remains gated (D2): this is still additive parity, not the production painter.
Added
-
A thrown paint resumes once instead of leaving a stale badge. The apply transaction still skips the rest of the batch. The status says why, and that subscription asks for one snapshot. A later failure does not resume again until a full arrives.
-
The worker transport and the single chart are the defaults. A tab with no
axis.engine.transportuses the ingest worker. A tab with noaxis.chart.layoutuses one Lightweight Charts chart per slot.jsonandmultiremain the rollback. A missing Worker still counts one JSON fallback and stays on the main-thread socket. -
Panning to the left edge prefetches one older history page. The dataset read does not run on the drag frame. The page is painted with
fitoff, and the logical range shifts by the number of bars inserted on the left so the viewport stays put. The rolling window grows with that page, up to the 100k cap, so the next live bar does not slice the page away. Conflation itself is unchanged: on at 2,500 bars, precomputed from 10,000. -
A live bar keeps its plot and its fill. When the engine axis ends before the chart tip, the last value is extended onto the newer bars instead of head-padding the series, which slid the fill onto later candles. A live tick whose axis is behind also drops the memoized session so the next run evaluates the bar the candle is already showing.
-
A script axis with a hole the candles do not have asks for a series repair.
noteChartSeriesHoleruns at apply time, so Donchian does not wait for the next series snapshot. -
A PYNE tick that reports
staledrops the script session. That flag means the engine clamped its history walk, not that the feed was idle. The next run loads the store after the gap page is written. -
A fresh engine tip no longer hides a hole in the series. Repair used to stop when
newestwas current, so a tick walk that skipped the middle (about 22 hours on DOGEUSDT 1m) stayed in the store and the Donchian axis. A bounds deficit of 30 or more bars now uploads the local window. An upload that adds nothing is remembered, so a weekend on a session venue does not re-upload every cooldown. A snapshot whose stamps jump, while the chart's do not, asks for that repair. -
axis.chart.layout=singleuses one Lightweight Charts chart per slot. The default staysmulti(one chart per pane, manual time-scale sync).singleadds indicator and volume panes withaddPaneon the price chart, sends each series to that pane index, and removing a sub-pane does not remove the candles. Time-scale mirroring is skipped because there is one scale. -
The ingest tip coalescer folds a window instead of keeping one frame. Last OHLC tip wins, plot columns union by key, and a plot tip no longer drops the pending candle. A non-tip frame flushes that subscription's tip first.
UNCHANGEDkeepalives with no bars and no columns are dropped. -
A script delta paints inside one apply transaction.
runApplyTransactionrefuses a seq gap, a rewind, a generation mismatch, or a pane/plot shape check beforecommit. A thrown step skips the rest of the batch and does not paint. The live wire commits the merged script delta through it, so a gap never reachesapplyScriptPush. -
A new candle uses
series.updateunless it is the first bar or the window slides.appendLiveBarused tosetDatathe whole window on every append, on the belief thatupdatecannot add a time. Lightweight Charts 5.2 appends when the time is newer than the tip. The first bar and a rolling-window slide stillsetData. Forming ticks stay onupdate. -
The live proxy forwards a Blob frame as the original bytes.
relayFrameacceptsBlob(Workers compat dates ≥ 2026-03-17 deliver binary frames that way) and both sockets preferbinaryType = 'arraybuffer'when the property exists. Text andArrayBufferframes are unchanged. The compat date stays2024-11-01. -
Subscribe and resume send
encoding: 'axb1'only when the ingest worker owns the socket. That isaxis.engine.transport=workeror HP on. The main-thread JSON client omits the field, so a server that ignores it keeps the old JSON frames, and a server that honors it does not switch a parse-only client onto binary. -
axis.engine.transport=workerdecodes/ws/liveoff the main thread without turning HP on.workeris now the default (see above);jsonis the main-thread rollback.workerreuses the ingest worker for the same JSON frames and does not allocate the hot ring, negotiate AXB1, or start the overlay. A tab that asks for the worker whereWorkeris missing stays on the JSON client and counts onejsonFallback. Pinned bytests/engine/v1-transport.test.ts. -
v1 engine metrics record tick→apply, tick→paint, long tasks, and pan frames.
src/engine/v1/metrics.tsstampstick:recvbefore main-threadJSON.parseonfull/delta/close,batch:postwhen the ingest worker delivers a decoded batch, andapply:start/apply:endaroundapplyStreamPayload, then one frame for tick→paint. The first stamp in a burst wins. Seq gaps and gap-driven resumes increment counters; conflation and the visible bar count are gauges onsetData; pan and wheel frame deltas sample while the price pane is ranging.chart.lagreturns the rolling p50/p95/p99 snapshot asv1. Pinned bytests/engine/v1-metrics.test.ts. -
Tick → frame latency is split into pipeline stages (hp2 A1). The §14.2 number was one delta — applied tick to next frame — so a bad p95 could hide anywhere between the worker and the glass. The ingest worker now stamps
publishedAton everyframesmessage (dedicated-workerperformance.nowshares the main clock origin), andpaint-latencyrecords per-burst stage marks: worker publish → main apply (publishToApply, covers postMessage + dispatch), apply → hot-ring copy-out, copy-out → overlay draw. Stages that never stamp (JSON path, LWC-only paint) reportnullinstead of fictional zeros, and the existing total is untouched.hp.getexposes the split aspaint.stagesp50s next topaint.latency, so the p95-tail hunt in.opencode/hp2/implementation.mdcan point at a stage instead of the whole pipeline. Pinned by three newpaint-latencystage tests (first-stamp-wins, null-path, burst reset). Controlled live runs (2 × 10 s, HP-0 JSON path, MCP idle): 587/593 frames @ ~59 fps, rolling paint p50 ~13 ms / p95 ~16 ms / max ~129 ms (n = 240) — the §14.2 tail (p95 ≈ 160 ms, max ≈ 520 ms) does not reproduce under quiet conditions and attributes to observer load during that measurement (heavy MCP diagnosis traffic + screenshots), not the pipeline. HP-on rung runs still pending for the A2 gate. -
The ingest worker reads its own cold history (hp2 B1). The page slot used to paint only what the main thread's chart window happened to hold: a range older than the REST window arrived as a short (or empty) slice and the overlay fell back. The worker now owns a
ColdStoreper series bind — hydrated frombars-cacheIDB on connect via the newcoldKey(sourceId/symbol/interval, posted onconnect), extended by every snapshot (upsertByTime), every verified bar close (appendTick— forming re-ticks share the tail time and are ignored), and every page slice the main thread sends (the REST slice accumulates, so gaps close over time). Thepagemessage carries the wantedcount, and the worker publishes the merged range — main bars win on overlap, cold fills the rest, truncation at the first uncovered slot instead of an invented bar.ColdStoretracks its absolute base (baseAbs) so an evicted head or a prepended deeper history cannot silently misalign slots; losing absoluteness flipscompleteand the merge stops trusting positions. A symbol switch re-seeds; a hydrate racing the socket merges instead of wiping. Pinned by new write-back/upsert/eviction/merge tests intests/hp/page-buffer.test.ts. -
AXB1 codec fuzz + relay reconnect proof (hp2 E1). The audit found no dead surface:
HPC1_MAGICis the live control-block magic andAXB1_HDR_COMPRESSEDis live as a v1 reject-rule (decode throws, encode refuses) — both are now marked as audited inconstants.ts. Newtests/hp/axb1-fuzz.test.tsthrows 500 seeded-PRNG frames across all 11 opcodes atencode → decode(exacttoEqual, 4-byte alignment asserted) plus 50 single-bit-flip corruptions that must fail loudly withRangeError/TypeError, never silently.worker/tests/live-proxy.test.tsgains the reconnect leg: upstream drop closes the server side, the client redials with a fresh ticket, the JSON resubscribe stays text, and the first binary frame on the new socket is still byte-identical. -
Calc session runs in a worker (hp2 C1).
CalcSessionwas main-thread library code; the HP topology needs it off the Window thread with cold history as its replay source. Newsrc/hp/workers/calc-engine.ts(pure, testable) owns one session per series plus aColdStoreeach: snapshots seed cold and replay from cold once it holds history (full-history replay, not the 500-bar main window — a rebind with no reposted bars replays identically), tips stay scratch, only appends commit.FrameValuesEvaluatorfiles the Pro-API server values by bar time; itssnapshotCommitted/restoreCommittedare an O(1) cursor — the pickle-restore analog for a host with nothing to pickle — andevaluateCallspins the no-rewalk bound (12 bars + 3 tips = 15 calls, not 12 + 3×12). Hydrate merges IDB behind live bars instead of wiping them; per-series isolation and error-instead-of-throw replies are covered. Pinned by sixcalc-workertests against the PYNE-recordedbarstate-parityfixture (ta-cum confirmed series identical). The thincalc.worker.tsentry ships with the protocol; the live frame producer that posts to it is the explicit next step, not this commit. -
The GPU overlay paints zoomed-out viewports via pixel-column downsampling. The hot window plus one cold page cover at most
2 · HOT_CAPbars, so a fully zoomed-out chart fell back to LWC for most of the pane — and paging the whole series in would break theO(HOT_CAP)memory contract. When the exact hot+page fill cannot cover the viewport's real bars, the overlay now downsamples the chart window LWC paints (downsampleToPixelColumnsinsrc/hp/paint/): one bin per pixel column, first-open / column-extremes / last-close / summed volume, bin centers in fractional viewport slots that the renderer positions natively. It reads the exact array LWC paints, so parity is by construction and it needs no SAB traffic; the exact fill stays the path wherever the viewport is coverable, and the forming tip keeps its flag through the bin. Pinned by binning-semantics, fractional-center, hole-skipping, and viewport-clip tests (tests/hp/downsample.test.ts). -
Cold paging is wired: the GPU overlay paints past the hot ring. The overlay used to clear whenever the viewport started left of the hot window, because the page slot existed only as library code nothing populated. Now the overlay reports its absolute viewport every frame (
noteHpViewportinsrc/streams/stream-live.ts, throttled to 200 ms), the main thread slices the cold range out of the chart window it already owns, and the ingest worker — still the SAB's single writer, so a page replace can never tear a concurrent hot publish — replaces the page slot via the newpagemessage (publishBarPage, converted with the buffer's own price scale). A re-seed or reconnect retires the page (its absolute base is snapshot-relative), and the overlay fills the merged hot+page range in one seqlock sample (fillVisualAttrFromViewport), stopping at the first uncovered bar so LWC keeps painting underneath. Chart index ↔ absolute mapping lives in one place (chartRangeToAbsolute) so the pager, the page slot, and the fill cannot disagree.hp.getreports the page (page.active/from/count). Pinned bychartRangeToAbsolute,publishBarPage,fillVisualAttrFromViewport, and hostpage-message tests. The worker-sideColdStore/IDB adapter stays library-only (future calc-reader territory). The@gpue2e re-asserts the shrunken-ring step: the cold viewport must repaint without zooming (downsample, then the paged exact fill) — the old clear assertion encoded the pre-paging contract. -
The HP engine is configured in Settings, not the console. Every
axis.engine.hp.*flag used to requirelocalStoragein DevTools, which made an opt-in plane look like a developer hidden switch. Settings → General gains a High-performance engine section: the master toggle (off keeps the JSON/ws/live→ Lightweight Charts path byte-identical), the GPU painter (auto/webgl2/webgpu/off) and the SAB transport (auto/off), each writing the same tab-local keys so automation still works. The section renders the resultinghpChip()verdict inline (json · lwc,hp · sab+gpu,Kill switch, …) and, once something changed, the Reload to apply button the flags need — the plane is read at boot, so a silent write would look like a no-op. The architecture page and the README now point at the section instead of DevTools, and an e2e smoke asserts the toggle, the select write-through and the reload affordance (e2e/smoke.spec.ts). -
Tick → frame latency is measured (the plan §14.2 gate).
src/chart/paint-latency.tsstamps the first applied live tick of each frame and records the delta to that frame's timestamp — one rAF callback per burst, no steady loop, no timers, and the recording is a no-op for rejected ticks.appendLiveBarcalls it, and bothhp.get(paint.latency) andhp.sample(paint) expose p50 / p95 / max over the last 240 samples, so the default-flip decision can be argued with production numbers instead of a stopwatch.hp.sample's ring progress is nowclosedUptoTimeDelta— the engine'sclosed_uptois a time stamp, not an index, and the old name invited that misreading. -
Chart screenshots include the HP GPU overlay.
chart.screenshot(MCP) and the Screenshot menu composed LWC's pane canvases, drawings and the watermark — but never the HP-3 WebGL candle canvas, which is a sibling element, so a captured image silently differed from what the trader sees.capturePaneCanvasnow composites the overlay under the drawings (annotations stay on top) viaoverlayPlacement, which maps the overlay's CSS rect into the screenshot's pixel space (shot.width / pane.width, so 1× and 2× captures both line up). A WebGL drawing buffer is discarded at composite time, so the copy runs in the next animation frame — the overlay's own rAF tick was registered earlier, which is what keeps the buffer intact withoutpreserveDrawingBuffer. Verified by sampling 1 948 candle pixels: the live composite reads (174,92,94), the app screenshot (184,96,98), LWC alone (229,119,122) — the screenshot follows the composite. -
High-performance chart engine implementation spec on branch
engine/high-performance(.opencode/hp/implementation.md). It pins generation-based double-buffering, ingest/calc/render workers, the AXB1 4-byte-aligned binary delta, viewport-only SAB paging, int64 price ticks (float32 only after NDC), worker-onlyAtomics.wait, tip-vs-append Pinebarstatecontext, and a HP-0…HP-4 fallback matrix on top of the existing JSON/ws/liveplane. -
AXB1 codec + JSON execution context (HP PR 1). Canonical prices are int64 ticks (
decimalToTicks/priceToTicks); the encoder/decoder round-trips the 32-byte little-endian envelope and the TIP/APPEND/SNAPSHOT opcodes. Live JSON deltas withforming: truestampexecutionContext: 'tip'on the merged payload so a later calc worker will not commitbarstate.isconfirmedaccumulators untilAPPEND_BAR. -
Cross-origin isolation headers (HP PR 2). Every static host (Pages
_headers, nginx, Vite dev/preview, Bun and Python PWA servers) and the SW offline shell send one locked-stepCOOP/COEP: credentialless/CORP: same-origintrio — COOP landed popup-safe and later flipped tosame-origin(see Changed), so SharedArrayBuffer is now reachable without a second header pass.isolationProbe/selectHpRungstay HP-0 untilaxis.engine.hp.enabledand report SAB only whencrossOriginIsolatedis true. The kill switch isaxis.engine.hp.disabled. -
Ingest worker:
/ws/livedecode off the Window thread (HP PR 3).createLiveWsClientreturns anIngestHostClientonly whenaxis.engine.hp.enabled === '1'and a Worker exists; otherwise it returns the originalRedesign2WsClient, so the flag-off path is byte-for-byte today's client. The worker owns the socket (binaryType: 'arraybuffer'), decodes JSON and AXB1 through the shareddecodeInbound, holds forming tips for a 16 ms vsync coalesce whileclose/APPEND_BARflush immediately, and posts typed frames over aHostToIngest/IngestToHostport — the auth token rides once onconnectand is never echoed back. The host keeps theWireClientcontract (onFrame,awaitFrame, reconnect handlers) soStreamLiveWireis unchanged, and the Workers Manager gains anhp-ingestcard that probes asnone(there is nothing to HTTP-probe in a tab). Tests:tests/hp/ingest-core.test.ts(JSON + AXB1 fixtures through the pure decoder) andtests/hp/ingest-host.test.ts(loopback port: connect, frame delivery, exact subscribe serialization, reconnect, error mapping). -
Hot double-buffer + main-rAF copy-out (HP PR 4). One SharedArrayBuffer is sectioned into a 256-byte control block (the seqlock words), a series header (price scale + epoch), two reserved tip records, and the hot window itself:
HOT_CAP= 5_120 bars × 2 slots of int64-tick columns (~450 KiB — O(hot window), not O(N), so a mobile WebView never has to allocate 200k×2). The single writer (ingest) fills the unpublished slot and flips the generation from odd to even; readers sample only even generations, and a copy is re-verified by a second generation load — a torn sample is discarded and retried, and after the retry budgetcopyOutBarsreturnsnullso the paint path keeps the previous frame instead of showing a mixed candle.Atomics.waitis worker-only:waitInWorkerthrows the momentglobalThis.Window/selfsays Window thread, which the main thread replaces withsubscribeGeneration'srequestAnimationFramepoll. ARangeErrorfrom the allocator drops the tab to rung 1 for the session (axis.hp.rung = '1') instead of retrying the OOM on reload.copyOutBarsclips the visible range to the hot window (cold pages are HP PR 5) and produces the float64Bar[]Lightweight Charts paints; the tests feed those bars throughappendLiveBarand assert all four store verdicts (ignored/updated/appended/slid) plus the torn-write, main-thread-guard and 5_120×2 allocation cases (tests/hp/{hot-buffer,atomics-main-guard,copy-out-lwc}.test.ts). -
Viewport paging + columnar cold store (HP PR 5). The SAB gains one single-buffered page section (
PageSlot[HOT_CAP]) after the hot double-buffer, so panning into cold history copies one page of bars — a far-left jump replaces that page instead of allocating a third history buffer, and "fit content" over a range that still overlaps hot is acked without pulling 100k bars into shared memory (§9.2). The allocation is a fixed 691,968 bytes (~676 KiB): O(HOT_CAP) and provably independent of series length, because a 200k-bar history allocates the same bytes as a 1k one. A page replace runs its own seqlock transaction (beginPageWrite/commitPageWrite) that never moves the hot published slot, so the hot window and the forming tip keep updating while the page is on screen. Readers getcopyOutPageand thecopyOutViewportdispatcher besidecopyOutBars, all three sharing one seqlock-verified clipped copy.ColdStorekeeps the worker's full series as columnar int64 ticks — a page copy is a straight column read, never a re-encode — hydrates from the existingbars-cachememory-first, and on rung 1 (copyOutPageFromCold, no SAB at all) the viewport is still served straight from the cold store (tests/hp/page-buffer.test.ts). -
Calc scratch-vs-commit state machine (HP PR 6). The calc worker's core rule, made testable without a Worker or a Pine runtime: a forming tip is a scratch evaluation and a bar close is a commit.
barstate.tspins the §10.1 contexts — history replay is new + confirmed + never realtime (isfirstonly at index 0,islastonly on the rightmost bar), the snapshot's forming tip and every incremental tip are never confirmed sota.cum/ta.valuewhen/varaccumulators stay scratch,isnewis true only on the first tip after an append, andAPPEND_BARcloses the bar with a confirmed + realtime eval that folds the accumulators before the next slot opens.CalcSession(calc-core.ts) drives anyCalcEvaluator— PYNE in a worker, Pyodide, or a test double — throughonSnapshot/onTipUpdate/onAppendBar: each tip restores the committed snapshot before evaluating, so ticks cannot accumulate into committed state, and only the append path grows the committed plot series. This split is not invented: PYNE's own multi-tick realtime window setsisconfirmed = tick_i == n_ticks - 1(forming ticks unconfirmed, closing tick commits), which is exactly this table. Parity is pinned by fixtures recorded from PYNE (scripts/record-hp-fixtures.py→tests/hp/fixtures/barstate-parity.json, pynescript 0.6.18, re-recordable after a PYNE release): the tests drive the session through wild forming ticks and assert the committed series still matches the recorded closed-runta.cum/ta.valuewhen/varoutputs bar for bar, that three 200→150→125 ticks leave committed untouched, and that the recordedbarstateflag columns match the replay contexts (tests/hp/barstate-tip.test.ts). -
AXB1 golden vectors now pin every payload shape (HP PR 7 prep).
tests/hp/axb1-vectors.jsongrows from a lone heartbeat to 12 vectors — heartbeat, unchanged, status, tip (close+volume+time and full OHLCV), append-bar, close-verify, plot-tip (int64 ticks + float64), a 2-bar OHLCV snapshot, a plots snapshot, error, and a JSON blob. Each vector carries the language-neutral frame spec (int64/uint64 as decimal strings, masks and value kinds as names) and the exact little-endian hex the encoder must emit, plushash_checkspinningfnv1a32on shared inputs. This is the cross-repo contract: PYNE'stests/test_axb1.pybuilds the same frames from the same spec with its own encoder and must produce identical bytes, so a wire-format drift now fails both repos' test suites instead of silently corrupting live frames.bun scripts/generate-axb1-vectors.tsregenerates the hex from the specs through the real AXIS encoder, and the codec test loops every vector through spec → encode → golden hex → decode → re-encode (tests/hp/axb1-codec.test.ts). -
The live relay no longer corrupts binary frames (HP PR 7b).
worker/src/live-proxy.tsdecoded every non-text WebSocket frame withTextDecoderbefore relaying it — fine for JSON, fatal for AXB1: any byte that is not valid UTF-8 would be mangled before reaching the client. The relay now hands payloads through untouched (relayFrame: text stays text,ArrayBufferand views stay bytes, in both directions), which is exactly what a transparent proxy means once the engine can speak binary. The test drives the realhandleLiveProxyupgrade path with a fakedWebSocketPairand upstream, and asserts an encoded AXB1TIP_UPDATEcrosses the relay byte-for-byte in both directions while JSON control frames stay text (worker/tests/live-proxy.test.ts). -
GPU paint plane: NDC staging + instanced WebGL2 renderer (HP PR 8).
hp/paint/visual-attr.tsturns visible candles into the plan's 32-byteVisualCandlerecord (§6.5):x / open / high / low / close / volumeas float32 and nothing else — prices are mapped to NDC in float64 against the caller's visible y-range and narrowed once at the store, so neither float64 nor int64 ever crosses the GPU boundary (WebGPU has no float64) and canonical ticks stay in the SAB.fillVisualAttrFromHotmirrors the copy-out's seqlock discipline (sample only even generations, re-verify after the copy,null= keep the previous frame) over avisible + 64capacity, withvolume_normnormalized against the visible maximum.hp/render/adds the painter seam:probeRendererKindpins the plan's probe order WebGPU → WebGL2 → LWC as a literal 4×4 truth table whereoffwins over capability and a forced mode never claims a backend the probe did not find, andcreateWebgl2Rendererdraws the whole viewport as one instanced call — one VAO, an 8-word-per-record divisor-1 attribute layout read straight out of the staging bytes, and a fragment shader that carves the body box plus a centered 1/8-slot wick out of a single quad per candle (packed0xAABBGGRRunpacked with core ES 3.00 bit ops, since GLSL ES 3.00 has nounpackUint4x8; alpha 194/255 blending over the transparent-clear, stacked LWC chrome). The factory is null-never-throws, so any context/shader failure falls through to LWC instead of failing the page. Tests pin the struct stride and byte order, the NDC math, the hot-fill contracts, the full probe table and the factory's failure surface (tests/hp/{visual-attr,select-renderer,webgl2-renderer}.test.ts). Live wiring is deliberately deferred: no producer writesvisual_generationyet and plan §14.2 forbids gutting PaneManager before GPU parity tests exist, so — like PR 4–7 — this lands as library + tests, and the e2e "candles visible" smoke waits for a producer. -
HP visibility: architecture page, agent playbook, and rung chip (HP PR 9).
docs/architecture/hp-engine.mdxdocuments the opt-in plane end to end — the HP-0…HP-4 rung matrix and selection algorithm, the flag table with the kill-switch rule, the COOP/COEP reality — the isolating pair (same-origin+credentialless; COOP flipped from the popup-safe value, see Changed) — while HP-3/4 still run on the copy transport until a producer wires the SAB hot ring, the AXB1 transport and its cross-repo golden vectors, the SABO(HOT_CAP)memory contract, the GPU paint seam, and a landed-vs-library-only status table — registered in the Architecture nav and track map.okf/playbooks/hp-engine.md(curated,okf_lock: human) gives agents the same map as a playbook: which hp submodule owns what, the contracts that must not break (seqlock discipline, 8-word/32-byte record stride, float32 only after NDC, GLSL ES 3.00 bit unpack, the right-edge NDC anchor, flag-off byte-identical rollback), and the reminder that rung selection ≠ wiring. The Workers Manager gains a Chart plane card carrying the plan's HUD chip:hpChip()(src/hp/hud.ts) reports the selected rung in plan format —hp · sab+gpu/hp · copy+lwc/json · lwc(transport memory + painter) — where default-off honestly readsjson · lwcbecause that is exactly today's path; the kill switch shows asKill switchand engaged tabs show the rung word (HP-3). Pinned across the full flag × capability matrix including the session OOM clamp (tests/hp/hud.test.ts). The feed-chip surface and MCPstream.get'shpfield stay unwired. -
A working OpenCode project configuration. The project
opencode.jsondeclared a Prettier formatter whose command never passed a file, so every write ran a no-op formatter and then fell through to Biome — which is lint-only here (biome.jsonsetsformatter.enabled: false) and exited non-zero, so no file was ever formatted. The command now ends with$FILE, which OpenCode replaces with the absolute path, and the formatter is scoped to the code extensions the repo declares as Prettier-managed (.ts,.tsx,.js,.jsx,.mjs,.cjs,.json,.jsonc,.css); Markdown/MDX and YAML are excluded because Prettier rewrites the#-block license headers into headings and flips the compose files to single quotes. Biome,rustfmt,ruffanduvare disabled as formatters: Biome is the linter,src-tauri/src/*.rsis not rustfmt-clean (the repo uses a 2-space Rust style), and the Python helpers have no formatter config. The same file keepslsp: true, adds the OKF bundle as project instructions, ignores the heavy build directories in the file watcher, guardsgit push/git tag/ deploy /rm -rfbehind an approval prompt, enables thetodotool, and adds the/commit,/verifyand/releaseslash commands plus a read-onlyaxis-reviewersubagent. Tworeferencespoint OpenCode at the sister checkouts —pyneat/home/jango/Git/pynescript(the checkout is still namedpynescript, notpyne) andhooxat/home/jango/Git/hoox— so Pine Script™ parity questions can be checked against the real PYNE builtin signatures without scanning the tree. Automatic compaction is enabled (auto, 15 000 retained tokens, 20 000 buffer) so long/verifyand/releaseruns stop walking into the context ceiling. Four more shell rules joingit push/git tagbehind the approval prompt:git reset --hard*,git clean -f*,git checkout --*andwrangler secret*— the destructive git forms that could discard work, and the command that mutates live Worker secrets. -
One git backend instead of two near-identical files. GitHub and GitLab were separate modules with twelve functions of the same body, and the dispatch picked between them with nine ternaries. The provider-specific parts — URL shape, auth header, content encoding, update token, conflict status — now live behind a
GitForge, and the shared operations live in one place. The per-provider differences stay explicit: GitHub identifies a blob by its sha and answers409on a conflict, GitLab only needs to know whether the file exists and may answer400/409/422. -
Engines declare what they are instead of being guessed from their id. Seventy-one places branched on the engine id string —
id === 'server',id.includes('pyne-worker')— to decide how the engine behaves: which credential slot it uses, whether the remote LSP is reachable, which transport the HUD reports, whether the run can be re-applied. An engine now declares a family (server/pyodide/worker/dynamic), and the behaviour branches on that. The id stays the user-facing name and the selection key, and the credential vault still resolves a family exactly, so an unknown engine id is never routed to the shared server or worker slot by accident. -
One event-listener primitive instead of the same four lines everywhere. Dozens of components repeated
onMount(() => target.addEventListener(...))/onCleanup(() => target.removeEventListener(...))— the same shape, easy to get subtly wrong (acaptureflag on one side only, a listener that outlives its component).attachEventListenerattaches and returns the disposer, so the two halves cannot drift apart, anduseEventListenerwires it to the component lifetime. -
One modal shell instead of six copies of the dialog chrome. The About, Symbol, Script settings, Dataset manager, Keyboard shortcuts and Storage change dialogs each carried their own backdrop, accent, header (title, hint, close), body and footer, plus their own focus trap, Escape handling and backdrop dismiss. They now render through one shared
Modal, which owns the chrome, the focus trap and the Escape listener — the listener exists only while the dialog is open. The Keyboard shortcuts dialog declaredaria-modal="true"and had no focus trap; it now has one, like the other five. -
The shortcuts dialog's close button now uses the shared icon. It was the only dialog drawing a literal
✕instead of the LucideXthe other five use. -
One run-result envelope instead of three. The engine result arrived over the REST path, the WebSocket path and the batch path, and each built the same envelope with its own copy of the body — plus a byte-identical copy of the bar-time-axis extractor. One mapper builds it now. The differences between the paths are stated per call site instead of hidden inside three copies: the WS path resolves the run mode server-side, the REST path infers
script_typefrom the payload kind, and the batch path only accepts a real booleanoverlay. -
One Worker route table instead of four copies of the route list. The route knowledge lived in the entry dispatcher's
switch, its ad-hocif/startsWithguards, the MCP proxy's own dispatcher, and the MCP allowlist's method+path pairs — and the four drifted. One table declares the routes now; the entry dispatcher and the MCP proxy both read it. The allowlist stays the authority on whether a path may be reached over MCP (it is a security boundary with its own script-id and version-path rules), and a test asserts the table and the allowlist agree. -
One popover shell instead of four copies of the menu skeleton. The chart-layout, screenshot, extra-widgets and run-split menus each carried an open flag, an anchor element, a measure-and-clamp placement, a resize/scroll reposition, an outside-click dismiss and an Escape handler. One
Popoverowns that skeleton now. Two of them had drifted: the run-split menu dismissed onmousedownwhere the others usedpointerdown, and it hand-rolled the outside check the shared helper already provided — it now dismisses on the same event as the others. -
A network failure carries its status code. The same three steps — read the body, decide what a non-2xx means, give the failure a status — were written into every network call, and the status was usually flattened into the error text, so a caller could not tell a 404 from a 429 without parsing a string.
src/api/client.tsowns those three steps now, and the cloud storage client and the venue fetchers carry the status on the error. The engine plane, the probe plane, the git adapters and the editor LSP client keep their own contracts on purpose — a failed engine request is a verdict, not an exception — and the module documents which is which. -
HP-2 producer wiring: ingest writes the hot SAB ring, the chart samples it on rAF. The SAB plane stops being library-only. When the tab is opted in and cross-origin isolated,
IngestHostClientallocates the hot ring once and rides it onconnect(hotSab); the ingest worker wraps the same buffer, confirms with ahotreply, and becomes the ring's single writer (hp/workers/hot-ring-writer.ts). The first series snapshot binds the ring (a snapshot for another id rebinds with an epoch bump, so a symbol switch never reuses ticks); a bound seriesdeltais written into the ring and stripped from the posted frame —seq,closed_uptoand the positional block still crosspostMessage, soapplySeriesPush's no-bar branch keeps resume/repair sequencing intact — while snapshots keep their payload (initial paint) and script deltas, foreign series and stale-only deltas pass through untouched: the frame path is always a correct fallback. Every flip republishes the whole window throughpublish(an unpublished slot must be fully rewritten or readers see stale zeros outside the touched indices); a forming tip overwrites the newest entry in place, and a full window slides one step with a whole-window rewrite per closed candle. Main-thread paint isstartHotRingPaint(src/chart/live-append.ts): asubscribeGenerationrAF pump samples the published generation viacopyOutTailand feeds the contiguous tail run that continues the store tail throughappendLiveBar— the same store verdicts and paint invariants as the frame path (a forming tick never reopens a closed tip; a jumped ring — suspended tab, resume replay — is left to the snapshot/repair path).stream-live.tsstarts and stops the sampler with the wire lifecycle. Flag-off, kill switch, non-isolated tabs and a refused SAB allocation (the existing rung-1 OOM clamp) all keep today's JSON/transferable frame path byte-identical. New tests:tests/hp/hot-ring-writer.test.ts(bind/strip/slide/rebind/passthrough routing),tests/hp/hot-ring-paint.test.ts(sampler verdicts incl. the non-contiguous guard and disposer),tests/hp/ingest-host-sab.test.ts(the isolation × flag transport gate). -
HP-3 producer wiring: the WebGL2 candle overlay paints over the price pane (parity mode). The GPU paint plane gets its producer.
src/hp/render/gpu-overlay.tsstacks a transparent WebGL2 canvas over LWC's price-pane content area and draws the same candles LWC is painting — aligned by construction against LWC's own geometry (visible logical range + right-scale coordinates) — with LWC staying the painter and the full fallback; hiding LWC's candle series remains the plan-gated next switch (§14.2). Data is the HP-2 ingest hot SAB ring:stream-live.tsexposes the ring the wire allocated (getLiveHotBuffer), and ChartHost starts the overlay per active slot after the initial paint, stopping it in the main cleanup. The frame path reuses the PR-8 renderer (fillVisualAttrFromHot→ one instanced draw) with a new pureoverlayViewporthelper (fractional logical anchor, right-edge index, theMAX_OVERLAY_BARSsanity cap) and apriceScaleLinearprobe that refuses non-linear (log/percentage/indexed) right scales; every bail — no ring, ring/chart length mismatch, cold left edge, non-linear scale, torn seqlock sample — clears the transparent canvas, so a skipped frame shows LWC underneath and never a stale GL layer. The overlay engages only whenaxis.engine.hp.gpuis forced towebgl2: LWC paints candle bodies translucent (0.76) and stacking identical GL bodies composites them darker, soauto/off/webgpustay dormant until the hide-LWC-candles switch is measured. Tests:tests/hp/gpu-parity.test.tspins feed parity againstcopyOutBars(bit-exact NDC + reconstruction), hot-fill bit parity, capacity-drop parity,overlayViewportanchor identity vs LWC, the explicit-gate matrix (auto dormant even when probeable; forced+probeable engages once, disposes safely) andparseCssColorToRgbon LWC's premultiplied palette. The e2e smoke (e2e/gpu.spec.ts,@gpu) drives the full product chain — empty chart, offline mock-poll stream, scripted/ws/livefull snapshot + per-slot rollover re-seeds against a local WS server (Playwright's WS routing does not intercept the ingest worker's socket) — and asserts the overlay canvas turns visible and its framebuffer carries alpha-nonzero GL pixels. -
MCP observability for the HP plane:
hp.get+hp.sample. The HP flags live in tab-locallocalStorageand isolation is a runtime property — neither crossed the sanitized app snapshot, so an agent asking "is HP actually working in this tab?" got nothing.src/mcp/hp.tsadds two read-only capabilities:hp.getreports the flags (enable / kill switch /gpu/sab), the cross-origin isolation probe, the HUD chip verbatim, the session rung cap after a SAB OOM (axis.hp.rung), the actual hot-ring runtime (window bounds,seq/closedUpto, SAB bytes,tailTimevschartTailTime, and the tail-identity alignment the GPU overlay guard enforces), the wire state, the overlay canvas's DOM state, and a verdict —kill/off/awaiting/copy/fallback/degraded/misaligned/live;hp.samplemeasures a bounded window (100 ms–10 s, default 1 s) of movement: ringseqdelta (ingest publishes landing),closedUptoprogress, rAF frame cadence, and the chart's append/tail deltas. Pure reads — no flag writes, no wire start, no SAB allocation, no secrets. Tests:tests/mcp-dispatch.test.tspins registration as read-only, the default-off / kill-switch verdicts, the engaged-rung-without-wire wording, and the sample envelope incl. the window clamp.
Changed
-
The last hand-rolled LRU trims use
BoundedMap. The session run-mode memo, the session retry cooldown map and the dataset memory mirror each reimplemented delete + re-insert + evict-oldest; they now useBoundedMap.set, which owns that sequence (and, unlike the dataset store's old loop, needs no special-case to protect the just-written key — a fresh write is always newest, so the trim can never evict it).BoundedMapalso gains[Symbol.iterator]so the dataset mirror's existingfor..ofloops keep working. Pinned by the dataset, live-sessions and data-sessions suites. -
The cloud client uses the shared JSON fetch instead of its own.
storage/cloud's privateapi()reimplementedfetchJsonline for line (fetch +readJsonBody+HttpError, same{status, json}shape, same 30 s timeout). It now delegates, which givesfetchJsonits first production caller — the helper is no longer a tested-but-unused abstraction. No behavior change: same return shape, same timeout, same error contract. -
One flag pair instead of two state machines.
engine/feature-flagandhp/feature-flagwere the same module with different keys: an enabled key, a disabled key, in-memory fallbacks, and a kill-switch-wins rule written out twice. They now sharecreateFlagPairfromsrc/utils/flags.ts; the plane modules keep their key constants and public function names, the GPU/SAB/rung handling stays HP-specific, and the kill-switch-wins rule exists once. Side effect, now matching both modules' doc comments: the stream-engineisEnabledalso respects an explicit opt-out (previously only HP's did). Pinned by thehp/feature-flag+engine/feature-flagsuites. -
One storage accessor instead of eight helpers and thirty guards. Four modules each owned an
lsGet/lsSet/lsRemovetriple and ~30 call sites hand-rolledtypeof localStorageguards, differing only in whether a failed write returnedfalseorvoid. They now sharesrc/utils/storage.ts(getItem/setItem/removeItem/getJson/setJson/getFlag/setFlag), which never throws — the boolean write return is kept because the alerts persist path reports quota failures. The memory fallbacks in both feature-flag modules and the quota-reportingtrySetItemin the store stay exactly as they were. Pinned by the existing storage, alert, credential, vault, catalog, watchlist, plugin, optimize and editor suites (234 cases). -
Nine listener sets share one emitter instead of hand-rolled loops.
bar-replay, both alert modules, the MCP bridge, the dataset store, the secret vault, the credentials store, the editor bridge and the open-script source each kept aSetof listeners with its own subscribe/unsubscribe pair and notify loop — and two of them (editor-bridgedispatch,open-script-sourceemit) had no error isolation, so one throwing subscriber broke delivery for the rest. They now sharecreateEmitterfromsrc/utils/emitter.ts(snapshot iteration, per-listener fault isolation); the emitter gains aclear()for the dataset store's test reset. All public subscribe functions keep their names and signatures. Pinned by the existingbar-replay,editor-bridge,dataset-store,credentials, alert and MCP-bridge suites (236 cases). -
One public-REST definition per venue instead of two.
symbol-catalog(instruments) andwatchlist-tickers(24h tickers) each implemented OKX / Bybit / Coinbase / Kraken — same hosts, sameHttpErrorcontract, sameXBT → BTCnormalization — and had already drifted: the catalog sentcache: 'no-store'where the tickers did not. Each venue now owns its public fetchers (fetchOkxInstruments/fetchOkxTickers, …) insrc/data/venues/, sharing onevenueFetch(alwaysno-store, throwsHttpError) and one symbol spelling (toUsdt,okxInst,coinbaseProduct, Kraken legacy-key handling) invenues/spot.ts. The catalog and the Watchlist are thin mappers over those;toUsdt/okxInst/coinbaseProductstay re-exported fromwatchlist-tickersso the WS layer's imports are unchanged. Side-effect fix, not a behavior change: the Coinbase ticker path strippedUSDT/USDbut notUSDC, so a…USDCsymbol built a broken product id — it now shares the exported helper. Pinned by the existingsymbol-catalogandwatchlist-tickerssuites (42 cases). -
One wire-bookkeeping core for both live socket clients.
Redesign2WsClient(direct WebSocket) andIngestHostClient(HP ingest worker) each carried a verbatim copy of the request/response waiter map, the reconnect-handler set with its swallow-per-listener notify loop,awaitFrame,dropPending, and thestats()snapshot — a fix to waiter cleanup or teardown in one never reached the other. They now sharePendingRequests<TFrame>(id-keyed waiters, timeout reject,dropAll) andReconnectHandlers(snapshot iteration, fault isolation) insrc/engine/wire-bookkeeping.ts, plus awireStats()snapshot helper. Public behavior is unchanged (addReconnectHandler,awaitFrame,stats()shapes identical; the one white-box test that read the private handler set now reads the shared core). Pinned by thews-client,ws-send,ingest-hostandingest-host-sabsuites plus themcp-live/engines-live-sessions/data-sessionssuites (124 cases). -
The Worker answers every route with the same error envelope. The entry
fetchopened itstry/catchafter the MCP, MCP-bridge and stream paths, so an exception in any of them escaped the Worker as an unhandled rejection instead of a500. The dispatch now runs inside onetry, and a sharedhandleErrorhelper logs the failure server-side and returns the canonical{ status: 'error', code: 'INTERNAL' }envelope. The error text is still never reflected to the caller. -
One rate limiter instead of two. The MCP plane kept a verbatim fork of the fixed-window limiter, with its own bucket map. Both now share
worker/src/rate-limit.ts. The sharedallowRatetakes an optional call weight so a JSON-RPC batch is still charged per method — and a batch that does not fit the remaining budget is rejected without being counted, so it cannot eat into the next caller's allowance. -
One abort/timeout combiner instead of eight. Six modules each had their own
requestSignal/withBudget/ hand-rolled budget block, and four of them silently dropped the caller's abort signal on runtimes withoutAbortSignal.any. They now sharesrc/utils/fetch-timeout.ts, whose fallback keeps the parent signal honoured. The one place that deliberately lets the caller's signal win — the venue page walk, which owns its own budget — uses a separate, explicitly named helper so the difference is intentional rather than accidental. -
One error helper instead of four. Three modules carried their own
errMessagecopy and four carried their own abort-detection heuristic. They now sharesrc/utils/errors.ts, which separates the two questions the old copies conflated:isAbortErrordecides whether to rethrow (name-based only, so a network error that merely mentions a timeout is still a network error), whileisAbortLike/isTimeoutLikedecide how to word the message. -
One JSON builder per CORS policy, and one error envelope across the Worker. Eleven near-identical JSON wrappers were spread across the Worker (
apiJson, threejsoncopies,corsJson,proxyJson, twojsonErrors), and forty-five hand-written{ status: 'error', code, message }objects bypassed the canonical envelope.http.tsnow exportsscopedJson(cors)andplainJson: a route binds its CORS policy once and cannot answer with another route's headers by accident, and the inline envelopes go througherrorBody()so the error shape has exactly one definition. Two name collisions are gone —mcp/protocol.tsjsonError(an MCP tool result, not an HTTP response) is nowtoolError, andonchain.ts's localproxyJsonno longer shadowsproxy-router.ts's export — andproxy-router.tsno longer uses a reversed argument order. The session Durable Object answered 404/426 with a plain-text body; it now answers with the JSON error envelope like every other route. Header values and status codes on every existing route are unchanged. -
One time normalizer instead of five. The venue parser, the plot-time helper, the engine-time helper and the two on-chain helpers each implemented the same ms-to-seconds rule, and each rejected a slightly different set of values: a plot time of
0was accepted by one helper and dropped by another, and a time past year 2100 was accepted by some and rejected by others. They now share one function, so a time the venue accepts cannot be rejected by the engine and vice versa. The venue parser keeps its ISO date-string support, which is the one shape the other planes never see. -
The cached-datasets modal has one state, not three. The Topbar, the StatusBar and the Data Source Manager panel each owned a
datasetsOpensignal for the same modal, so the copies could drift: opening it from one surface left the other two showing their own closed state. They now share one module-scope signal, like the mobile-sheet state. -
One labelled-field component instead of two. The Data Source Manager and the Script Library each had their own
sc-fieldwrapper, byte for byte identical. They now shareScField. -
The cloud script list is newest-first, like the other backends. The Worker returned the scripts in whatever order the row scan produced, so the Script Library listed them differently depending on the backend.
-
A backend without version history no longer throws a special error. The service probed each backend for optional methods and then threw
VERSIONING_UNAVAILABLEat the call site. The backends now declare what they can do, the service reads that declaration, and an unavailable history is an empty list (andnullfor a single revision) instead of a runtime error. -
COOP flipped to
same-origin— cross-origin isolation is on. Chromium setscrossOriginIsolated(and unlocks SharedArrayBuffer) only for COOPsame-origin; the previously shippedsame-origin-allow-popupspair could never isolate, so every HP rung above the transferable copy was unreachable in a real browser. No AXIS surface depends on a popup opener — the GitHub/GitLab device-flow popup is openednoopener,noreferrerand the token arrives by Worker polling (waitForDeviceToken), never viawindow.opener; the detachable editor and panel companion windows are same-origin (BroadcastChannel / direct DOM wiring) and stay in the same browsing-context group undersame-origin. The flip movesCOOP_VALUEplus every static host in lockstep (public/_headers, the SW offline shell,axis_pwa_server.py, both nginx configs; Vite/Bun servers import the map) and re-pins the three header tests. The capability probe stays the runtime source of truth: a tab that fails isolation keeps the copy path (hp · copy+gpu/ HP-1), and the GitHub login is worth one manual pass on a deployed host.
Fixed
- WebGPU is reported as what it is: selected, but with no painter.
autoon a WebGPU-capable machine selected rung HP-4 and the chip readhp · sab+gpu— while no WebGPU renderer exists anywhere in the tree, so LWC painted and nothing said so. The chip now readshp · …+lwcwith ano painter yet — HP-4 reservednote whenever the probe outcome iswebgpu(the rung and the selected renderer are still reported, only the painter word follows what actually paints),hp.get's dormant-overlay verdict says the same, and the Settings GPU select labels the option(no painter yet — rung only). No behavior changed — this only stops the UI from claiming a backend that does not exist. - The GPU overlay draws LWC's candle widths, not a look-alike fraction. Even with the anchor fixed, the overlay's body was
0.75 · barSpacingand its wick0.125 · barSpacing, while Lightweight Charts computes the body withoptimalCandlestickWidth(barSpacing, pixelRatio)(a spacing-dependent coefficient, a flat 3-device-pixel special case, and device rounding) and draws a flat one-CSS-pixel wick — leaving a visible fringe on every candle edge.src/hp/render/lwc-candle-width.tsmirrors LWC's formula verbatim; the overlay derives the pixel ratio from LWC's own pane canvas (notdevicePixelRatio: LWC's canvas can be 1× on a 2× display) and passes CSS widths through the viewport, which the shader consumes as NDC half-width uniforms. Measured on the rendered chart: body 5 CSS px on both painters, wick 1 px on both (direct device-row runs, not column bucketing). The vertical mapping now also usescoordinateToPrice(h)so the GL viewport's bottom edge is LWC's price ath, noth − 1. - The GPU overlay's anchor now reproduces LWC's own
indexToCoordinate— no more doubled wicks. The overlay derivedbarSpacingaswidth / (to − from)and anchored records atwidth − (to − i)·spacing, but LWC's visible range spanswidth / barSpacing − 1(half a slot inset on each side), its bar x iswidth − (to − i + 0.5)·barSpacing − 1, and the pane content box is the pane canvas (554 px here, not the 555 apaneRect − timeScaleestimate produced). The result was a ~5 px shear across the pane — near-zero mid-chart, a couple of pixels at the edges — which read as "slightly wrong" overlaid candles.overlayViewportnow recovers the real spacing from the range, folds the half-slot phase and 1 px inset into the right-edge anchor, andpaneContentSizetakes the geometry from the pane's own canvas.tests/hp/gpu-parity.test.tspins the identity against LWC's formula to 1e-9 and the on-screen check (column-profile correlation + crops) went from 0.55 to 0.84 with the wick/body doubling gone. - The HP-2 hot ring now allocates when the wire client is constructed, so the sampler and the GPU overlay actually engage.
IngestHostClientallocated the hot SAB insideconnect(), butbuildWirereadsrawClient.hotBuffersynchronously right aftercreateLiveWsClient— before anyconnect()— so the read was alwaysnull:startHotRingPaintnever started,getLiveHotBuffernever exposed the ring, and both the HP-2 rAF copy-out and the HP-3 GPU overlay stayed dormant in the real app while the unit tests (which check afterawait connect()) stayed green.ensureHot()moved into the constructor (still gated on flag + isolation + SAB mode, still OOM-clamped to rung 1), and the regression is pinned by assertinghotBufferis non-null before the firstconnect(). - The GPU overlay places the hot ring as the chart's tail window, not as the whole series. The first production measurement (
hp.geton a live 9 500-bar chart) showedring.aligned: falsewithhotCount: 501,hotFrom: 0: the guard comparedgetBarsLength()against the engine snapshot's own index space (hotFrom + hotCount), which only ever matched when the snapshot happened to equal the chart window — the overlay therefore never painted on a real REST-loaded chart (the e2e staged equal counts and missed it). The ring is now placed by identity: the ring's newest bar must equal the chart's newest bar (tail time), ring slotjis chart indexchartBars − hotCount + j(ringWindowPlacement), and a viewport that starts left of the window clears instead of shifting candles — cold paging remains unwired, so zoomed-out views over more bars than the ring holds still draw nothing (LWC keeps painting).hp.getnow reportsring.tailTime/ring.chartTailTimeand a tail-identityaligned, and the@gpue2e covers the production shape: a shortened snapshot clears the canvas (alpha 0) and a wheel zoom into the tail repaints it. - A background backfill can no longer fail an unrelated test's fetch assertion.
tests/onchain-load-symbol.test.tsstarts aloadSymbolDatathat queues a Data Source Manager walk; the job outlived the test and kept paging after later files had restored the real GeckoTerminal source, so whichever file installed a fetch mock next counted a strayapi.geckoterminal.comcall. That is whysetScriptChartVisible > show does not run library() sourcesfailed in roughly every other full run with nothing to do with indicators — and the same leak failed its siblingshow while live…case instead. The case now resets the DSM inafterEach, the visibility describe resets multiplex rerun/live-bar timers inbeforeEach, and the assertion prints the offending URL and stack instead of a bare counter. - A long session no longer leaks a client, a timer or a memo per key. Six module-level maps grew with the number of distinct keys a session touched and nothing ever evicted them: the engine WebSocket clients (one socket plus its pending map per endpoint), the data-session series memos (one per endpoint + series), the dismissed job ids (one per job, forever), the pending live-bar writes (one per source + symbol + interval), and the bar-close verifiers (one per engine + series). They now share
src/utils/bounded-map.tsand are capped. Two of them needed more than a cap: an evicted WebSocket client is closed so the socket does not outlive it, and an evicted pending live bar is flushed rather than dropped — it is still a bar the dataset should receive. - A Worker route can no longer be reached with an uncapped body. Four routes read the request body outside the shared cap —
/api/keyscreated a key from an unbounded body,/api/git/oauth/*parsed the device-flow body unbounded, the MCP bridge forwarded an unbounded invoke payload, and the MCP plane carried its own hand-rolled copy of the byte check. All four now go through the shared capped reader and answer413 PAYLOAD_TOO_LARGEfor an over-cap body. A truncated device-flow body used to look like a request that carried no client id; it is now an explicit413. - Live indicators no longer drift away from the candles on the stream path. The stream plane replicates the engine's series (the wire has no client-side tick), but the stream branch returned before the engine-owned branch that ticks the chart's engine data session — so on the stream path nothing drove the engine and its series froze a few bars behind the venue. The script sessions that evaluate over that series froze with it, while the chart kept advancing from the venue, and the apply path tail-aligned the stale values onto the chart's tail: the indicators were displaced by the freeze gap (measured live: 38 bars after 38 minutes, growing with every new bar) and the newest bars carried no indicator value at all. The stream path now drives the engine's series on every venue flush (the
series-drivemodule existed for exactly this but was never wired in), and the re-run awaits that drive so the scripts evaluate against the freshly driven series. - Re-apply is no longer skipped once the rolling window slides. The dedupe compared only the bar length, but the rolling window slides with a constant length — so every reapply after the first looked "unchanged" and was silently skipped: the UI re-run button and the MCP
indicators.runcapability became no-ops, and the indicators kept the window they were last computed on while the candles moved on. The guard now compares the window identity (head bar time + length): a repeated call on the same window is still a no-op, a slid window re-runs.indicators.runalso reports how many scripts actually re-ran (reapplied). - Strategy events stay on the chart on every tick instead of flickering on/off. Silent live re-runs replaced all trade markers for a strategy with only the events from the current evaluation, so entry/exit labels disappeared between bar closes and only reappeared on the next close. Silent re-runs now merge new markers with existing ones (same-time markers are replaced, others are kept), so strategy actions are always visible; interactive runs still replace wholesale. Also
live.rerunOnis always honored —every-tick(the default) is no longer silently downgraded tobar-closebased on engine capability probing. - A new candle now advances the chart instead of freezing it.
appendLiveBarpainted every genuinely new bar (appended) withseries.update, which can only overwrite the last bar in place and never adds a time slot, so the chart stuck at the last loaded time and each new candle rewrote that same slot (the right edge stayed at11:30while the price there changed). New bars now go through the same full-windowsetDatapath as a window slide, so the candle lands at the right edge withinrightOffsetwithout auto-scroll; forming-tick updates (updated, same time) still use the O(1) in-place path. - The engine flush names every local dataset before it clears anything. The purge list was built after the memory mirror had been wiped, so IndexedDB-only series (the mirror keeps 48) were never deleted on the engine, and the result still said they had been. Settings and
engine.flushnow capture memory and IndexedDB keys first, delete those engine series, then wipe the local stores. A list that cannot be read is reported as a failure instead of a clean delete. - A page that starts during a flush cannot put the series back. The generation check ran only when a write was queued, and a bars-cache put that had already copied its batch could commit after the clear. The flush bumps that generation and clears the mirror after the removes, after the remote deletes, and after the cache wipe. A write that loses the race deletes its key, and a batch copied out before the clear is dropped.
- Spot and another market no longer share one dataset. Reads and writes never passed the provider lock, so linear and inverse bars merged into the public spot series while the engine key used a different market. Non-spot and keyed series now append
|market|authMode. The engine wire still mapslineartoswapandinversetofutureat that boundary only. - A Data Manager job follows the dataset that was loaded. Backfill and apply-when-complete used the chart's current market, so a linear row grew the spot series and could paint onto a chart that had since switched market. The job keeps the selection's lock, and a completed job does not paint when that lock is no longer the chart's.
- An IndexedDB read failure no longer looks like an empty dataset.
getCachedBarsreturned[]on a failed read, and the next page merge replaced the series. A failed read now throws and the write is refused. A genuine miss, and a browser with no IndexedDB, still look empty. - Replacing a dataset keeps a DEX pool address's case. The replace path uppercased the stored symbol while the write path did not, so list and delete rebuilt a different key for an address that contains
:or/. - A dataset repaint no longer drops chart bars the dataset is missing. The merge kept only bars strictly newer than the dataset tip, so a same-time forming candle jumped backward and an interior hole-fill was punched back out. Equal times keep a closed dataset bar over a forming chart bar, and otherwise keep the chart bar.
- A script subscription grows with the chart window.
history_barswas sent only on the first subscribe, which usually ran against the short first paint, so the indicator stayed blank on the rest of the window. The depth is part of the subscription identity and is the chart window even when the store is still shorter. A script resubscribe does not cool the series socket. - A new candle no longer shifts the indicators. Every forming tick pushed the open bar's time onto the candle axis again, so the script series was one row per tick instead of one row per candle. The next candle repainted that series onto a longer index than the price pane, and the plots jumped. A forming tick now keeps the time it already recorded, and a new candle extends the axis by one bar.
- Zooming while the chart loads no longer blanks the candles. A history replace paints the new candles while volume or an indicator still holds the previous times. That frame throws inside the chart library, and the price series stays empty until the next bar updates both. Those extra times are dropped before the candle replace, and a zoom already in progress is not fitted away.
- Donchian keeps moving after a new candle. The live snapshot stored the shorter history the subscribe asked for, while the session and every later update were numbered from the longer series. Each new candle was rejected, so the channel stayed where the load left it and drew as a single point out to the last bar. The snapshot now counts the plots it actually contains, and a bad time axis no longer drops the value update.
- Bollinger %B no longer repaints when a candle closes. The live script snapshot carries bar times, and the deltas that follow carry only the new values. The apply path dropped that axis and zipped %B onto the candles by index, so each new candle moved every closed value onto the next bar. The price series was never moved. Closed %B now stays on the bar it was computed for, and only the new candle takes the new value.
- Pine plot colors are no longer overwritten by the palette on re-run. The legacy
plots[]overlay path dropped the engine'splot_meta.color, so any script evaluated without namedseriesfell back to the palette instead of the script's color. It now uses the same precedence as the named-series path: explicit panel pick (custom) → Pineplot_meta.color→ palette. Auto-persisted (non-custom) colors no longer shadow a fresh script color, matching the earlier named-series fix. - Re-apply runs only once at a time. Multiple call sites (load-symbol ×4, DSM, store, MCP) could fire during a single load, each re-running every script and flashing the chart through intermediate states. A guard now skips concurrent reapply passes;
stillCurrentstill supersedes stale ones. - Script colors survive a reapply when
plot_meta.coloris missing. The re-run store path previously fell toPLOT_PALETTE[colorIdx]when the engine didn't ship a resolvableplot_meta.color, wiping Pine colors (e.g.#6366f1) to palette colors on every reapply. It now keeps the stored color when the fresh run doesn't ship one. - Chart overlays preserve stored script colors when
plot_meta.coloris absent.buildOverlayPayloadsfell to the palette on the chart (even though the script panel stored the correct color) whenever the engine response lackedplot_meta.color— e.g. batched reapply. It now falls back to the stored (non-custom) color before the palette, across named-series, legacyplots[], and OHLC overlay paths. Precedence: explicit panel pick (custom) → Pineplot_meta.color→ stored color → palette. - Fill bands render from
plot_metawhen no fill series array exists.resolvePlotFillBandsonly scannedseriesentries, sofill(plot1, plot2, color=...)bands had no band to draw — fills are derived from edge plots, not a separate series. It now also scansplot_metafor fill entries and resolves their Upper/Lower edge series. - Re-apply runs only once per bar window. Multiple call sites (load-symbol ×4, DSM, store, MCP) fired sequentially during one load with the same bars, each flashing the chart through intermediate states.
reapplyChartScriptsnow skips whenstore.bars.lengthhasn't changed since the last pass. - Stale session/stream pushes no longer pin or freeze the chart. A live push whose engine shipped no bar-time axis over a moved window (gap fill, backfill, slide while the tab was idle) was index tail-pinned onto the chart tail, and Lightweight Charts spans the resulting
nagap as a long diagonal from the last pre-break value to the tip. Those pushes are now skipped instead (staleNoAxisinsrc/indicators/apply-context.ts, live pushes only — fresh full runs keep the tail-pin), so the last good plots stay on screen until a full run with an axis arrives. The same gap left the pane frozen instead when the values-only delta salvage dropped the axis permanently: the wire now resubscribes once per snapshot for a real full (axisResubPendinginsrc/engine/live-wire.ts) instead of painting unpaintable state on every tick, and a seq gap resubscribes instead of force-merging. Pinned bystaleNoAxisalignment tests, skip/paint live-push run tests, and wire resub-vs-paint tests (tests/runner-engine-owned-apply.test.ts,tests/engine/live-wire.test.ts).
[2.25.3] — 2026-10-20
Fixed
- A remote dataset document that is already gone is no longer reported as a failure. The dataset flush deletes the remote dataset documents; removing one that was never pushed (or was pushed before the sink existed) answered "not found", and the flush reported it as a write failure. Removing a document that is already gone is the state the caller wanted, so it is silent now.
- The reconnect test no longer counts another suite's sockets. The reconnect suite counted every socket the shared WebSocket mock had seen, so a reconnect timer another test file left behind could land in it and make the suite count one socket too many (a CI-only flake). The suite now counts only the sockets its own client opened, and waits for the reconnect instead of guessing the delay.
[2.25.2] — 2026-10-20
Added
- The dataset flush reaches the engine. AXIS caches the venue's OHLCV twice: in the local dataset store and in the engine's series store (the shared venue cache behind
GET /data/series). Flushing only the local side left the engine serving the old — stale, holed — window, and the live path mirrors the engine's copy. Settings → Data → "Flush dataset manager" now also drops the engine's series for every key AXIS knows about (per key — the engine's store is shared), and the MCP exposes it asengine.flush.
Fixed
- A dataset write that was queued before a reset no longer repopulates the store. The suite resets the shared singletons between files, and a write that was still queued when the reset ran landed in the next file's mirror — a dataset the next file never created, which then failed its "the mirror is empty" assertion (order-dependent, CI-only). A queued write now carries the store's generation and is dropped when the store was reset underneath it.
- A live apply keeps the script's input descriptors. The socket payload carries the values, not the run's metadata, so a live apply replaced the run's input list with an empty one (the Results panel and the Script Settings modal read it from there). The descriptors of the script's last full run are carried over.
- A live script subscription now covers the chart's whole window. The socket subscribed the script with the series depth — the short live snapshot (500 bars), which is right for the chart's own OHLCV (the chart is painted from REST) but wrong for a script: the engine's script state was 500 bars deep, so every live apply repainted the indicator over the last 500 bars only, blanked the rest of the window and collapsed the script's band fill with it. The script subscription now asks for the chart's own depth (capped by the Rolling window).
- A script's input values now reach the engine. The live socket subscribed the script without its inputs, so the engine evaluated it with the Pine defaults: changing an input (a Keltner channel length, an EMA period) repainted the chart with the new values and the next live tick overwrote them with the old ones — the change looked like it did nothing. The values now travel with the subscribe frame, and a changed value re-subscribes (the engine keys its session on the inputs).
[2.25.1] — 2026-10-20
Added
- An uncaught error now carries its stack. "Uncaught error: Value is null" named no series and no frame; the first three stack frames are appended to the log line, so the next occurrence is attributable.
- The chart audits its own time axis. After every apply, the co-painted series and the volume are checked against the candle axis; a series that holds a bar the candles do not have is named in the log (
time axis drift: price/overlay_x: 1 row(s) outside the candle axis …).
Fixed
- The engine's live snapshot no longer replaces the chart's window. The engine's live series is a short tail (the socket subscribes with ~500 bars), and the chart painted that tail as a replace: a freshly loaded 20 000-bar dataset was thrown away and the chart showed the engine's stale 500-bar window — with the engine's holes in it. The snapshot is now merged into the chart's window: it fills the holes the chart has, extends the tail when the engine is ahead, and never shortens the chart's own history.
- The volume series carries exactly the bars the candles carry. The volume skipped a bar only when its open/close were not finite, while the candle series rejects a bar whose high/low are not finite too — so the volume could hold a bar time the candles did not have. The library's shared time scale is the union of every series' rows, and each renderer asks its own series for the bar at the rendered index: a volume row at a bar the candles dropped makes the candlestick renderer fail with
Value is nulland no other clue. Both series now use the same rule. - A live tick lands on the same bar in the candle and the volume series. The candle took its time from the mapped bar while the volume took the tick's own time, so a rejected tick could leave the two series one bar apart.
- The co-painted series land on the candle axis. The script series were clamped to the candles' range, so a row at a bar time the candles do not hold (a venue hole, a stale window) survived and extended the shared time scale past the candles — the render then failed with
Value is nullon every paint. The clamp now drops the rows the candles do not have, and the overlay lines are projected onto the candle axis (one row per candle, a Pinenawhitespace row where the script had no value). - The new chart-window tests no longer leak state into the suite. The test files that exercise the Rolling window left a small window in the shared store, so a later test file that loaded bars got them sliced — order-dependent failures on CI (and a coverage-gate drop). The store is now restored after each file.
- A dataset repaint no longer paints over a chart that moved on. The progressive repaint of a completed backfill ran on a timer: if the chart had switched to another symbol in the meantime, the repaint put the old series on the chart (and the store's bars with it). The repaint is now dropped when the chart no longer shows that series.
- Three test files no longer leak state into the suite. The dataset store, the provider session and the worker's proxy rate-limit buckets are module singletons: a dataset cached by one test file made the next file's backfill find the range already complete (and skip the walk), a venue left behind made the next file label its chart with the wrong exchange, and the worker's per-IP rate-limit bucket made a later worker test answer 429 instead of 404. The state is now reset per file.
[2.25.0] — 2026-10-20
Added
- AXIS Settings → Data can flush the dataset manager. A new “Dataset manager” section (next to the exchange keys) wipes every cached OHLCV dataset — bars and metadata — from the memory mirror, the local IndexedDB cache, and the remote dataset stores (git / cloud), behind a confirm step and with a result line (
Dataset manager flushed · N datasets removed). Chart bars stay until the symbol is reloaded.
Changed
- The Rolling window is now the chart window, and it slides instead of growing. Settings → Rolling window used to bound only what the engine was asked to evaluate: the chart itself kept every bar a live tick appended (up to the 100k safety rail) and only the next dataset repaint re-capped it. The window is now one number (
chartWindowLimit) shared by the load paths, the live append path, the background catch-up and the engine input, and a new bar at the cap drops the head instead of extending the series. The status-bar inline edit and the Settings page both re-slice the chart through the same helper, so the setting and the painted window can no longer disagree.ROLLING_WINDOW_MAXis nowHISTORY_BARS_MAX(100k) — the setting cannot promise a window the chart would refuse to paint. - A live tick is applied through one audited path.
store.appendBarreturns a verdict (ignored/updated/appended/slid) andchart/live-append.tsis the only place a live bar reaches the chart. A rejected tick no longer moves the price card, no longer evaluates alerts, and no longer overwrites a dataset bar. - The chart data session follows the chart window. The engine session was provisioned with a fixed 5 000 bars while the chart window is 10 000, so the engine's store could only serve half the configured window. The depth is part of the session identity now, and changing the Rolling window re-provisions the session.
- A dataset write no longer re-repairs the whole series. The stored series is already repaired, so a page write merges the page in one linear pass instead of rebuilding a map, re-sorting and re-sanitizing every bar.
Fixed
- The dataset repair no longer deletes venue bars (the phantom-gap loop). The phase-aware snap moved an off-phase bar onto the nearest grid point even when that slot already held a bar; the dedupe then dropped one of the two — losing a real venue bar, shifting another bar's OHLCV onto the wrong candle, and leaving a hole the gap detector reported as fillable. The DSM chased that hole on every round and the job always ended with “Partial: N gaps remain”. The snap is now collision-safe (a bar only moves onto a free slot, and keeps its own timestamp when both neighbours are taken), never snaps onto a non-positive time, and the repair is idempotent.
- The backfill walk stays on one grid. The walk advanced its cursor by one second, so a venue that answers “the newest bar with open time ≤ endTime” returned a bar one second off its own grid — a one-second hole in the dataset, which is a phantom gap the job then chased. The cursor now advances by one step (one duplicated bar per page, dropped by the merge).
- A widened backfill window is fetched by the running walk. A second
startBackfillfor the same series widens the job's window while the walk runs; the walk now re-reads the bound on every page instead of keeping the one it was launched with. - A zero Rolling window falls back to the default.
0used to mean "no cap" for the chart as well, which could grow the series without limit; the engine keeps its "no cap" escape hatch, the chart stays bounded. - Monthly intervals count calendar months. The expected-bar count used a fixed 30-day step, so the density floor was unreachable for most windows and a
1Mjob could never report "complete". - A session venue's Friday closure is no longer chased as a gap. The weekend classifier excused only gaps whose every missing slot was a Saturday or Sunday, so a venue closed from Friday's close to Monday's open produced a fillable gap: every job burned all six gap rounds on a bar the venue does not have. A missing Friday bar now belongs to that closure; a missing Monday bar is still chased.
- The dataset job no longer re-walks a complete dataset. The pre-walk completeness check validated the series without the venue calendar class, so a session venue's weekend closure counted as a hole and every job re-walked the whole range.
- A failed dataset read can no longer truncate the dataset. A sink that could not be read looked exactly like an empty dataset, so the merge base was empty and the last backfill page replaced the whole series. The sinks now distinguish “absent” from “broken”, and a write against an unreadable sink is refused instead of applied.
- A dataset write is atomic and serialized. The sink replace was a clear-then-write (a window where the dataset did not exist at all, and a concurrent write could be clobbered); it is now a single record put. Writes are serialized per key, so two concurrent writers (a backfill page and the debounced live-bar write) can no longer drop each other's bars. The memory mirror evicts LRU instead of FIFO, so the hottest series is no longer the first to be dropped.
- A closed candle is immutable in the dataset. A stale REST page carrying the partial (forming) values of a candle the venue had already closed could overwrite the final bar; a closed bar now wins against a forming one.
- The live tail survives a dataset repaint. The dataset store only learns about a live bar when the Data Manager source is active, so a background backfill slice landing in between repainted the chart from a series that was one bar behind and the forming candle vanished. The repaint now merges the chart's live tail back in and re-caps the result to the window.
- A late tick cannot rewrite history. A tick for a bar behind the tip was appended behind it, which put the series out of order and broke every index-based zip. Such a tick is now dropped, and a closed tip is never reopened by a forming tick.
- The forming tip is replaced, not merged. Solid's store setter merges an object into the existing node, so a tick without a volume kept the previous volume and the chart drew a stale histogram. The tip payload is normalised before the path-set.
- A slid window no longer drifts the indicator pane. When the window slides, the candles move forward by one bar while the script series do not (the engine has not re-run yet); the prune left them one row short, which shifts the pane's logical range against the price pane. The pruned series are padded back to one row per candle with Pine
nawhitespace rows. - A Pine
nais a gap, not a zero. A non-finite plot value was coerced to0, which draws a line at the axis; it now becomes a whitespace row (an omitted value).nullis still coerced to0because the library rejects it. - Strategy fills follow the bar, not a stale index. The engine stamps
bar_indexrelative to the window it evaluated, and the chart window slides (it drops its head on every new candle) — so an index pointed at a different bar in the current series: every entry/exit marker landed one bar early and the PnL was computed from the wrong bars. The absolutebar_timeis now the authority (the index is the fallback), and the report is built from the run's bars rather than the live store. plotshape()markers survive the apply that paints them. The owner-scoped series cleanup also cleared the script's shape and trade markers — and the apply path calls it on the price pane right after painting them, soplotshape()on every non-overlay script (RSI/MACD sub-panes) never rendered, and "Add Indicator" lost its markers before they could be re-homed. The series cleanup is now series-only; the detach path still clears the markers.- Two scripts that plot the same series name no longer collide. The marker id was plot-scoped (
<plot>_<index>), so the price-pane merge kept only one owner's markers and the rest vanished. The id is owner-scoped now. - The legacy
plots[]fallback is length-checked. Aplots[]array of a different length than the chart window was index-zipped, mis-plotting every value with no error and no visual cue. It now goes through the same alignment as the named series. - A re-owned indicator's
bgcolorband stays on its own pane. The re-own path left the band on the price pane, where the removal loop (which only inspects the target pane) never removed it — a permanent duplicate band and a leaked series. - A destroyed pane releases its drawing layer. The script
DrawingLayer(SVG overlay, DOM listeners, a reference to the removed series) outlived the pane; a recreated pane with the same id got the stale layer back and projected drawings against a disposed scale. - A bar replay cannot leak the future. The
bgcolorbands were built from the full series and had no clamp, so they extended the shared time scale past the replayed candles — the user could scroll into the colours of bars the replay had not reached. The bands are clamped to the painted range now. - A bar-colour repaint updates the painted range. The overlay clamp used a stale range after the candles were re-tinted, so it either dropped the newest overlay points or kept rows that no longer had candles.
- A hidden script no longer fires its alerts. The Pine alert notification ran before the "hide / detach after the engine returned" check.
- A live alert sees the bar it fired on. The alert context was built from the store, which is appended after the alert hook — so a condition on
bar.open/bar.high/bar.lowsaw the previous candle. The tick's own bar is part of the context now. plotshape()follows Pine's defaults. The marker position is no longer inferred from the arrow direction (Pine's default islocation.abovebarwhatever the style), and the plot title is only used as the marker text for the label styles (shape.labelupand friends) — in Pine the title is the plot's name.- A fill band is only drawn where both edges exist. Two edges of different lengths were zipped positionally, which drew half a band (one edge present, the other missing) instead of a clean gap.
- A hidden plot keeps its palette slot. The colour index only advanced for the plots that were painted, so hiding one plot changed the colour of every later plot of the same script.
- A cross-indicator
input.sourceseries is re-joined by time. The cached series was zipped against the consumer's window by index, but the chart window slides — so every value of aplot:<script>:<plot>source was off by the slide. The cache now carries the bar times it was written for and the consumer re-joins by time. - A session run without an engine bar axis says so. The series is tail-pinned onto the chart (the only anchor left), which is only correct when the session's window is the chart's window. The apply now logs a warning instead of mis-plotting silently.
- A bar replay cannot leak the future through the compare overlay. The overlay was built from the full series, so the compared symbol's future extended the shared time scale past the replayed candles.
- The compare overlay notices a mid-history correction. The memo compared the refs, lengths and endpoints, so a gap fill (same endpoints, changed middle) left the compare line stale.
- A line-break primitive is keyed by pane. The same owner and plot name on two panes shared one entry, so one pane detached the other's primitive.
- The Pine drawings fallback calls the layer once. Without a sub-pane layer everything went to the price pane in a second call, which replaced the first call's list and overwrote the kept count.
- Scrolling to a time before the history falls back to the ±14-day window instead of snapping to the oldest bar.
- The paint dedup is per chart slot and samples the whole window. The signature was a module global (the second slot's paint could be skipped, leaving it blank) and sampled one middle row, so a correction anywhere else in the window left the stale candle on screen. It now samples up to nine evenly spaced rows per slot.
- The dataset job cannot be resurrected after a cancel or a dismiss. The final validation flipped a cancelled job back to
completeand painted a chart the user had just cancelled, the cache-only job had no abort check at all, and a late status sync re-inserted a dismissed job into the list. - A paused job can be resumed. The pause request only reached the runner at the next page boundary, so a resume in that window was dropped and the job stayed paused until the user pressed resume again.
- The gap counter counts the right store. The gap-fill progress was measured against the bars cache while the job writes to the dataset store, so “gaps filled” stayed at 0 in session persistence mode.
- Adjacent gaps are filled in one walk. The gap-fill phase now merges adjacent holes before walking back, so a fragmented hole costs one request instead of one per fragment.
- The background catch-up runs once. The in-flight gate was armed after the first
await, sofocusandvisibilitychange— which fire together when the user tabs back — both started a repair (double network traffic, two DSM jobs). - The internal gap fill no longer skips single-bar holes, and the closed tail / filled holes it fetches are persisted instead of being thrown away on the next load.
- The interval parser is case-insensitive for the fixed units (
1His an hour, not a day) and warns on an unknown interval instead of silently assuming a 24× wrong step. Monthly intervals count calendar months, so their density floor is reachable. - A DEX pool symbol keeps its case in the dataset key. Uppercasing a base58 /
0x…address made two distinct pools share one cache key — the store served the wrong series and merged one pool's bars into the other's. The provider lock (market/authMode) is part of the key for a non-spot or keyed series. - A persistence-mode switch migrates every dataset. Only the memory mirror was copied, so a dataset that lived solely in the old sink (evicted from the mirror, or written before it was warmed) was invisible in the new mode.
- The chart window slide copies the window once, not twice. The head drop and the tail append were two full copies of the series; the tail is now pushed into the sliced array. The 500k-tick firehose bench (50k window) halved from 15.7s to 8.0s.
- The backfill is an order of magnitude cheaper per page. A page write rebuilt a Map, re-sorted the whole series and re-sanitized every bar; the stored series is already repaired, so the page is now merged in one linear pass. The completeness pre-check and the validation round no longer re-repair a dataset that is already sorted and unique.- The chart window slide no longer rebuilds the overlay axis per bar. The bar-time cache recognizes a slid window (the head moved, the length did not) and shifts the axis instead of reading every bar through the store proxy.
- An engine series that covers a different window is aligned by time. A same-length series from a session that slid independently of the chart was index-zipped onto the chart axis, which mis-plotted every value; the engine's own axis now decides when the windows disagree.
- Deleting a dataset in the Dataset manager removes the full set, metadata included. The delete only cleared the local bars cache, so the dataset-store memory mirror kept the series and its metadata row survived — the dataset came back in the list on the next refresh. The delete now sweeps the mirror and every sink (session / local / git / worker), including the
|market|authModecache-key variants, and waits for in-flight persistence-mode copies so a background copy can no longer resurrect a dataset that was just deleted.
[2.24.8] — 2026-10-19
Fixed
- The desktop release pipeline works again. The changelog extraction read the file with the Windows default codepage (cp1252) and died on the typographic quotes in the release notes, and the matrix jobs raced on creating the GitHub Release (
HTTP 422 — Release.tag_name already exists) — the tag shipped without its installers. - The feed chip says what the data path is, not a stale state word. A live engine feed read
degradedwhenever only the engine’s own copy confirmed the close, so the chip disagreed with the engine plane (ws/open) while the data was flowing. It now readsliveand flags the weak tier aslive·unverified; a feed that never came up keeps its state word plus the time it has been stuck (connecting · 45s). The opened panel lists the facts behind it — state, freshness, verification tier, progress, the engine hop and the series identity. - The session delta merge keeps the engine’s bar-time axis. A merged engine-owned payload described only the tail, so the apply path fell back to an index join — which displaces the plot whenever the engine’s stored series has gaps (a missed minute). The axis is spliced with the tail like the plot columns.
[2.24.7] — 2026-10-19
Added
- GitHub Release notes use the CHANGELOG version section (
scripts/generate-changelog.py --extract) instead of the generic “Desktop build of AXIS (Tauri 2)” stub. - MCP connect / session chip sits on the command bar trailing edge (very top row, above Studio). Overlay indicator plots stack above price candles (
setSeriesOrder); Layers lists Scripts above Panes. - Topbar Market after Venue. Binance (and OKX / Bybit / MEXC) can switch Spot, USDT-M, and COIN-M. BTCUSDT on Binance was always spot (
api.binance.com/stream.binance.com); USDT-M usesfapi/fstream, COIN-M usesdapi/dstream. Engine series keys send PYNE/CCXT names (swap/future).
Fixed
- CI is green again. The coverage gate ran the test files in parallel, so files sharing module state raced (
clampPointsToPaintedRange) and every run since 2026-10-15 failed on a flaky assertion. The gate now runs serialized (--max-concurrency=1), and the engine-owned live plane (wire, series subscription, snapshot paint, close verification) got real unit coverage — the coverage floor (89%) was below it and is met again. - Pyodide compile mode no longer crashes on
alert()/alertcondition(). The wheel hands the compiled__alertsback as an empty float array, and the runtime’sor []on it raisedValueError: The truth value of an empty array is ambiguous— the whole compile run failed in the browser (Pyodide has no Numba, so this path is the norm there). The runtime now normalizes the packed event containers without truthiness checks. - A broken engine WebSocket no longer re-opens on every run. An abnormal close (1002 protocol error, 1006, 1011) after the socket opened now marks the shared
/ws/runclient dead for the cool-down, so the next runs go straight to REST instead of re-connecting to an endpoint that accepts the handshake and then drops every frame. Normal closes (1000 / 1001) keep the client reusable. - Interval switch no longer paints a stale cache tail plus the live candle. Switching 5m → 1m used to show yesterday’s last cached bar, a hole, then the current price because live started before venue pages closed cache→now. The load path now fills that tail before
startLive. - Internal holes in the painted window are filled from the venue. A 1m cache that already reached “now” still left a ~2h blank (133 bars); load now fetches those ranges before live starts.
/ws/livesubscribes a 500-bar tail instead of the full 5k window. A 5000-bar 1mfulltimed out; the chart stays on the REST/DSM window and the socket can go live.- Transient
/ws/liveerrors no longerdisconnect()the client. Snapshot timeout, empty snapshot, and subscribe/connect errors still fall back to venue+REST and debounce subscribe, butstoppedstays false so the socket can stay up or reconnect. Onlyreset()/stopLivestop the client. /ws/livesnapshot wait is 120s, and a script miss no longer cools the series wire. History ensure of a painted 5m window often exceeds 30s; a Keltner (script) snapshot-timeout used tofail()the whole socket so the HUD stayeddegraded · venue+REST.- Plot fills tail-align when the engine series is shorter than the chart. Adding Keltner (or any
fill()) on a 5000-bar cache plus DSM/forming bars no longer leaves the band 2–3 candles left of the last bars. - Worker and engine API keys survive a new tab, PWA update, and browser restart. The secret vault was memory +
sessionStorageonly, and persist strippedapiKeyfrom the app-state JSON — so the first hard reload after that migration (or the 2.24.6 update) looked like the app deleted the keys. They now also live in a dedicatedlocalStoragebag (axis.secret-vault.v1), still out of workspace snapshots and the main persist payload. Exchange CEX keys stay session-only. /ws/liveno longer dies onRedesign2 auth timeout. Browser sockets cannot sendAuthorization, so the client now putsapi_keyon the upgrade URL (the engine already accepts that handshake) and does not blockconnect()onauth.ok. The auth frame is still sent. A close rejects any leftover auth waiter.
[2.24.6] — 2026-10-18
Added
- MCP can now read and drive the live plane itself — the diagnostic and recovery surface that until now needed DevTools or SSH into the engine host, as eight new
app_invokecapabilities (catalogsrc/mcp/catalog.ts, implementations in the newsrc/mcp/live.ts, dispatched fromsrc/mcp/dispatch.ts):chart.lag— the lag ledger for "why is the chart a bar behind?": chart tail vs the wall-clock bar slot vs the venue's last tick (a fresh tick wins, so session venues holding their last close are not accused), plus the engine's own series bounds. Returns a verdict code (live/one-bar-behind/behind/empty) with every raw number it used. The engine read is one in-tabGET /data/series(6s budget, fail-soft;engine: falsefor the local-only view) — the API key never leaves the tab and is reported ashasApiKeyonly.chart.bars— bounded OHLCV windows (taildefault 50, optionalhead, cap 500) plus a missing-slot map (gaps.total+ up to 50{after, to, missing}runs) and a forming-bar flag, so "is the forming bar on the chart / where are the holes" is one call instead of a screenshot.engine.get— sanitized engine plane: active id, endpoint,hasApiKey, mode, config field names (never values), cached capabilities, router decision, cached/health, the chart data session the drive ticks (getChartSessionInfo()), live prefs, telemetry.engine.series— the engine's own store for a series (chart default, or{exchange, market, symbol, interval}):count/oldest/newest/updatedAt,lagBars,behindChartBars,stale(same verdict as the paint guard) andupdatedAgeSec— the ground truth behind "the engine feed is frozen" (a staleupdated_atmeans nothing is writing it at all). Never puts the key on the URL;NO_ENDPOINT/ENGINE_UNREACHABLEinstead of a silent{error}.engine.tick— force one drive tick past the 10s throttle (minGapMs: 0, riding the drive'stickseam so the throttle bookkeeping still runs) and report the raw engine envelope (appended/forming/unchanged/stale) plus before/after series bounds andadvancedBars— an agent pokes once and sees whether its tick moved the series.engine.repair— force the stale-series broker (force: true, 60s cooldown skipped) and reportvia(localupload vs engine-sidefetch) with the same before/after bounds.stream.get— the live plane in one read: route decision, wire state (open/cooldown/failures/socket counters/subscriptions by id + last applied seq — never frame payloads), every feed chip, per-script statuses, stream/engine telemetry, last tick. Anullwire is itself the answer to "why is there no subscription?" (it builds lazily).stream.reset— drop the engine socket and every subscription, then probe one re-subscribe (default;{resubscribe: false}to skip, gated on live + the stream route). The wire singleton is discarded, not just closed —Redesign2WsClientis terminal afterdisconnect(), so the next tick rebuilds from the current engine config. This recovers in one step the states "no live ticks forever" reduces to: a changed API key/endpoint, a socket superseded by a second tab, a wedged reconnect loop, or a failure cooldown.- Supporting introspection (library-level, reused by the capabilities):
StreamLiveWire.snapshotState()/reset()(src/engine/live-wire.ts),Redesign2WsClient.stats()counters (src/engine/ws-client.ts, opt-in onWireClient),streamWireState()/resetStreamWire()(src/streams/stream-live.ts),getChartSessionInfo()(src/engines/chart-data-session.ts). - Mutating capabilities (
engine.tick,engine.repair,stream.reset) aremutates: true, so the in-page read-only host still denies them (MUTATION_DENIED); every engine call reuses the active engine's endpoint + key in-tab. Covered bytests/mcp-live.test.ts(24 cases incl. the no-key-on-URL assertions and mocked engine chains) and four new wire cases intests/engine/live-wire.test.ts; documented indocs/enduser/guides/mcp.mdxand the capability table indocs/worker/mcp.mdx.
Changed
-
The in-browser Pyodide engine now vendors the PYNE 0.6.12 wheel. Rebuilt with
scripts/sync-pyne-wheel.shfrom the sister pyne repo (1.40 MB, verified byte-identical inpublic/vendor/andvendor/), so the browser engine runs the same release as the Pro API. The wheel-side payload since 0.6.9:chart.point.copy(pt)keeps its argument._call_prefers_array_methodreturned True for any attribute receiver, sochart.point.copywas lowered to the collection-method path and emittedarray_copy_value(None)before thechart.pointbranch could run — the point becameNone,pt.priceread throughudt_get_field(None, …)as NaN, and the strategy/plot silently plotted nothing. Thechart.pointnamespace is now excluded from that path (_is_chart_point_namespace).- A numeric multi-return UDF is no longer forced into object mode.
f(float a, float b) => [a + 1.0, b + 2.0]is markedfunc_returns_sequenceso a single-targeta = f()keeps the tuple handle, but the same marker demoted the multi-unpack[u, w] = f()to object mode. Such functions are now recognised as numeric-tuple UDFs and the unpack indexes__tup[i]directly — theisinstance(__tup, (tuple, list))guard cannot be narrowed by Numba and alone failed the njit warm-up. - Interpret and compile agree on every titled
fill(). Interpret wrote the per-bar band colour into theseriescolumn while compile packs series into float64 arrays and therefore cannot emit a colour string, so the same script rendered differently per mode. The interpret fill column is now null too; the band is still described completely byplot_meta(kind="fill",plot1/plot2,color). pynescript.debug/pynescript.stats(the 0.6.10PYNE_DEBUGlogging and theGET /statscounters) and the 0.6.11util/datafeed.pyCCXT Pro fan-in ship in the wheel but are inert in the browser, which receives bars as inputs rather than through the engine datafeed.
The hard-coded wheel URLs in
src/engines/catalog.ts(prefetch warm list) andsrc/workers/pyodide-runtime.ts(micropip.install), the worker-test expectation, and the wheel references indocs/architecture/evaluation.mdx,docs/plugins/engines.mdx, anddocs/llm.txtmoved with the filename.scripts/sync-pyne-wheel.shnow warns against those three call sites instead of the long-deletedsrc/engines/index.js.
Fixed
- Live feed no longer sticks on
degraded · venue+RESTafter a keyless first tick. The/ws/livewire is rebuilt when the engine API key/endpoint appear,connect()waits forauth.okbefore subscribe, adatafeed-loadingstatus extends the snapshot wait, and subscribehistory_barsmatches the painted window so PYNE can emitfullimmediately. - REST data-session create no longer binds a compact symbol.
createDataSessionwas overwriting the canonical pair-form key (BNB/USDT) with the AXIS load-form ticker (BNBUSDT), so PYNE persistedbinance|spot|BNBUSDT|1mwhile MCP/stream readbinance|spot|BNB/USDT|1m. Create and tick now both sendcanonicalSeriesKey/pairFormSymbol. CompactBNBUSDTstill binds and looks upBNB/USDT. - Live-wire cooldown now pauses
/ws/livereconnects. P0 stoppedfail()from self-extending cooldown, butRedesign2WsClient.scheduleReconnectkept retrying after the first open.fail()now disconnects for the cooldown window;connect()aftercoolUntilcan open the same client again (or stream-live can rebuild a fresh wire). A reconnect timer already elapsed whendisconnect()ran re-checksstoppedbeforeconnect(), so it cannot un-stop and reopen/ws/liveduring cooldown or after MCP reset. - Live stream P3 — stop driving the engine from venue ticks.
streamSeriesTickno longer REST-ticks the engine series; the feed push owns advance. EnginesetDataruns on snapshot/repair only — tick deltas path-set the forming tip.SESSION_KEY_MISMATCHdrops the chart session so pair-form ticks recreate it. - Live stream P1 (client) — series and study push. Series
full/deltaframes now paint throughonSeriesFull/onSeriesDelta(no applier required) and advanceappliedSeq. Script deltas call the apply bridge immediately instead of waiting forscheduleRerun. A reconnect resumes every live sub; a close mismatch sendsrepair. - Live stream P0 — stop the bleeding (client only). Venue ticks no longer wait on the engine, the wire no longer re-subscribes or self-extends cooldown, series keys agree with PYNE, and REST fallback no longer looks like a dead EMA:
- Paint venue first.
flushPendingBarappends the venue bars (including same-slot forming updates) before kickingstreamSeriesTick. Engine work is fire-and-forget; a later snapshot re-applies the forming tip instead of gating LWC. ensureSeriessubscribe-once. A live series returns the cached snapshot; concurrent ticks join the in-flight wait;inboxWaitersfan out instead of stealing. First snapshot still paints; later ticks leave the venue tip alone.- Cooldown does not self-extend.
fail()while already cooled down throwscooldownwithoutfailures++or resettingcoolUntil. The previous latch was thousands of failures/s with the socket still open. awaitFullaccepts onlyfullor terminalerror. Leftoverdelta/closeframes stay in the inbox instead of failing asno-snapshot.- Pair-form series keys.
canonicalSeriesKey, streamchartSeriesKeyObject/seriesMeta, and MCPengine.seriesnow mapBNBUSDT→BNB/USDT, matching the PYNE store (binance|spot|BNB/USDT|1m). - HUD. Stream REST fallback is
degraded · venue+RESTon the feed chip and the script badge, not a stickycooldownon a live EMA.
- Paint venue first.
- Engine-owned live no longer drops the venue's forming tip on every engine tick ("the chart is cut off by a bar"). The engine's store holds closed bars only, while engine-owned live mode paints the venue's forming tip locally between engine ticks — so the chart routinely carries one trailing bar the next engine read does not have.
diffEngineOwnedBars()treated any shorter engine window as a reshape (full), so every ~10s engine round trip repainted the chart without the tip: the candle oscillated (tip → repaint → gone → venue re-append) and indicators re-ran on the truncated window. The diff now recognises a prefix-aligned shortfall — the engine window is exactly the chart minus its local trailing tail — asnoop: the prefix came from this engine and the trailing local bars are newer than anything it holds, so the chart keeps them. Misaligned shorter windows (history rewrite, window slide, depth change) still repaint in full. Regression-covered intests/multiplex-engine-owned-paint.test.ts(diff-level alignments + a live cycle where the engine window retracts to closed bars while the venue tip survives). - A frozen engine series can no longer back-date the chart.
DATA_MISSis a count check (have < need), so a series that is deep enough but frozen — Pro's upstream feed stopped advancing, or a stale local window was uploaded before the Data Manager repaired it — never misses; AXIS paints the chart from that series in engine-owned live mode, so the chart sat on a days-old tail while the venue WebSocket and feeds looked connected (newestfour days back on $DOGEUSDT 1m). Staleness is now an explicit contract:- New pure predicates
src/engines/series-freshness.ts— stale means the newest bar lags the comparison bound (chart tail, venue trigger clock, or now) by more than 2 whole interval steps; unknown bounds are never treated as behind by the skip predicates. repairStaleEngineSeries()insrc/engines/data-sessions.ts— local-first repair (upload the local window when it reaches now; upsert-by-time keeps Pro's deeper history), elsefetchfrom Pro's exchange feed; cooldown-guarded (60s per endpoint+series), never throws. Exposed asPOST /data/seriesnewesttruth:tick'sunchanged: trueis not proof of freshness.- Both engine-owned paint paths refuse to back-date the chart —
src/streams/multiplex.ts(paintEngineOwned, including theunchangedearly return against a moved venue clock) andsrc/streams/stream-live.ts(snapshots, deltas, per-tickensureSeries). A stream delta that does not continue the chart tail is dropped instead of punching a hole. They report once viasrc/engines/series-repair.ts(noteStaleEngineSeries: throttled log +enginetelemetrydegraded+ feeddegraded) and repair in the background; the next snapshot/tick paints the healed window. - Provisioning tops up:
sharedProvisionSeriesasks Pro tofetchafter an upload whose tail is already stale, so a stale local window can never become the engine's frozen tail. - Background catch-up (
src/data/background-catchup.ts) skips the local repaint in engine-owned mode (it would fight the store the overlay time axis is built from) and pushes AXIS' freshest window onto Pro's series instead. Documented indocs/devops/engine-owned-data-plane.mdx§ Series staleness; covered bytests/series-freshness.test.ts, therepairStaleEngineSeries/seriesIsStalecases intests/data-sessions.test.ts, and the two frozen-series paint cases intests/multiplex-engine-owned-paint.test.ts.
- New pure predicates
- A repair-driven window jump no longer trips LWC's
Value is null, and a stale engine window no longer pins the candles. Two follow-ups to the staleness contract above, both observed on the deployed build:- Skip vs report. A behind-the-venue-clock verdict no longer refuses the paint in either engine-owned path (
src/streams/stream-live.ts,src/streams/multiplex.ts): it reports + repairs, and the snapshot is painted whenever it does not back-date the chart. Only a genuine back-date (engineWindowBehindChart) keeps the fresher chart window — refusing a snapshot that would advance the chart pinned the candles while every other series kept moving. Theunchangedearly return inpaintEngineOwnednow reports as a no-op instead of a skip (there is nothing newer to paint either way). Covered by the new lagging-engine-window case intests/multiplex-engine-owned-paint.test.ts(it fails if the guard is narrowed back to "any stale verdict skips"). - Moved-window prune.
PaneManager.notePaintedCandleRangenow prunes co-painted script series (overlay_*,bgcolor_*, equity) to the new candle range immediately, in the same task as the candlesetDataand before LWC's next render. A long forward jump (a four-day-frozen series repaired 279 bars at load) left those series holding rows the candles no longer had; LWC's shared time scale is the union of every series' rows, so the candlestick painter threwValue is nullon every render for ~2 minutes until an overlay apply happened to re-clamp them. Compare / on-chain overlay series are untouched (their windows are legitimately independent). Covered bytests/pane-manager.test.ts(pruneCoPaintedSeriesToPaintedRange).
- Skip vs report. A behind-the-venue-clock verdict no longer refuses the paint in either engine-owned path (
- A superseded engine socket can no longer tear down the live connection. Observed on the deployed build: a symbol switch logged
Uncaught error: WebSocket not openand left the engine stream plane in per-tick failure cooldown (venue fallback painting instead). Browsers dispatchopen/close/erroras separate tasks, so an abandoned connect attempt could open or close after a later attempt had already taken overthis.ws;ws.onclose/ws.onerrornulled the socket unconditionally, which killed the replacement connection — the auth frame sent from the live socket'sonopenthen threw out of the socket handler, while the client believed it was down and kept reconnecting. Both engine WS transports now check ownership (owns()): a stale socket's late events and frames are ignored, a socket that opens after abandonment is closed instead of adopted,sendfrom the heartbeat can no longer throw out of a timer (the same uncaught-error class), andEngineWsClientno longer rejects the live connection's in-flight runs or marks the whole client dead on a stale close. Covered by four tests intests/engine/ws-client.test.tsandtests/engine-ws.test.ts(all fail without the guard); documented indocs/devops/engine-owned-data-plane.mdx§ Stream plane (socket ownership). - The stream plane now drives the engine's series, so the chart stops sitting at its frozen tail.
live_streamsubscriptions carry no client tick frame (subscribe/resume/repair/pingonly), so the plane could only replicate the engine's series while the calculator plane advanced its own with a REST chart-session tick per venue flush. On the observed Pro deployment the engine's per-tick advance stalled (GET /data/seriesnewestfrozen three bars behind the venue), which left the chart moving only when the staleness verdict + repair landed — a sawtooth of 0 → 3 bars behind, one bar painted every ~3 minutes on a 1m series, i.e. a chart that never looks live even though the venue stream is healthy. Newsrc/engines/series-drive.tsticks the same chart session the calculator plane uses (tickChartDataSession), throttled toSERIES_DRIVE_MIN_MS(10s) with a new venue bar always driving immediately;streams/stream-live.tscalls it per venue flush without awaiting it (the paint stays bound to the flush, and the back-date guard still refuses a late snapshot). Astaletick envelope falls through tonoteStaleEngineSeriesso the existing report + repair broker remains the backstop for engines withoutdata_session. Covered bytests/series-drive.test.ts(throttle, bar-advance bypass,stale/failure outcomes, per-series scoping); documented indocs/devops/engine-owned-data-plane.mdx§ Series staleness + § Stream plane (series drive).
Docs
- Docs landing refreshed to the current release (
docs/index.mdx): the stale v2.21.0 line moved to v2.24.5, and a new What's new section surfaces this changelog's headline surface — the eight MCP live-plane capabilities, the engine-owned live fix batch (forming tip, staleness repair, socket ownership, stream-plane series drive), and the PYNE 0.6.12 wheel — linked to the MCP guide, the engine-owned data plane doc, and the online changelog.docs/llm.txtregenerated from the PYNE exports pipeline (the pack had been missing the six docs commits since the last regen). - Landing What's-new plate synced (
hoox-landing-page→app/axis/axis-data.ts):AXIS_WHATS_NEWnow leads with the MCP live-plane capabilities, the live-correctness batch, and the 0.6.12 wheel — all tagged2.24.5(the build production serves).
[2.24.5] — 2026-10-17
Added
- Engine-owned data plane (stream profile,
WS /ws/live). Each engine that can hold a feed owns its datafeed with the exchange as source of truth: the client subscribes with script + meta, receives status (datafeed-loading → calculating → live), one full snapshot, then deltas with per-subscriptionseqand theclosed_uptowatermark, and re-verifies closed bars against the venue. Newsrc/engine/client (frames, WS client, delta applier, bar-close verifier, router, apply bridge, live-wire),src/streams/stream-live.tsglue, HUD feed chip + indicator stream badges, and thefeatures.live_streamcapability gate (strict opt-in, fail-open to the calculator profile). LegacyserverEnginepath frozen for engines withoutlive_stream(pyodide, pyne-worker, offline, HPO). runAndApplyInnerdecomposed into apply stages (F-25). The ~1180-line function is now a ~250-line orchestrator oversrc/indicators/apply-context(run context + series alignment),apply-pane(pane setup),apply-overlays(line/OHLC payload building),apply-mutations(chart mutations),apply-strategy(trade markers + report),apply-drawings(Pine drawings), andapply-store(indicator store update). No behavior change — full suite green.- Maintenance mode (
VITE_MAINTENANCE_MODE) — a full maintenance page instead of the app. Setting the flag in.env(or the build environment) makesindex.tsxmount only the newui/MaintenancePage: dark void background, a dimmed overlay, the landingtexture-crosshairline grid (0.8px accent lines on a 10px pitch at 25% opacity), and aui/SideRayslight fan painted above the content (mix-blend-screen) so the beam grazes the mark and copy — the spotlight is the only light. The HOOX mark is anchored dead-center of the viewport and flickers once on load plus again on hover (the landinglogo-flickerstagger, via a new opt-inHooxLogointroFlickerprop reusing the existing one-shot hover stagger — no continuous loader animation; phosphor glow ring included), and no dust motes are drawn; a "Planned maintenance — will be back soon" status line, an upcoming-major-release (AXIS v3) teaser label, and the hoox.sh/axis slogans typed character-by-character by the newui/SloganTypewriter(see below) sit below that center anchor, and the site link is pinned to the bottom edge. The chart workspace and editor popout never import, so no engine or panel can half-work while a deployment is down; the check lives in the newsrc/maintenance.ts(unit-tested intests/maintenance-mode.test.ts) and the page stays a lazy chunk. Reduced motion is honored by the existing app-wideprefers-reduced-motionblock (SideRays renders a static frame). Vite inlines the value at build time, so flipping the flag always needs a rebuild; e2e forces it off viaplaywright.config.ts(webServer.env— process env outranks.envfiles), documented in.env.exampleanddocs/devops/build-and-serve.mdx. ui/SideRays— SolidJS port of the React BitsSideRayslight-fan background. The WebGL2 shader + RAF loop are unchanged; the React wrapper was adapted to Solid (useRef/useState→ bindings +createSignal,useEffect→createEffect/on/onCleanup,className→class) and the upstream.side-rays*CSS is inlined as Tailwind equivalents. Renders a configurable fan of light rays (two color layers, origin/anchor, density, dust, mouse-leaning); honorsprefers-reduced-motion, pauses off-screen and on hidden tabs, and degrades to an empty container when WebGL2 is unavailable. Used by the maintenance page (indigo fan,origin="top-right").ui/SloganTypewriter— SolidJS port of the hoox landingTypewriterTagline. Cycles phrases with the landing rhythm (type 65ms/char → hold 2s → delete 35ms/char), sizes the box from an invisible longest-phrase probe so the typing origin never slides in a centred layout, keeps the whole linearia-hidden(the maintenance page root is anaria-liveregion — per-character mutations would otherwise be announced by screen readers), and renders the first phrase statically underprefers-reduced-motion(motion-safecursor). Used by the maintenance page for the AXIS slogans: the hoox.sh/axis hero headline ("Own the axes. Swap the engine.") plus the three footer phrases.
Fixed
- Session ticks resolve delta envelopes against the cached full result. Tick/append calls now send
delta: true(newengines/session-deltamerge: positional splice with count/key validation, minimal-unchanged reuse, null on any mismatch so the session recreates instead of applying partial data). Old backends that reject the flag are retried plain once per endpoint and remembered. Applies to engine-owned data sessions and bars-in live sessions; the chart session stays flag-free (its envelope is already tiny). - Engine-owned bars are normalized before chart paint.
getDataSeriesBarsnow runs the venue pipeline (normalizeHistoricalBars: ms→s, sanitize, dedupe last-wins, sort) so engine paints cannot hit the LWCValue is nullpath on ms times or unsorted data. - Candle-range clamp is per-chart instead of global. The painted range lives on the
PaneManagerinstance, extends on live appends, and is a no-op without a recorded paint — slots no longer clamp each other and newest tips are not dropped. - The candle-range clamp is no longer a no-op before the first recorded paint. The per-chart painted range is only recorded after a successful candle
setData, so an engine/indicator overlay that applied first (the engine-owned plane can paint ahead of the debounced history paint) was clamped againstnull— i.e. not at all — and could extend the shared time scale past the candles, which is exactly theValue is nullboot loop (candles keep failing, so the range is never recorded and the clamp stays off forever). Co-painted payloads now fall back to the extent the candle series already holds (rangeFromSeriesData, fed fromPaneManager.clampRange());nullis still the answer for an empty chart, so a chart with no candles behaves exactly as before. A moved painted window also invalidates the overlay tip cache, so the next overlay apply repaints in full and is re-clamped instead of appending a stale tip. Covered by therangeFromSeriesDatacases intests/chart-series-sanitize.test.ts. - The engine-owned live plane authenticates over the socket.
stream-livebuilt itsRedesign2WsClientwithendpointonly, so the documentedauthframe was never sent: the engine answeredUNAUTHORIZEDon the first frame and AXIS fell back to REST (with reconnect churn) even on hosts that served the plane correctly. The client now getsauthTokenfromgetActiveEngineConfig().apiKey— resolved vault-first, like every key-gated REST call — matching{type:"auth",payload:{token}}. Companion engine-side fix: the Pro API must not run theGeventWebSocketWorker, which corrupts flask-sock framing after a successful upgrade (PYNE 0.6.12 defaults togthread). - Engine error frames are no longer mistaken for malformed ones. The engine answers an unauthorised or bad frame with the body flat (
{type:"error", code, message, retryable}— its shared_errhelper), while the AXIS contract reads every frame body frompayload. The strict validator therefore discarded realUNAUTHORIZEDreplies asskipping malformed frame error, hiding the reason and making an auth failure indistinguishable from framing garbage.isErrorFramenow accepts either envelope and the wire client normalises the flat shape intopayloadon receipt, solive-wirereports the engine's actual message when a subscribe fails. Covered by theerror framecases intests/engine/frames-contract.test.ts. - Session series join the chart by bar time, not index.
alignSeriesToTimescoversdata-session-*andsession-*hits viameta.engineTimes/ live-sessionbarTimes; index tail-align stays as fallback for time-less payloads. - New indicators no longer re-upload the full window. Series provisioning is memoized per endpoint+series (max coverage wins) with in-flight dedup; reapply and live reruns run bounded-concurrent on a shared epoch honoring plot-dependency levels.
- Engine-owned live paints are tip-aware and ordered.
paintEngineOwneddiffs bars (noop/tip/append/full), paints tip-only when possible, and reruns wait for the paint chain to settle;runBatchhandles free-tier 413 withwindowBarsmeta like single runs. - Engine-owned live throttle actually throttles.
lastEngineTickAtwas set inside the async paint step, so every quick venue tick saw the initial value and chained a new/tick+/data/series/barsround-trip (~3s each) back to back — starving indicator re-runs (they await the paint chain) and leaving the chart lagging 20–30s. The tick time is now recorded synchronously before the step is chained, so ticks withinENGINE_OWNED_TICK_MIN_MS(10s) skip the engine and paint the venue tip locally instead. - Every-tick indicator re-runs honored on heavy histories. Delta ticks (O(delta) server-side) plus tip-only chart apply make per-tick updates cheap regardless of bar count, so
effectiveLiveRerunModeno longer downgradesevery-ticktobar-closeatHEAVY_LIVE_RERUN_BARS(10,000 bars). The local venue-tip paint path (within the engine throttle gap) also schedules an indicator re-run whenevery-tickis on, so indicators update per venue tick instead of only after engine ticks.
[2.24.4] — 2026-10-15
Fixed
- Co-painted series can no longer extend the shared time scale past the candles. When the candle window and an overlay/equity window diverged (a stale engine-owned window vs live venue ticks is the classic case — e.g. an 18-minute-lagged host series against real-time bars), the overhanging points extended the shared time scale past the candle PlotList and LWC's renderer threw
Value is nullon every paint. Overlay lines, plotbar/plotcandle overlays and the equity curve are now clamped to the last successfully painted candle range (tracked inchart/series-sanitize, a no-op until the first candle paint and for already-aligned payloads). The candlesetDatafailure envelope now reports chartType, series type, fill/merge/non-integer counts, payload length and time range so any recurrence is instantly attributable. Covered by theclampPointsToPaintedRangecases intests/chart-series-sanitize.test.ts.
[2.24.3] — 2026-10-15
Fixed
- The Pro API engine key now survives a reload.
sanitizePluginsConfigForPersiststrips everyapiKeyfrom the durablelocalStoragepayload, but the session vault never captured theserver/pyne-workerengine keys — so after every reload the key field was empty and every key-gated call (/data/*, authenticated/run,/auth/usage) answered 401 until the key was re-entered. The engine keys now live in the vault like the cloud key (engine.server.apiKey/engine.pyne-worker.apiKeyslots, captured fromengine:server/engine:pyne-workerbags on every persist, migrated from pre-vault blobs on boot):getActiveEngineConfig(),readEnginePluginConfig()and the Runtime form hydrate resolve vault-first, and saving mirrors the key back into the vault. Covered by theengine key vaultcases intests/wsd-vault.test.ts(capture → strip → resolve round-trip) plus a vault-isolation reset intests/engine-pro.test.ts. - A short local dataset no longer falls through to an exchange fetch the host may not support. When the engine reported
DATA_MISSafter a local/data/series/provideupload, the broker unconditionally called/data/series/fetch— which fails withccxt_missingon hosts without ccxt. The broker now retries the session create clamped to the uploaded bar count (the apply path tail-aligns the shorter series) and never fetches for data the host cannot download. Covered by the clamp cases intests/data-sessions.test.ts.
[2.24.2] — 2026-10-15
Fixed
- Engine-owned live mode now finds the local Data Manager dataset. The data-session broker looked up local bars under the canonical venue (
binance|BTCUSDT|1m), but the Data Manager keys datasets by the source plugin id (binance-rest|BTCUSDT|1m), so the lookup never matched: the broker skipped the local upload and went straight toPOST /data/series/fetch, which on a host without ccxt fails (ccxt_missing) — every tick paid create → fetch → create and the run stayed in error (observed 190+ failed/data/series/fetchcalls in one session).defaultLocalBarsnow resolves candidate keys (active Data Manager selection → provider session source → active source →${venue}-rest→ bare venue, each matched byexchangeForSource) and uploads the newest window via/data/series/provide, which needs no exchange feed on the host. Regression:tests/data-sessions.test.tsstores a dataset underbinance-restand asserts the broker provides it instead of fetching. - A failing data-plane provisioning no longer retries on every live tick. The three-round-trip broker chain (create → provide/fetch → create) was re-paid on each tick while the run was already failing. Live ticks (
tick: true) now back off per session slot forDATA_SESSION_RETRY_COOLDOWN_MS(60 s) after a provisioning failure; interactive runs still retry immediately, and a success clears the memo. - Free-tier bar caps are handled instead of failing every run. A PYNE host with
FREE_TIER_LIMITSrefuses windows overFREE_MAX_BARS(default 5000) with413 TOO_MANY_BARS; with the default Rolling window of 10,000 every bars-in run (reapply, bar-close live ticks) failed. The REST client now readsmax_barsfrom the 413 body and retries once with the newestmax_barsbars, tagging the resultmeta.windowBars; the apply path tail-aligns such series to the chart window (older bars becomenawhitespace) instead of shifting every value early, and an interactive run logsEngine bar cap: evaluated newest N bars (free tier).
[2.24.1] — 2026-10-15
Fixed
- Engine capabilities no longer silently reset the saved live re-run mode. The Indicators panel clamped
store.live.rerunOntobar-closewhenever the selected engine's resolved capabilities did not includeevery-tick— which includes a/healththat simply does not advertisefeatures.live_modes(the hosted PYNE Pro does not) and any probe that failed. The stored preference was overwritten in memory and later persisted by the next unrelated save, so "Every tick" reverted on its own and read as "settings do not save". Capability resolution now never rewrites the preference: the live loop and the panel chip derive the effective cadence throughliveRerunModeForEngine()— bar close only when the engine positively advertised its modes without every-tick, unknown capabilities keep the stored choice — and the chip marks the engine-limited case (Bar close · engine). The Settings modal keeps showing and saving the user's own choice. Covered by theliveRerunModeForEnginecases intests/engine-ownership.test.ts. - Indicators and strategies advance on new bars in engine-owned live mode again. In every-tick PYNE Pro mode the venue stream is only a trigger: each flush ticks the chart's engine data session and paints
GET /data/series/bars, but the painted bars were never written back tostore.bars. The runner builds every overlay point by zipping the engine's series against the time axis fromstore.bars(getOhlcvTimesForApply→lineSeriesToOverlayData), so that axis stayed frozen at the load window while the engine's session kept appending: indicator/strategy lines never gained the new bars and their tip stopped changing once the old window's last bar closed — "stays as on the first print". The engine-owned paint now advancesstore.barswith the engine's newest window (setBarsQuiet, memory-only — no dataset write, no persist, no viewport re-fit), and the runner tail-aligns engine-owned series (meta.sessionVia=data-session-*) to the chart window, because the session's own series slides independently of the local Rolling window (trimmed when longer,na-padded when shorter) instead of mapping index-for-index and shifting every value by the difference. Covered bytests/multiplex-engine-owned-paint.test.ts(store advances from the engine series even when the venue tick carries no new closed bar) andtests/runner-engine-owned-apply.test.ts(series tail lands on the newest bars, 1:1 mapping untouched for bars-in runs). - Cross-test store leakage that made CI order-dependent.
setStore('pluginsConfig', {})merges in a Solid store rather than replacing, so test files that setengine:server.preferWs: falseleft that key behind for every later file —classifyTransport('engine', 'server')then reportedrestwhere the telemetry test expectsws, depending on run order. The leaking files (tests/runner-engine-owned.test.ts, the batch runner-seam test) now restore withreconcile({}), andtests/telemetry.test.tsasserts the default explicitly instead of inheriting it. The batch seam test also dropped an assertion onstore.lastRun, whichsetLastRunonly publishes whenresultsFocusIdis null — order-dependent for the same reason.
[2.24.0] — 2026-10-14
Added
- The rest of the
/healthpayload is now consumed, and the parity surfaces have e2e coverage.compile.default_mode/features.default_run_modeseed the Runtime form's execution mode when the user has not saved one (a host configured for interpret no longer opens onauto); the prewarm action is disabled up front when a probed backend answered without advertisingPOST /compile/prewarm, instead of firing a guaranteed 404; the Workerspyne-prodetail shows the compile detail (disk cache (N IR)) and flagsfeatures.free_tier_limitswhen unauthenticated runs are guarded.e2e/pyne-parity.spec.tsasserts both studio surfaces against a stubbed Pro/health. Fixing that test surfaced a real bug:baseResultinsrc/workers/probe.tsrebuilt the probe result field-by-field and silently dropped the newreadiness/compilefields, so the Compile row would never have rendered. - Runner jobs can be deployed from the chart, and PYNE keys validated from the CLI. The Workers Hosted runner section gains Deploy active script, which registers the newest applied chart script as a hosted job via
POST /scripts(upsert by indicator id, carrying symbol/timeframe), alongside the existing list / enable / run-now controls.axis keys validate --pynechecks a key againstPOST /auth/validate(tier, active, rate-limited) without consuming a call, mirroringaxis keys create --pyneandaxis usage. The parity ledger (docs/devops/pyne-endpoints.mdx) now recordsPOST /auth/validateandPOST /scriptsas used, withGET /scriptsandDELETE /scripts/{id}left out deliberately — job state already flows throughGET /cron/jobs, and destructive deletion stays out of the charting UI. - The optional PYNE hosted runner is manageable from AXIS, and PYNE keys/usage from the CLI.
src/engines/runner.tswrapsGET /cron/jobs,PUT /cron/jobsandPOST /cron/runbehindrunnerAvailable(), which readsfeatures.script_runnerfrom the cached/health— so the UI hides the surface entirely on a host withoutPYNE_RUNNER=1instead of probing a 404. A disabled runner (RUNNER_DISABLED) comes back asunavailableand denied access as its own message. The Workers page's pyne-pro detail gains a Hosted runner section (load jobs, per-job enable toggle, Run now); the host'sADMIN_TOKENis deliberately never stored in the browser, so a 403 is surfaced as guidance. On the CLI side,axis keys create --pynemints throughPOST /auth/create_key(admin token from--admin-token/AXIS_ADMIN_TOKEN, 403 mapped to a host-sideADMIN_TOKENhint) and the newaxis usageprintsGET /auth/usagefor a key. - Pro surfaces are wired: usage, chart thumbnail, indicator preview, quick backtest.
src/engines/pro.tswrapsGET /auth/usage,POST /preview/chart,POST /preview/indicatorandPOST /backtest/quickbehind the same endpoint/key/consent resolution the runner uses, sends only the close series the endpoints accept (capped at 500 bars), and returnsunavailable— never a thrown error — when the host does not offer a Pro route (403/404/501) or the key is not recognised (401). The Runtime page gains an EngineProPanel: an API usage section (tier, calls used/limit/remaining, key id — only when a key is configured), a Quick backtest section that maps equity curve, trades and summary and shows the backend's equity PNG, and a Chart thumbnail section that renders the newest applied script over the current bars with a Save-PNG link. - Chart reapply can evaluate several indicators in one round-trip (
POST /run/batch).src/engines/batch.tsmaps the backend's per-script envelope into the sameRunResultshape a single REST run produces (includingplot_meta, alerts andmeta.batch), enforces the backend's 8-script cap, isolates per-script failures, and returnsnullwhen the route is missing so nothing changes on older backends.reapplyChartScriptsnow sends one batch when at least two visible scripts are eligible — no resolvedinput.*overrides (the batch body has no per-script inputs), one shared resolved mode (resolveServerRunModeis applied per script and a mixed bag falls back), theserverengine, and not engine-owned live mode. Batched results are applied through the existing path via the newRunOptions.precomputedResultseam, so panes, overlays, drawings and alerts behave exactly as before; per-script failures and oversized sets fall back to individual runs. - Live sessions are reconciled against the engine instead of trusted blindly.
GET /session/{id}is now used for what it is good at:getSessionStatus()returns the engine's own view (bars, idle seconds, TTL) as a typedok/gone/unknown, and an idle session (untouched for 20 s, checked at most every 5 s) is reconciled before a tick is paid for. A session the engine has reclaimed — or one whose bar count drifted from the recorded count — is dropped locally so the next tick rebases over the chart window; a transport failure leaves local state alone, because a tick still detectsgoneon its own.refreshLiveSessionTelemetry()reuses a status fetched within the throttle window, so the live stream telemetry line can showsession N bars · idle Ss · ttl Sswithout adding a request per paint. - Numba prewarm is available from the app and the CLI (
POST /compile/prewarm). The backend compiles inmode: autoby default, so a cold host makes the first interactive run absorb the JIT cost;src/engines/prewarm.tsnow pays it up front. The Runtime form has a Warm compiler button that uses the draft endpoint + key (so it works before Save) and re-reads/healthafterwards so the readiness badge reflects the warm state; the Workers Manager pyne-pro detail has the same action and re-probes. An opt-in Warm compiler after save toggle warms the applied visible scripts (libraries and hidden scripts excluded, capped at the backend's 16) through a memoised client that retries only real failures — so a Save cannot spam the endpoint.axis prewarmexposes the same call with--endpoint/--key/--force/--script/--json(defaulthttp://127.0.0.1:5002orAXIS_PYNE_URL), andaxis doctorpoints at it when the PYNE sibling repo is present. The client never throws: no endpoint, Pyodide, a 404/405 older backend, an error envelope, a declined key-consent prompt, and timeouts all return a typedPrewarmResultthe UI renders. - The full PYNE
/healthpayload is now parsed and surfaced, not just the live-mode subset.src/engines/health.tstypes the backend's discovery surface —features.*(warm_compile,optimize,alerts,alert_webhooks,default_run_mode,free_tier_limits,script_runner,script_runner_scheduler) andcompile.*(has_numba,builtins_warmed,prewarm_enabled,host_prewarmed, disk cache, IR/source entries, default mode) plus the advertisedendpointsmap. Parsing is conservative: booleans require=== true, unknown/malformed keys degrade tonull, and nothing is inferred from the service name. The sameGET /healthprobe that feedscapabilitiesFromHealth()now also populates a cachedEngineHealth(getCachedEngineHealth()), which clears when the endpoint stops answering or a non-server engine is selected, so a readiness badge can never describe the wrong backend.compileReadiness()summarises it aswarm/cold/unavailable/unknown; the Workers Manager Pro-API detail shows a Compile row and the Runtime form shows readiness for the endpoint it just probed (via the side-effect-freeprobeEngineHealth(), which does not touch the selected engine's cache). - Live ticks evaluate O(delta) via PYNE live sessions (Pro API 0.6.9+). The
serverengine keeps a resumable session per script (POST /session,/tick,/appendinsrc/engines/live-sessions.ts): forming-bar re-ticks are O(1), new closed bars append O(delta) — measured 1,087 ms → 0.13 ms on 20k bars upstream. Sessions are interpret-only, keyed by endpoint + script + inputs + libraries + symbol + interval, with tick-then-append ordering (append alone would confirm stale forming values and duplicate the bar), no-change-tip envelope reuse, rebase once the session outgrows the chart window by 2,000 bars, and silent fallback to the full WS/REST run on any miss (old backends, edge, proxies,NO_SESSION,SESSIONS_FULLare remembered per endpoint). Wired throughEngineOpts.liveTick(src/plugins/types.ts) fromrunScript(src/indicators/runner.ts); the live multiplex already marks genuine ticks. Session path is taggedmeta.sessionVia(session-create|tick|append|cached). - Engine-owned data sessions for PYNE Pro every-tick (AXIS side). New
src/engines/data-sessions.ts: identity-only session client (POST /data/session,POST /data/session/{id}/tickwith{exchange, symbol},POST /data/series/provide|fetch,GET /data/series/bars) and a cache-miss broker. OnDATA_MISSthe broker auto-uploads the matching local Data Manager dataset (lasthistory_bars, chunked to 5,000), and only if that is still short asks the engine to fetch from its exchange; it never loops.serverEngine.runaccepts anEngineOpts.dataSessionidentity and evaluates over the engine's series without sending bars; broker failures surface as run errors instead of silently falling back to shipping bars. Session reuse is per script owner, so live ticks re-tick one session and sibling indicators keep their own. Contract and remaining steps:docs/devops/engine-owned-data-plane.mdx. Verified cross-repo against the PYNE Pro API/data/*store (tests/integration/pyne-data-session.e2e.test.ts, run viascripts/e2e/pyne-fake-feed.py; skipped unlessAXIS_PYNE_E2E_URLis set). The runner, chart paint, and no-persist routing are not wired yet.
Fixed
- In-browser Pyodide runs no longer freeze the UI, and Cancel really stops them.
pyodideEngineevaluated Pine synchronously on the main thread, so a multi-second run blocked chart, panels and input, and a cancel only took effect before evaluation started. Evaluation now runs in a dedicated module Web Worker (src/workers/pyodide.worker.ts, main-thread clientsrc/workers/pyodide-client.ts, typed request/response protocol inpyodide-protocol.ts). Cancel terminates the worker, rejects the run withAborted, and the next run respawns and reloads the runtime (cold-load cost accepted). Runs queue one-at-a-time on the worker; a worker crash surfaces as a run error and respawns on the next run; a failed load keeps the existing 30 s cooldown. The wheel/runtime load sequence is shared (installPynescriptRuntime) so the worker and the in-thread fallback cannot drift. Without a Worker (bun unit tests), the original in-thread path runs unchanged. Public engine API andRunResultshape are unchanged; the worker chunk is emitted as an ES module (worker.format: 'es'invite.config.ts). Value is nullchart crash — Lightweight Charts asserts at render time, not atsetData. The library's candlestick style getter callsensureNotNull(valueAt(barIndex))inside the render pass, so a stored row with missing OHLC, a nullclose, or two rows sharing an open time throws asynchronously — escaping everytry { series.setData() } catchin the app and surfacing as a raw uncaught window error on each redraw (captured in a browser harness with a stack pointing atbarStyleFnMap.Candlestick). The old guard only dropped bad rows, which also left the series shorter than the time axis the overlay mapper walks.chart/series-sanitizenow repairs the payload instead: duplicate/non-ascending open times collapse to the last write (matching DatasetStore conflict resolution) and re-sort, missing/null OHLC fills from the nearest valid neighbour, and null/NaNvaluebecomes0(an omittedvalueis left alone — that is how a Pinenawhitespace gap is encoded). Dropping is the fallback for rows with no donor. The repair runs at thesafeSetDataboundary inchart/pane-manager, so every setData path (candles, volume, overlay lines, equity, indicators) is covered in one place, and the history paint logsdropped/merged/filledcounts so repeat offenders stay visible.- Live ticks survive turbulent chart updates on the session path. Follow-up to the
liveTickforwarding fix:runScriptcompared the forwarded bars against the livestore.barsreference, but DSM progressive slices (setBarsQuiet) and live appends can replace that array between capture and evaluation — exactly what happens on every tick while a backfill lands — so the flag misfired back to full re-runs.runAndApplynow records chart-vs-private-window once, up front, andrunScripttrusts the flag (isolate studies still always take the full path). - Live ticks actually reach the session endpoint now.
runAndApplyInneralways forwards the bars it evaluated, so the!opts.barsguard inrunScriptnever fired and every live tick silently fell back to a full WS/REST re-run (production ticks measured 1–2s each with the session client idle). The guard compares by reference againststore.bars: chart-series ticks go O(delta), private HPO/holdout slices stay on/run. Regression testtests/runner-livetick-session.test.tsasserts the wire sequence (POST /session→tick→append, never/run) and the HPO opt-out. - Sibling indicators no longer evict each other's live sessions. Session ownership was keyed
endpoint|symbol|interval, so on a chart with several applied indicators each run released the previous script's session and recreated its own — every tick fell back to a full-window eval (O(window), never O(delta)) even with the session client engaged. Ownership now includes the script instance (liveSessionOwner, threadedrunAndApply → runScript → engine.run → /sessionasownerId): one session per indicator, and a new revision of the same instance still releases its predecessor. Covered by the sibling-independence and same-instance-revision cases intests/engines-live-sessions.test.ts. - Live sessions rebase when closed history is rewritten under a stable count. Continuity was judged by bar count + tip time alone, so a gap fill or venue revision of an earlier closed candle — or older bars prepended — left the resumable session serving stale values.
tryLiveSessionTicknow fingerprints the closed bars (bars[0..count-2], the forming tip excluded because it legitimately changes every tick) and rebases over the current window on mismatch, for both the same-tip and the advanced-tail paths. Covered by the stable-count-rewrite and prepend cases intests/engines-live-sessions.test.ts. - Reload no longer strands the chart at one venue page (1000 bars).
loadSymbolDatapassed Settings → Historical bars as a singlefetchHistoricallimit, but venues cap one response (Binance/Bybit/MEXC 1000, Kraken 720, OKX 300), so Reload painted the first page and stopped. The venue path now walks back page-by-page (endTimecursor, stall/empty/abort guards, 12-page sync budget) up to Historical bars, seeds the full window into the dataset store, and subscribes the progressive repaint before the DSM background job runs — the chart keeps growing as backfill slices land instead of freezing at the first page. - Dataset vs chart split is now Historical bars vs Rolling window. The DSM target stays Settings → Historical bars (default 5000); every paint path (
paintDataset, venue load,applyCachedToChart, offline fallback) now shows only the newest Settings → Rolling window slice (default 10,000) while the dataset keeps the full depth.applyCachedToChartalso drops the invertedmax(history, 100k)clamp — an explicitmaxBarswindow wins, otherwise Rolling window, hard-capped at 100k. - A live-session response that is not a result is no longer rendered as a successful run.
/tickand/appendaccepted any 2xx JSON body, andpayloadCountfell back to the local bar count when the body carried nocount— so an error envelope ({ status: 'error' }, which the/runpath already guards against and which some backends return with a 200) passed every shape check,mapRestPayloadcoerced it tostatus: 'success'with emptyplots/series, and the empty overlay list then cleared the indicator off the chart while the status bar reported success. The session client now requires a usable envelope (nostatus: 'error', a matchingsession_idwhen echoed) and classifies each op as ok / gone / invalid / miss. Covered by the error-body case intests/engines-live-sessions.test.ts. - A backend that cannot serve sessions no longer costs two full evaluations per tick. An answered-but-unusable response (error envelope, mismatched bar count, missing
session_id) dropped the session and returnednull, so the caller ran a full/runon top of the full-windowPOST /sessionit had just paid for — on every tick, with no negative cache. Unusable answers now cool the session key down forSESSION_RETRY_COOLDOWN_MS(30 s), bounding that cost to one wasted create per cooldown. The!tail.length(same-tip) path also pins its closed-history fingerprint to a session that really holdsnbars, instead of storing a hash that may not describe the session's own history. - Live sessions now engage under the default
mode: auto. The fast path was gated onmode === 'interpret', but the engine'sconfigSchema.mode.defaultisautoandpluginsConfigstarts empty, so on a stock install — and on any deployment that never switched to interpret — every live tick still took the full WS/REST run and the session client was never called. Sessions always evaluate in interpret (compile has no resumable evaluator state), so the client now records the mode the backend reported for each(endpoint, script, mode sent, profiler)run key and allows a tick only when that wasinterpret:autodeployments get O(delta) ticks wherever the backend really interpreted, and a compiled series is never silently switched to interpreted values. Regression:tests/runner-livetick-session.test.ts(auto tick →/run, then/session) plus the mode-memo case. - Scripts with
input.sourcestay on/runinstead of churning sessions. A resumable session freezes its inputs at create (/tickand/appendcarry bars only), whileresolveInputSourceValuesinlines cross-indicator plot refs as live series arrays that change every tick. Those arrays were part of the session key, so each tick released the session and created another — a full-window create plus aDELETEper tick, i.e. no O(delta) at all. Scripts whose resolved inputs are not all scalar now skip the session path (fresh inputs, one full run per tick — the pre-session behaviour); the guard is documented insrc/engines/catalog.tsand asserted intests/runner-livetick-session.test.ts. - Session release authenticates.
DELETE /session/{id}was issued without headers, so an authenticated backend rejected it and every dropped session lingered until the 15-minute idle TTL — including the ones released on every rebase. Releases now send the sameX-API-Key/Authorizationas create/tick/append. - The session budget survives a caller abort signal.
withBudgetreturnedsignal || timerwhenAbortSignal.anywas unavailable, silently dropping the 30 s/90 s budget whenever a caller passed a signal; it now combines both by hand.
Changed
- Every-tick Pro mode is wired end to end. When the selected engine owns the feed (
data_sessioncaps + every-tick policy): the chart paints from the engine's series (GET /data/series/bars, memory only), the runner evaluates over the engine's series with identity only (no bars sent), and live ticks advance the engine's store so candles and indicators stay aligned. Isolate studies and engines withoutdata_sessionkeep the bars-in path. - No local OHLCV persistence in engine-owned live mode. When the selected engine owns the feed (
data_sessioncaps + every-tick policy), live bars stay in memory: the streaming multiplex no longer grows the dataset cache and cache→now expand serves cache only. The gate (isEngineOwnedLive()insrc/engines/ownership.ts) is fail-open — while capabilities resolve, or on engines without data sessions, persistence works exactly as before. - Live re-run policy is hydrated from the selected engine. Engines expose
getCapabilities()(EngineCapabilities:liveModes,ownsData,dataSession, optionaldataExchanges). PYNE Pro reportsfeatures.live_modes/features.data_sessionon/health; anything absent (pyne-worker, Pyodide, a generic endpoint without those keys) is bar-close only. The indicator panel cycles only through supported modes and falls back to bar close once an engine answers without every-tick. The preference is not changed while capabilities are still loading. Parsing iscapabilitiesFromHealth()insrc/engines/catalog.ts, which never infers support from the service name. - Status bar bars are interactive. Left chart count is an editable input (Enter/blur commits to Rolling window and re-slices from the dataset; Escape cancels). Right
· dataset Nksegment is a button that opens the Dataset manager modal instead of the DSM panel. Settings copy updated to match (Historical = dataset depth, Rolling = chart + engine window). src/engines/live-sessions.tsdocuments its costs and exports its key types. The module header now states that the closed-history fingerprint re-reads the closed window on every tick (O(window) client-side by construction — the O(delta) claim covers the server-side evaluation and envelope) and that unusable responses are memoised per session key. Session identity is split into exportedLiveSessionKeyFields(key) andLiveSessionModeFields(run-mode eligibility).- The evaluation map and the OKF bundle now describe the live-session path.
docs/architecture/evaluation.mdxgains aPOST /session→/tick//appendnode with its interpret-only and miss-falls-back-to-/runedges, ansrc/engines/live-sessions.tsrow in Internals, and an invariant that a live tick never changes evaluation semantics. Two curated OKF playbooks carry the durable rules:okf/playbooks/live-evaluation-sessions.md(protocol invariants, eligibility, continuity, not-a-result responses, failure memos and their cost) andokf/playbooks/chart-apply-and-bar-windows.md(repair-never-drop at thesetDataboundary, the three bar counts).okf/code/src/engines.md,okf/playbooks/index.md, andokf/log.mdare regenerated bybun run okf:enrich.
[2.23.1] — 2026-10-13
Changed
-
The in-browser Pyodide engine now vendors the PYNE 0.6.9 wheel. Rebuilt with
scripts/sync-pyne-wheel.shfrom the sister pyne repo (1.39 MB, verified byte-identical inpublic/vendor/andvendor/), which brings the browser engine up to parity with the Pro API:- the
array.copy(na)codegen no longer emits a deadlist(None)into generated Python; Runtime.create_session/append_bars/update_last_barare present in the wheel, so a Pyodide-backed session is possible without a second runtime.
The hard-coded wheel URLs in
src/engines/catalog.ts(preload warm list and themicropip.installcall) and the wheel references indocs/plugins/engines.mdx/docs/architecture/evaluation.mdxmoved with the filename.sync-pyne-wheel.shprints a warning when that path drifts; this time it was caught and followed rather than ignored. - the
Fixed
- The vendored-wheel lock test no longer breaks on every wheel bump.
tests/pynescript-interpret-ta.pypinned the literal filenamepynescript-0.6.8-py3-none-any.whl, so it failed as soon as the wheel was rebuilt — a test that fails on a routine dependency bump is a test that gets deleted. It now resolves the single wheel invendor/and fails with the actual set of filenames when there is not exactly one, which keeps the lock (the wheel must still import and evaluateta.sma/ta.stdevin interpret mode) while removing the version coupling.
[2.23.0] — 2026-10-13
Added
- Rolling window (Settings → Historical bars, second field): caps how many bars reach the engine on every run, default 10,000 (
ROLLING_WINDOW_DEFAULT, clamped 200–200,000). The chart keeps the entire dataset — this bounds only what is evaluated and shipped back. Added because the Data Source Manager can grow a dataset toDSM_MAX_BARS_PER_JOB(50k), and every extra bar costs evaluate time, encode time, and full-size response-envelope bytes on every run including each live tick. Explicitopts.bars(HPO holdout / walk-forward) is windowed too, so a trial cannot quietly exceed the user's budget. - Dataset size in the status strip: the existing
N barsreadout covers chart bars, which understates the dataset once a deep backfill has run. A· dataset 20.0ksegment now appears whenever the DSM memory mirror holds more bars than the chart, sourced frompeekDatasetand kept reactive viasubscribeDatasets.formatBarCountkeeps the readout inside the monospace strip: exact below 10k, grouped above. /health?deep=1reports live upstream reachability: shallow/healthstays cheap and binding-only, but?deep=1pays oneHEADper egress peer (Binance, MEXC, DefiLlama, GeckoTerminal) and returnsstatus: "degraded"plus a per-hostupstreamblock, downgrading only the plane that failed. Probes useHEAD— DefiLlama's cheapest GET is ~9 MB / 2 s and would abort a shared timeout while proving nothing. Still returns HTTP 200: a third-party outage must not flap uptime probes.- Git-native script storage on Cloudflare® Artifacts (no GitHub creds): optional
SCRIPT_ARTIFACTSbinding mirrors every script save/delete as a git commit to a per-API-key repo (scripts/<id>.pine+ meta sidecar viaisomorphic-git), history endpoints serve from git log with D1/memory fallback, andGET /api/scripts/_git+POST /api/scripts/_git/tokenhand out clone remotes plus short-lived repo tokens for any standard git client. Unbound workers behave exactly as before. Identical re-saves dedupe against HEAD (no clone+push, no history spam);POST /api/scripts/:id/restoreresurrects any version as the new tip, recreating deleted scripts. Setup:axis setup artifacts [--namespace axis-scripts](writes[[artifacts]], verifies namespace access); docs indocs/worker/data-plane.mdx.
Fixed
- Market + on-chain proxies work again in production (100% failure rate fixed): both
fetchUpstream()helpers passedredirect: 'error', which workerd rejects (Invalid redirect value, must be one of "follow" or "manual"). Every/api/market/*and/api/onchain/*call returned 502UPSTREAM_NETWORK, so DefiLlama TVL and GeckoTerminal DEX overlays were dead in the deployed Worker. Bun/undici does support"error", which is why all 3,419 unit tests stayed green. Now a sharedfetchUpstreamJson()inworker/src/http.tsusesredirect: 'manual'plus an explicit 3xx /opaqueredirectrejection — the original A10 intent preserved on every runtime. Regression guard added:worker/tests/upstream-fetch.test.tspins the redirect mode and both proxy paths. - Upstream error text no longer leaks to clients: the 502 body used to echo the raw runtime message (workerd internals, undici phrasing) verbatim. Failures now reduce to a short reason —
upstream unreachable/upstream timed out/upstream redirect refused— viaUpstreamError, and the signed Binance path never logs or echoes its HMAC-signed URL. - CI now installs worker dependencies before the root test run.
bun run testexecutesbun test tests/ worker/tests/from the repo root, but the worker is a separate package with its own lockfile; CI only ran a root install. Any worker test importing a worker-only dependency therefore died withCannot find package— nine tests failed onisomorphic-git, and the coverage gate never ran. It passed locally only becauseworker/node_moduleswas left behind from a manualcd worker && bun install. - The coverage gate is enforced at its measured floor, not an unreachable target. The 95% rung of the 70→80→87→90→95 ratchet was set but never met, so CI had been red on main since before 2026-10-13.
check-coverage.mjsnow separates an enforcedBASELINE(89%, the real number) from an aspirationalTARGET(95%), and reports the exact shortfall — currently 1,165 lines, concentrated insrc/store/index.ts,src/chart/manager-access.ts,src/storage/local.ts, andsrc/data/data-source-manager.ts. The floor's job is to stop coverage going down; nothing here makes the gap smaller. - Unit suite is green again (
bun run testexits 0): two independent causes. (1)tests/helpers/mock-solid.tsis preloaded so Bun's automatic JSX runtime resolvessolid-js/jsx-dev-runtimeto a runtime that actually implementsjsxDEV— the package's own export map points that specifier atdist/solid.js, which has nojsxDEV, so every test whose import graph reached a.tsxdied withSyntaxError. Root-levelconditionsinbunfig.tomldoes not affect Bun 1.4.3's runtime resolver, hence the preload. (2)tests/pane-badge.test.tshad a stale expected-button list from before the Pine tables button landed in v2.22. - E2E no longer asserts against a UI that moved:
loadMockBarsand the smoke spec seed{ autoload: false }so the manual Load button is actually rendered (it is hidden by default), and the seed merges on reload instead of resetting app state.selectDrawingTooltargets[data-drawing-group]+ the caret flyout instead of a group label, which the caret button shadowed. The Script Library spec switches to the Personal tab, where the storage picker and save form actually live. Drawings persistence reloads bars before asserting shapes. - Chart
setDataboundary guard: history apply now sanitizes candle/volume payloads (drops non-finite times/values and non-ascending times with a counted log line) and reports candle vs volume failures under distinct contexts, instead of surfacing a bare library assertion (Value is null) on every redraw after one poisoned row.
Changed
- Bun is pinned to 1.4.3 across all workflows, plus a
packageManagerfield inpackage.json. Coverage measurement was Bun-version-sensitive: the identical suite reported 81.31% under 1.3.14 and 89.82% under 1.4.3, withsrc/store/index.tsalone reading 35.7% vs 88.4%. The gate was measuring different code depending on the runner.
Docs
- Removed dead references:
hoox-sh/pine-workerandhoox-sh/hoox-landing-page(both 404) and the unpublishedhoox-sh.pyneVS Code Marketplace link. - The PYNE Agent plugin install URL is now labelled as a reference deployment rather than project infrastructure, with a pointer to
hoox-sh/pyne-agent-workerfor self-deploy.
[2.22.1] — 2026-10-11
Fixed
- Fib circles / arcs respect X/Y ratio:
fibCirclesandfibArcno longer force pixel-perfect circles from the hypotenuse. Radii are now independentrx=|dx|·lvl,ry=|dy|·lvlellipses — round only at 45°, with circular fallback for horizontal/vertical trends so levels stay visible. Hit-testing uses normalized ellipse distance plus trend-guide hit; drafts preview ellipses;create()stamps dual-shapepoints[]so moves stay in sync. - Fib circles background fills relocate with strokes: nested ellipse fills (largest→smallest,
fillOpacity, per-level colors) added tofibCirclesand half-annulus fills tofibArc, matching the style dialog Background toggle. Fills derive from the samerx/ryas strokes so body-move/resize relocates them together. - N-point drag + fill relocation:
hitTestHandlenow exposes every vertex for fixed 3–5pt tools (channel, pitchfork, fibext, fibchannel, fibWedge, triangle, xabcd, headShoulders) instead of only the first three; freehand (brush/highlighter, long polylines) stays body-move-only to avoid DOM thrash.resizeDrawinghandles genericpN,shiftDrawingkeepsp2=lastfor n-pt tools sop1/p2mirrors no longer desync frompoints[]after a move. - Drag perf: pointermove drags coalesce to one user-layer rebuild per frame (
dragRaf, flushed on pointerup) instead of a full SVG rebuild per event; pan/zoom path unchanged (script/fills never rebuild on drag). - Long/short fills honor style: profit/loss rects use
ctx.fillOpacityinstead of hardcoded0.18. - Indicator
fill()/linefillrelocation:ensureScriptPaneLayerrefreshes its Y series + bar clock on every call (stale removed-series scale misplaced sub-pane fills);runAndApplyclears this owner's fills on the non-target layer when overlay toggles so fills don't duplicate/linger;clearScriptChartOverlaysclears only the hidden script's drawings/fills on shared sub-panes (previously destroyed the whole layer, wiping siblings);plotFillsSignaturesamples up to 16 interior points so historical rewrites with identical tips still invalidate the cache.
[2.22.0] — 2026-10-10
Fixed
- Full-app review fix batch (2026-10-10, 152 items, plan
.opencode/plans/review-fixes-2026-10-10.md): seven parallel review passes (Worker, engines, data/streams/chart, store/SW, UI, alerts/on-chain/MCP, CLI/desktop/CI) with every finding re-traced before fixing. Suite 3418 pass / 1 pre-existing fail (tests/pane-badge.test.tsstale expectation fromfa15352b, untouched); typecheck clean; build passes;bun run lintexits 0; CLI 79/79. - Worker
/api/runacceptsmode:'auto': the PWA default was rejected with 400 when the Worker was selected as engine;autonow normalizes to interpret/compile, with a contract test on the PWA request body. - Worker hardening: billing idempotency keys carry a per-isolate random prefix (no more cross-isolate dedupe); usage metering keyed by key hash; public on-chain/market proxies get per-IP rate limits, in-flight coalescing, and bounded cache keys; request bodies capped (32 MiB run / 8 MiB scripts) with Content-Length pre-checks; generic 500 bodies instead of raw
err.message; upstream abort covers the body read withredirect:'error'; console verify has a 2 s timeout behind an IP pre-limit; script PUT is CAS (UPDATE … WHERE revision=?→ 404/409); timing-safe admin-token compare with attempt gate;clientIpfromCF-Connecting-IPonly;Vary: Originon CORS;/api/streamper-IP gated. - Secrets out of localStorage: Worker API key, git PAT, and agent key now live in a session vault (memory + sessionStorage) with boot migration of legacy values; persisted payloads strip secret keys; cloud endpoints require https (loopback excepted); MCP
alerts.*no longer echoes webhook URLs. - Service worker correctness:
/api/*responses are no longer cached (auth-ignoring cache could serve the previous account offline);skipWaiting()on install removed so updates wait for the banner instead of reloading mid-edit; unhashed pyodide/plugin files are network-first while hashed/assets/*stay cache-first; pyodide gets its own cache;public/sw.jsandsrc/sw/strategy.tsparity is enforced by test. - Persistence robustness: persisted payload gains a schema version (
PERSIST_SCHEMA_VERSION=1) with a migration hook;topbar/onchain/drawingUiare now persisted (they reset on every reload before); per-section hydration so one bad field no longer voids the blob; unknown keys dropped; numeric bounds validated; nested defaults cloned instead of aliased; corrupt-blob backup is capped and only clears after a successful write; cloud drafts serialize; update dismissals stick per session; stale editor popout resets to docked. - Engine transport parity: WS frames now carry
libraries; one shared JSON sanitizer preservesNaNinside string literals on both transports; Pyodide receives scriptinputs(HPO and Script Settings overrides work) and reports unsupported fields instead of silently dropping them; WS/REST both honor abort signals; Pyodide load failures get a 30 s cooldown; cross-transport conformance fixture locks WS ≡ REST ≡ Pyodide. - Live data and chart races: progressive repaint stops on CSV upload; "apply when complete" re-checks the current symbol; DEX pool keys keep case; Kraken live bars align to interval start like history; DSM-first failures fall through to venue fetch; stale-run flag clears are epoch-guarded; dead ccxt sockets take Live offline; destroyed panes purge overlay meta and listeners; bar-color repaint respects Heikin-Ashi and replay; overlay color edits resolve owner-scoped keys; catch-up repaints respect replay; backfill validation is every 4th page; progressive repaint appends instead of full
setData; single sharedexchangeForSource; tick coalescing per bar slot; deadsrc/streams/binance.tsduplicate removed. - Alerts correctness: live bars carry the stream's symbol/interval and mismatches are dropped (no more BTC ticks firing ETH alerts); persisted per-symbol prices with a default cooldown stop reload re-fires;
pct_changebase latches at create; on-chain watermarks keyed per protocol;data:/blob:plugin URLs rejected in prod;app.eventallowlisted; engine endpoint must be an https origin; per-tick alert path caches instead of parsing localStorage twice. - MCP host:
mutatesenforced in dispatch (same-origin page scripts are read-only; ticket-authenticated bridge traffic may mutate);includeBarsreturns bounded rows instead of"[array N]"; bridge replies on the originating socket; event allowlist of 22 real listeners. - UI and design system: shortcuts modal mounted;
--color-muteddefined (66 uses); extra price/alert cards use theme tokens; focus trap installed in settings/symbol/command modals withfocusincontainment;ExtraMenuis a dialog, not a menu; deadSettingsDialog/AppDrawer/PluginManager/WorkersManagerdeleted (live panels extracted tosettings/panels.tsx); hardcoded hex replaced by tokens; off-palette fallbacks fixed; shared.sc-popoveranddismissOnOutside;FloatableShellno longer buildsdoc.writeHTML; shared 1 Hz clock; timers tracked and cleared; keyboard-recording pulse honors reduced-motion; non-null assertions narrowed. - CLI, desktop, and CI:
setup --prodfails closed (exit 1) on D1/KV failure and writesALLOW_OPEN_KEYS=""; deploy refuses open-keys configs with D1-but-no-KV; template ships the flag empty; doctor requires KV when D1 is bound; Tauri CSP added; Windows binary resolution and.cmdspawn fixed with tests; npm token scoped to publish steps; compose binds127.0.0.1with production Flask default; https enforced for admin/key traffic; secrets leave argv;secret putno longer redeploys by default; wrangler pinned with ENOENT-only fallback; Tauri read errors surfaced; nginx runs as non-root on 8080; pyodide downloads SHA-256-verified; workflow permissions scoped;ref_nameviaenv:; e2e waits on state, not sleeps; unit coverage for CLI helpers and pure functions.
Added
- Chart settings panel: floating panel (chamfered SVG frame) opened from a new gear button in the price-scale cluster (
axis-chart-scale-settings), anchored top-right of the active chart. Controls chart type, interval (reloads data), log / auto scale, and the last-value / plot-name / price-scale label toggles. Stays in sync with the scale cluster and chart context menu; closes on Escape. Open state is UI-only (src/ui/chart-settings-state.ts), not persisted. - Chart settings entry points: the chart context menu's “Chart settings” row now toggles this panel (id
chart.settings, previouslyapp.settings, which opened app Settings; app Settings stays reachable from the topbar and palette). New command-palette entry “Chart Settings”. - Pine tables in the bottom panel (September 2026 parity): any script's
table.*drawings can now move off the chart into a bottom Tables tab via “Move tables to bottom” (chart context menu, pane-badge table button), and back via “Move tables to chart” (same menus + Tables tab header). Bottom tables stack vertically in creation order regardless ofposition/force_overlay, stretch to the pane width with proportionaltable.cell(width=…)columns, never truncate text (scrollbars when needed), keep tooltips on hover, honormerged_cells, and allow text selection + per-table TSV copy — matching TradingView®'s bottom-panel rules. Placement persists per script (pineTablesLocation); chart HUD hides moved tables; newTablesbottom-docked panel, Topbar toggle, command-palette entry, andpanel.tableschrome. - Pine table model forward-fill:
PineTableCellnow carrieswidth/height/tooltip/ border hints andPineTablecarriesforce_overlay/merged_cells, parsed from either engine shape (interpret + compile) so proportional bottom sizing, tooltips, and merges work as soon as the engine emits them.
Changed
- Studio UI elevation and motion: layered shadow tokens (
--ax-shadow-ambient/-card/-raised,--ax-drop-shadow) with light-theme variants; 150 ms transitions and focus rings on buttons, cards, chips, tabs, and rail items; press feedback on buttons and cards; all motion disabled underprefers-reduced-motion. - Pyodide 0.26.2 → 0.29.5 (Python 3.13.2): self-hosted runtime replaced in place — same file layout (
pyodide.js+pyodide.asm.js, no.mjsmigration needed), so loader, prefetch, probe, and worker CDN paths only change version strings. Bundled micropip 0.6.0 → 0.11.1 and packaging 23.2 → 26.2 from the new lock file. Verified end-to-end in Node: 0.29.5 boots, wheel 0.6.8 unpacks,run_scriptinterprets a real Pine script successfully. - pynescript wheel 0.5.0 → 0.6.8: rebuilt from the sister pyne repo via
scripts/sync-pyne-wheel.sh(85 commits, incl. September 2026 Pine release-notes parity); stale wheels dropped, hard-coded paths insrc/engines/catalog.tsupdated. Editor builtin metadata already in sync; interpret lock (ta.sma/ta.stdev) passes. - Docs review vs last 20 commits: fixed stale post-2.21.0 references (pre-Solid
state.js/main.js/server.ts/rootsw.jsnow past-tense acrossui/store,architecture/{overview,state-namespaces,adrs},devops/build-and-serve), bumpeddocs/index.mdxto v2.21.0, and documented previously uncovered changes — Pine tables bottom panel (enduser drawings guide + parity invariant), extras ticker controls / float-only time+price panels / big-price watchlist rows / panel motion (ui-shell), shared Worker plumbinghttp.ts/proxy-router.ts/rate-limit.ts+ clientthrottle.ts(worker/runtime,worker/index, feature atlas), and Storybook lab (local-dev,ci-and-testing, atlas). - Docs repo identity + voice:
docs/llms.txtRepository/Docs-tree links pointed athoox-sh/pyne— nowhoox-sh/axis; enduser builtin-scripts heading de-maintainer-voiced (What AXIS does not ship). Nojango_blockchained-as-source clone instructions exist in docs body (only required AGPL author headers); all clone instructions already targethoox-sh.
[2.21.0] — 2026-10-07
Removed
- Pre-Solid static shell (~3,900 lines): deleted the legacy pre-Solid tree —
src/main.js,src/registry.js+registry.d.ts,src/registry-bootstrap.js,src/plugin-types.d.ts,src/chart.js,src/state.js+state-hash.js, thesrc/{sources,streams,engines}/index.jsbuiltin barrels, the eightsrc/ui/*.jslegacy DOM modules, rootpyne-editor.js/style.css/storage.js/sw.js/server.ts, andtests/{registry,state,server}.test.ts. Before deleting, the real import graph was traced fromsrc/index.tsx: no legacy file was reachable and none shipped indist/after a build — the only consumers were those three tests. This leaves exactly one plugin registry (src/plugins/registry.ts), one service worker (public/sw.js), and one state store (src/store).LEGACY.mdanddocs/reference/legacy-shell.mdxrewritten to record what was removed and why it was safe.
Changed
- Legacy plugin registry disambiguated:
src/registry.jsexported its singleton asregistry, the same name as the shippingsrc/plugins/registry.ts— two unrelated classes under one identifier, so a wrong import registered into a dead store with no error. The legacy singleton is nowlegacyRegistry(updated inmain.js,registry-bootstrap.js,ui/manager.js,ui/legacy-topbar.js,registry.d.ts, andtests/registry.test.ts). Traced the import graph fromsrc/index.tsxand confirmed no legacy file is reachable and none ships indist/;LEGACY.mdnow records the full legacy file list, that finding, and why deleting the tree is a product decision rather than a refactor. - Allowlisted proxy routers: new
worker/src/proxy-router.ts(createProxyRouter) turns the/api/onchain/*and/api/market/*planes into ordered route tables instead of hand-rolled if-ladders. The accepted paths are now data (onchainAllowlist()/marketAllowlist(), pinned byworker/tests/proxy-router.test.ts) rather than implicit in evaluation order, which matters because for these planes the route table is the security boundary — they are not open reverse proxies.onchain.tsandmarket.tsshrink by ~100 lines net with identical responses; the preflight, 405, health, validation-error, andNOT_FOUNDpaths are unchanged. One deliberate difference: a path that only shares a string prefix with the plane (/api/onchainfoo) is now declined by the plane (null, 404 from the top-level dispatcher) instead of being absorbed as a plane 404 — same status and code, different message. - Worker HTTP helpers consolidated: new
worker/src/http.tsowns CORS header sets (corsHeaders+READ_CORS/MARKET_CORS/WRITE_CORS/SCRIPTS_CORS/MCP_CORS/ORIGIN_ONLY),jsonResponse, the{ status:'error', code, message }envelope (errorBody/errorResponse),methodNotAllowed,preflight, andclientIp. Replaced seven near-identicalcorsHeaders/json/jsonResponse/jsonErrorcopies acrossindex.ts,onchain.ts,market.ts,git-oauth.ts,scripts.ts,keys.ts,runtime.ts,mcp/{handler,proxy,bridge}.ts, anddurable-objects/session.ts. Header values are preserved verbatim per call site (pinned byworker/tests/http.test.ts), so this is a no-behavior-change refactor. clientIpdeduplicated: the verbatimcf-connecting-ip→x-forwarded-for[0]→unknownchain existed in bothruntime.tsandgit-oauth.ts; one implementation now.- Rate limiter deduplicated: new
worker/src/rate-limit.tsreplaces the byte-identical fixed-window limiters inruntime.ts(/api/run) andgit-oauth.ts(device flow). Behaviour and prune threshold are unchanged; it stays per-isolate and best-effort. - Bearer parsing deduplicated:
keys.tshad its ownAuthorization.replace(/^Bearer\s+/i,'')fallback; it now callsauth.extractBearer, the same readerrequireApiKeyuses. - Throttle guard consolidated: new
src/utils/throttle.ts(createThrottle, leading-edge drop guard keyed per call) replaces three hand-rolledlastTrigger/Date.now()copies inupdate/update-manager.ts,ui/boot-errors.ts, andui/error-share.ts. - Bun-native scripts:
scripts/*.mjs,scripts/okf/*, andscripts/capture-*.tsmoved fromnode:fs/crypto/child_processtoBun.file/Bun.write/Bun.CryptoHasher/Bun.spawnSync/Bun.$(keptmkdirSync/unlinkSyncand ImageMagick/ffmpeg where Bun has no equivalent); capturewebp()step usesBun.Image(byte-identical output at q92, ~1.2x faster). - Panel open/close motion: dock columns snap instantly on open/close (no width/height/margin transitions, so the chart reflows exactly once); the shell plays a smooth clean slide + fade (
ease-out, compositor-only transform/opacity, no flicker/brackets/blink). Hover-slide stays linear on layout props;will-changeis compositor-side only. - Datafeed WS hardening:
/watchsockets now clean up their ccxt watcher on disconnect (previously leaked one watcher per dropped client), with typedBun.servedata,perMessageDeflate: false, and socket timeouts. - Test runner:
bun test --isolate --parallelbenchmarked at ~12.5s vs ~37.8s serial with an identical failure set (seetest:fastproposal).
Fixed
- Ticker drag handle: listeners were only removed on
pointerup, so unmounting mid-drag (emptying the symbol list) leakeddocument-levelpointermove/pointerupagainst a detached node; addedpointercancel,onCleanupteardown, anduser-selectsuppression during the gesture. The drag offset is now persisted instead of being written straight tostyle.transformand lost on reload, and negative offsets are clamped so the band can't paint over the command/module bars. - Extra menu dividers:
.axis-extra-menu-sectionre-declared the border and padding that.axis-extra-menu-rowalready carries, producing two divider lines per section and roughly doubled vertical padding; the section is nowdisplay: contentsand the trailing divider is handled with:last-child(the unreferenced--lastmodifier classes are gone). - Typecheck green: added the missing
src/vite-env.d.ts(vite/clienttypes) —bunx tsc --noEmitwent from 8 pre-existingImportMeta.env/ CSS-module errors to zero. - Flaky UI tests:
workspace-snapshotsample now opens the editor chrome it asserts on;editor-menumatches the<For each={DOCK_MENU…}>prefix (source gained a floatOnly filter);storerapid-switch waits on condition instead of a fixed 50ms sleep. Full suite: 3272 pass, 0 fail. - Datafeed version drift:
/healthversion now comes frompackages/datafeed/package.jsonvia static import (bundler-embedded, compile-safe — same pattern as CLIown-package.ts) instead of a hardcoded string. - CI-only test pollution:
mcp-bridge-tabsresets cloud config inbeforeEach(wasafterEach-only — an earlier suite's stored key leaked into the no-key assertion); the offlineexpand-cachetest uses a unique symbol instead of the sharedcsv-upload/BTCUSDT/1hkey.
Added
- Panel open effect:
panel.openMotion tune (Settings → Theme → Motion) — duration (40–200ms, default 180) and slide distance (0–100% of the panel, default full) for a smooth slide + fade; disabled or reduced-motion zeroes both to an instant snap. - Datafeed standalone binaries: new
packages/datafeed/scripts/build-binaries.tscross-compiles the sidecar (bun build --compile, same 7 targets as the CLI) with a boot +GET /healthsmoke test; addedprotobufjs(ccxt's optional peer dep, required by the bundler) and abuild:binscript. test:fastscript:bun test --isolate --parallel(~12s vs ~38s serial). Known caveat:tests/background-catchup.test.tsintermittently segfaults a Bun 1.4.2 test worker under parallel load (upstream runtime crash, not a test failure) — CI keeps the serialtest.- Storybook dev scaffolding:
storybook-solidjs-vite+@storybook/addon-docswithstorybook/build-storybookscripts,.storybook/config (lucide-solid aliased to the prebuilt ESM bundle), and 19src/stories/entries for brand/icons/panels/studio chrome. - Ticker band polish: fixed the loop seam — spacing moved from a flex
gapon the track topaddingon two identical.axis-extra-ticker-sethalves, sotranslateX(-50%)is exactly one period instead of a gap-width short (the band no longer skips ~1rem every cycle). The clone half isaria-hiddenso screen readers no longer hear every symbol twice; prices usetabular-numsso live ticks stop re-flowing the band mid-scroll; items split into dim symbol / strong price / colored ▲▼ change (the symbol no longer turns green with the change); band gets a fixed 22px height, a panel tint, edge fade masks, and pauses on:focus-withinalongside hover. New purebuildTickerRowsbuilds the display rows (unit-tested). - Ticker customization + controls: persisted sliders for item spacing (0–3rem), band height (18–40px), font size (10–16px), and opacity (20–100%) in the Extra menu; a pause/play toggle, click-to-pin items (pinned symbols keep full contrast + underline, exposed via
aria-pressed), and a drag handle for vertical repositioning (extras.ticker.offsetY, clamped 0–160px, persisted with a "Reset band position" action). Hover still pauses, but the explicit toggle now wins — focus-based pausing was scoped to tick items so the pause button stays operable. Extra menu gained vertical clamping (flip above the button when there's no room below) plusmax-height/overflow-yso the taller ticker stack scrolls on short viewports.
[2.20.1] — 2026-10-04
Fixed
- Price-card trend on live ticks: the Extra price-card arrow now heads the trailing-N trend with the stream-scoped live tick (falls back to the last bar close when no tick), so it reacts to every tick instead of only bar closes.
- Price-card countdown prefix: the bar-close badge reads
-M:SS(e.g.-4:32). - Ticker marquee placement: the marquee is a full-width band under the module bar (app top) — no longer a squeezed item on the right side of the topbar row.
[2.20.0] — 2026-10-04
Changed
- License headers: unified all own-file headers to
Copyright (c) 2026 HOOX · AXIS · hoox-sh (jango_blockchained)/part of axis(waspynescript). Comment-only, no behavior change.
Added
- Watchlist big-price rows:
!button after the alert bell toggles an expanded row — full-cell-width big price (auto-shrinks for long prices / extra decimals) with symbol, change, and prev close in a sub-row. - Ticker marquee options: travel direction (left/right) and a 24h-change toggle join speed + symbol picks in the Extra menu; persisted with the ticker state.
- Float-only time + price panels: compact Extras (normal width, ~2 rows, float only — no layout docks, title-drag moveable). Time shows the candle-close countdown + local/UTC clocks; Price shows the big last price + 24h change for the chart symbol. Both default side-by-side just under the topbar.
- Price-card time badge: left name badge on the current-price overlay — bar-close countdown while bars load, live clock otherwise (tooltip: local + UTC).
- Quote dock panel: full quote sheet (price + trend, 24h change/range, day open, last O/H/L/C, 24h volume, tick time, venue) as a right-dock panel with multirow stacking, toggled from the Extra menu.
Fixed
- Extra chrome widgets (review hardening): ticker starts once per mount and REST-seeds underneath live quotes instead of wiping them; color pickers no longer sit inside the checkbox label so picking a color can't toggle the overlay; alert direction treats
0levels as real levels; disabling the overlay clears any visible/stale alert; price card ignores ticks from a previous symbol; persisted ticker symbols are trimmed + deduped and overlay colors must be#rrggbb; alert-broadcast test now covers the firing path. - Ticker auto-seed: enabling the price ticker copies the watchlist symbols when nothing is selected, so the band appears immediately; single quote-mux lifecycle per mount.
- Price card alignment: overlay sits top-right, clear of the 72px price-scale gutter.
- Extra menu polish: dropdown rebuilt with Studio inputs — toggle switches with hints,
ax-rangesliders in labeled fields (controls appear only when their widget is on),StudioColorInputswatches for alert colors,ax-chippills for ticker symbols. - Quote sheet correctness: day window anchored to an injectable clock (was date-sensitive in tests); 24h high/low loop instead of
Math.max(...spread)which overflows the stack on large histories; live tick ignored when it belongs to a previous symbol.
[2.19.0] — 2026-10-03
Added
- Extra chrome widgets: module-bar
Extrabutton (after Results) with dropdown toggles for a current-price trend card (tick-length slider), a watchlist ticker marquee (symbol pick + speed), and a fullscreen direction-colored price-alert overlay.
[2.18.2] — 2026-10-03
Fixed
- Background-tab data gaps: live ticks no longer depend on
requestAnimationFramealone — the multiplex queues every tick in order with a timer fallback, so hidden/throttled tabs keep every closed slot instead of only the newest.mock-pollemits each missed intermediate slot on wake. Newdata/background-catchupauto-detects trailing staleness / fillable holes onvisibilitychange/focus/online(and after long WS outages), REST-expands the dataset toward now, repaints without viewport reset, and queues a DSM backfill — the chart always delivers correct data.
[2.18.1] — 2026-10-03
Fixed
- Chart engine (Heikin-Ashi): per-chart incremental HA cache instead of a shared singleton, so multi-chart slots no longer thrash each other; live ticks stay O(1) and the price-line tint reuses the computed HA point instead of a second full-history transform. Non-finite bars return null instead of pushing NaN to Lightweight Charts.
- Chart overlays: smart-apply fingerprints are pane-qualified (
pane:key), so the same plot name on price + indicator panes never shares tip state; hline fallback series are owner-scoped like price lines. - Chart paint:
plot.style_*brprimitives sample head/mid/tail plus a sparse hash (and honor recomputeforceFull), so mid-history rewrites repaint; merged candle + indicator markers are capped atMAX_CHART_MARKERS; full-history paint dedup samples first/mid/last OHLCV so gap-fill corrections repaint;scrollToTimecenters on the nearest bar index instead of a fixed ±14d window. - Chart lifecycle: inactive-slot paints restore the global active slot in
finally; price-series swaps create before removing so a failed create never leaves the pane empty; price-series theme subscriptions are tracked per slot; compare alignment skips re-sort when sorted and memoizes on refs + endpoints; rAF coalescer drain is re-entrancy safe; empty volume profiles return no bins so the overlay hides. - Pre-eval typo checker: comma-separated multi-declarations (
const string A = 'x', const string B = 'y',var series bool L = na, var series bool S = na) now index every name instead of only the first per line, and single-line function bodies after=>contribute their bindings too. Large scripts no longer drown in falseUnknown COL2 / __GRID / __MTG / ATS … did you mean …?marks.dayofweek.sunday…dayofweek.saturdayare known builtins, sodayofweek == dayofweek.mondayis no longer flagged. - Drawing tool menu: the group flyout (Fib, Gann, and the rest) opens beside the rail with the full tool name. It is no longer clipped to the rail edge.
- Script delete: removing a chart script owner-clears its Pine drawings, plot fills, and barcolor. Lines no longer stay on the chart when another script is still applied.
[2.18.0] — 2026-09-28
Added
- Drawing example: AXIS Drawings places a line, linefill, box, label, polyline, and table. Open it from the editor Examples menu or apply it from Scripts.
- UI effect manager: Settings → Theme → Motion tunes short chrome flashes. The watchlist price flash is the first effect (duration, up tint, down tint). Later effects register the same way. Reduced motion turns flashes off without forgetting the tune.
Changed
- Future drawings: Pine lines, boxes, polylines, linefills, and labels can sit up to 500 bars past the last candle. The right margin grows to fit that point.
- Drawing gallery: AXIS Drawings places its line, linefill, box, label, and polyline on the candle clock. Pine drawing times are milliseconds; the chart bars are unix seconds, and the mismatch was pinning every anchor to the last candle. The corner table stays up in chart-only mode, and a failed live tick no longer clears the last good gallery.
- OKF query:
okf:queryalso matches file names listed on a concept, sodrawings.tsfindssrc/indicators/builtins. Path segments stay whole words (uidoes not matchbuiltins). The shared agent instructions areokf/playbooks/read-this-first.md.AGENTS.mdstays gitignored and is not compiled into the bundle. - Watchlist tick flash: the watchlist toolbar bolt turns the quote flash on or off. The same switch is Settings → Theme → Motion.
- Watchlist add field: the symbol browse control is a lens inside the input, matching the topbar symbol field.
- Data panel estimate: the backfill form shows the span, dense bar count, and page count for the past date and timeframe. A job past 50,000 bars says how much of that timeframe one run keeps. Job cards show fetched versus expected bars and the window length. Dataset load preview includes the span of the bars that will load.
[2.17.1] — 2026-09-27
Added
- Editor function tree: optional right-rail outline of Pine functions, methods, types, and enums (
editorRightRail: outline). Click a name to jump. Mutually exclusive with the minimap; toggle from the editor overflow menu or command palette.
Changed
- Quiet trader shell: a new session opens on the chart. The status row is Live, last price, and a Compose chip. Diagnostics expands the raw engine, source, stream, and storage chips. Layouts can switch between Trader and Operator. The Data tab keeps the Data Source Manager name in its tooltip.
- Problems stay while typing: the Problems list and underlines keep the last lint / run marks until idle re-lint (or a new run) replaces them. Optional Settings → Editor → Clear marks while typing still wipes mid-keystroke.
Fixed
- Calm live state: reconnect stays an amber dot and a tooltip. Routine loads and reconnects stay in the log. A toast fires when retries are exhausted or the stream recovers from a long outage.
- Chart failures: an empty chart shows a title, one sentence, and one action. HTML from a venue is not printed as a token dump.
- Pre-eval UDT fields with spaced generics:
array <label> labels/array <line> lines(and tab-indented draft arrays) are indexed as declared names again, so Progressbar-style libraries no longer get falseUnknown labels/Unknown linestypos.
[2.17.0] — 2026-09-26
Added
- Right-click menus: the chart plot, price scale, time scale, volume and indicator panes, script names, and drawings each open a menu for the actions that already exist there (reset view, scale, chart type, screenshot, script settings, drawing edit). Right-click a panel title to open its dock menu at the pointer.
- HOOX favicon and live tab title: the browser icon is the HOOX mark. The tab title leads with the last price and its change versus the previous bar, and follows live updates.
[2.16.0] — 2026-09-26
Added
- Named chart themes: Settings → Theme can save the current chart under a name. Each theme keeps a bar coloring. That coloring can also be saved on its own, applied into the selected theme, and updated across every theme that uses it. Editing one color on a built-in preset keeps the rest of that preset.
Changed
- Pattern dedupe: new
src/utils/emitter.ts(createEmitter+createSingleton) shared byThemeManagerandPluginRegistry; registry's 5 ordered kinds collapsed to generic helpers; venueassertHttpOk/assertBarshelpers insources/catalog.ts. No public API or message changes. - Drawing toolbar press states: tool-rail buttons, carets, width chips, and style-bar chips show an accent pressed state. The style bar has a thin divider.
Fixed
- DEX backfill symbol case:
startBackfillandexpandCachedSeriesToNowreusenormalizeLoadSymbolso background jobs no longer uppercase case-sensitive pool addresses (e.g. Solana base58 viageckoterminal-ohlcv). Fixes the order-dependentonchain-load-symbolfull-suite failure. - Script plot colors:
resolvePineColor()resolves Pine forms (color.red,color.new(…),color.rgb(…)) to CSS at every apply point (runner overlays,buildPlotVisuals, shapes/fills/bgcolor/barcolor, Pine drawings) so script colors no longer fall back to axis defaults; per-barcolor.*tokens now survive series coercion. - Persisted plot colors gain provenance: panel picks store
custom: true, so re-runs refresh auto-persisted script/palette colors from the fresh run (script color edits take effect) while explicit user picks survive. One-time self-heal: pre-existing persisted colors refresh on next run. - Chart resize sync: drawing layers no longer rebuild all SVG groups on every resize frame — the viewport tracks each frame while full repaints wait for resize settle (150ms), so overlays, indicator and drawing layers converge with the main chart instead of lagging it. Pane-manager resize paths batch rect reads before
applyOptionswrites, and the ChartHost reflow safety net dropped from double- to single-rAF. - Toolbar drag tracking: the tool rail and style bar track pointer deltas onto the live position, so a drag follows the pointer 1:1. Drop, dock, tap, and slop are unchanged.
- Editor pre-eval typos:
format.inheritis recognized,to/byare not flagged as bare calls, andfor-loop variables, UDT declarations, and multi-line signature parameters count as bindings.
[2.15.0] — 2026-09-24
Changed
- Drawing toolbar polish: extracted
drawings/toolbar/primitives (ToolRailButton,ToolCaret,ToolFlyout,DockMenu, style controls, shared drag factory) soDrawingToolbaris a composition root. Unified 32px grid / 16px glyphs, 16px caret hit area, focus rings, pill count chip, and keyboard-nav flyouts. Settings popover uses matching field/check/level styles. No placement-math or glyph-path changes.
[2.14.0] — 2026-09-24
Added
- OKF bundle:
okf/is an Open Knowledge Format v0.2 cache of the repo (one concept per source directory and doc, imports already linked). The pre-commit hook drafts, links, and lints it from the git index and stages it into the commit.bun run okf:queryandbun run okf:contextread that cache.bun run okf:checkfails CI when the bundle is stale. Curated notes inokf/playbooks/stay put (okf_lock: human). - Connect MCP: the chart loading splash and the status bar offer Connect MCP while the bridge is down. A saved Worker key attaches this tab. Without one, the button opens Settings → General.
Changed
- PYNE Agent plugin 0.1.7: same-origin
/plugins/axis-pine-agent.jsmatches pyne-agent-worker. Chat reads the live Workers Manager endpoint, API key, and persona on each send. A non-http(s) endpoint is refused. Closing the panel aborts the request. Validation status is plain text. Add to editor confirms, and says so when the editor panel is closed. The launcher sits under dialogs. - Studio Settings: General splits chart chrome (appearance, labels, strategy marks, results) from live data, script storage, the Worker key, and MCP. UI scale presets preview until Save; Cancel, Escape, and the studio rail restore the saved scale. Topbar, keyboard, and theme use the same studio controls. Arrow keys move the settings tabs.
- Chart loading splash: the HOOX mark, caption, and status line sit in the center of the chart on desktop and phone. The shared list empty-state style no longer pins them to the top-left.
- Drawing toolbar: the select tool is a filled pointer. The menu arrow sits inside the tool button — the icon selects the tool, the arrow opens the menu. The magnet mark stays inside its button.
Fixed
- ZigZag plots confirmed pivots and the live extreme. It was holding the last confirmed pivot as a flat line, so a rally never left that price.
- plotshape markers (fractals, MA crosses) stay on the chart. The engine sends
truefor a hit; that value was coerced to empty and the series was all blank. - Pine
timesent to the engine is milliseconds. Chart bars are unix seconds.hour()/minute()/ session highlights were reading a ~15 minute slice of the whole history, so AXIS Sessions never lit. - Chart arrows behind Studio: Left/Right no longer pan the chart while a modal is open, so the settings tab strip receives them.
- Script storage label: the storage select is labeled Storage. It lists storage backends.
[2.13.0] — 2026-09-23
Added
- Movable drawing toolbar: a grip docks the tool rail to the left or the top of the chart, or leaves it floating. Docked rails can slide in to the grip. The style bar has its own grip; double-click puts it back beside the rail.
Changed
- Classic colors on Fibonacci and Gann drawings: retracement, extension, time zones, channel, arcs, wedge, circles, Gann fan, box, square, and pitchfork paint each level in a fixed palette (0.618 teal, Gann 1×1 blue). Drawing settings can turn that off or recolor one level.
- Drawing tool icons are separate silhouettes, so the Gann fan, box, square, and Fibonacci tools no longer share one glyph.
Fixed
- AXIS Ultimate plots a value: the built-in no longer calls
ta.uo(the compiler emitted that as empty). It now plots the Williams Ultimate Oscillator from buying pressure and true range. The engineta.uocompiles to the same formula.
[2.12.0] — 2026-09-22
Changed
- Script Library and Layers panels: list chrome is split into
ui/libraryandui/layers. The library search covers built-ins and personal scripts (with a jump to matches on the other tab); Find across scripts and Recent scripts from the command palette now focus that search or show newest personal scripts. Personal scripts filter by kind. Layers sections show counts, drawing rows use labelled eye toggles instead of a nested button, and the drawing list can be filtered.
[2.11.0] — 2026-09-21
Added
- PYNE Agent persona control on Workers Manager (auto / pine / axis / trader), same
writePluginFieldpath as the agent endpoint.
Changed
- Large OHLCV histories stay interactive: 10k–100k candle loads lower LWC
minBarSpacingso fit-content can actually show the full series, precompute conflation on every pane (user-visible priority at 25k+), skip duplicate fullsetData(DSM paint + ChartHost), and throttle progressive backfill paints. Pan/zoom no longer walks the whole series for plot fills, line-break primitives, or volume profile (visible window only); time-scale sync is rAF-coalesced from the price pane; plotshape markers cap at 2,500 newest; Data Window / crosshair updates stop tracking the 45k-bar array identity. - Data Source Manager panel + Dataset manager modal: job card / coverage map / formatters split out; the panel inherits the chart symbol and interval, uses filter chips with counts (Issues instead of Error), and puts Dataset manager in the header. The modal gets a focus trap, two-step delete (no native confirm), sort-by-header, success banner, and arrow keys that no longer steal from date/search inputs.
Docs
- MCP setup/troubleshooting now points at Settings → General → Worker (cloud + MCP) (Data tab is exchange credentials only). PYNE Agent guide documents the Workers Manager persona/endpoint controls.
- Pyodide-in-Worker use case + test conventions:
worker/RUNTIME.mdrewritten to match the actual scaffold (boot wired, wheel/run_scriptstubbed — enabling the flag currently breaks evaluation; completion checklist included);docs/architecture/evaluation.mdxgains the scaffold status and theNO_BACKENDconsumer contract (don't retry);TESTING.mdrecords mock-hygiene, staleness-guard, and generation-advance conventions.
Fixed
- Dataset manager never throws and always delivers:
startBackfillclamps bad dates, remaps the cache-reader source, and always returns a job id. Venue failures retry with backoff then complete as Partial with whatever bars are already cached (jobs no longer die aserror). Empty-cache loads subscribe for progressive paint and kick a background walk after the first page. - System / script log chrome: expanded system-log rows size the timestamp to
13chsoHH:mm:ss.sssno longer runs into the level;.axis-empty-stateuses padding-inline (12px) so the editor Script Logs empty copy is inset when a run produced nolog.*lines. - Drawings no longer paint over the scales: the SVG drawing layer is sized to LWC
paneSize()(plot pane only), so fibs / rays / hlines clip at the price and time axes. Scale-control cluster (A L $ N T) and volume profile use the livepriceScale('right').width()so last-value titles that widen the gutter no longer sit on the ticks. - Compile
fill()plot_meta: auto/compile runs stampkind: fillplus distinctplot1/plot2series keys (untitledplot()→plot_N) so AXIS paints bands instead of dropping them. - Pine
fill/linefillgeometry: untitledplot()fill edges no longer both bind to the first series (plotvsplot_2); per-bar fill color splits the SVG band so Ichimoku-style clouds flip;line.fill/linefill.newquads order each line by time so oppositeline.newdirections do not bowtie. Live re-runs pick up mid-band color/shape changes. - MCP streamable HTTP handshake:
GET /mcpwithAccept: text/event-streamreturns 405 (stateless JSON-RPC, no SSE). Grok and other Streamable HTTP clients no longer parse discovery JSON as an event stream. - MCP invoke no longer fans out to every tab:
McpBridgeDOsends each/invoketo the newest attached socket only and ignores late replies from other tabs, so two open AXIS windows cannot both mutate the chart for one agent call. - Worker key rotation reconnects the MCP bridge: changing, clearing, or generating the
pn_…key compares the hashed session, disconnects the old socket, and reconnects (300ms debounce while typing; only well-formed keys attempt a socket). Settings dialog Save uses the same path. - PYNE Agent uses live plugin config: chat submit and the floating launcher re-read
getConfig()so Workers Manager endpoint/API key/persona edits apply without a reload. - MCP bridge WebSocket no longer puts the long-lived key in the query string: the PWA mints a 30s single-use ticket over HTTP (
GET /api/mcp/bridge?issue=ticket) and connects with?ticket=. Discovery stops advertising query bearer. JSON-RPC batches are capped at 10 items, charged per method against the 60/min limit, and bodies above 1 MiB fail closed before parse. - MCP reconnect toasts: socket blips log at
infowithout a toast; warn/error toasts only after the backoff ceiling. Ticket-mint failures use the same ceiling. - MCP bridge tickets survive Durable Object hibernation: one-time nonces are stored in DO storage (30s TTL, single-use), so mint→upgrade is not lost if the isolate sleeps between the two requests. If the targeted tab disconnects mid-invoke, the Worker fails
SESSION_OFFLINEinstead of waiting out the 20s timeout. - Flaky suite: shared-Response fetch mock + stale DSM status —
tests/git-oauth.test.tsdevice-flow tests intermittently timed out because the queued fetch mock re-served one single-useResponse(a leaked background task from another suite consumed its body →res.json()fell back to{}→ endlessauthorization_pendingpolling). The mock now builds a freshResponseper call. Separately,ensureDatasetComplete's detached closure announcedsetStatus('ready')unconditionally, clobbering newer loads/errors; it now takesstillCurrent,load-symbol.tspasses its generation guard, and the_reset*Generationtest helpers advance (never zero) so stale guards can't re-arm. - MCP
structuredContentalways an object: app-plane array results (indicators.list,logs.get,alerts.list,library.list,drawings.list) and primitiveapp.get/settings.getslices failed strict MCP clients (expected record, received array/string). WorkerjsonTextnow envelopes non-objects as{ result }. - MCP
chart.zoomacceptsactionalias:{"action":"reset"}previously fell back toin; nowop/actionare equivalent. - MCP
drawings.toolvalidates: unknown tools now failBAD_TOOL, missing tool failsNO_TOOL(previously set empty string). - Market Binance failover:
403/451/429fromdata-api.binance.visionnow triesapi.binance.combefore surfacing; geo-block HTML no longer sticks on first upstream. - PYNE Agent endpoint dead-end: installing the agent plugin from the same-origin
/plugins/axis-pine-agent.jsleftendpointempty with no UI to set it, so every chat failed withSet plugin config: endpoint = your pyne-agent-worker HTTPS URL. Remote installs now seed the endpoint from the install origin and same-origin installs fall back to the production worker origin (backfilled on boot for existing installs); Workers Manager → PYNE Agent gains an editable Agent endpoint section (worker URL + API key,component:pyne-agent). Self-hosters (localwrangler devin the sisterpyne-agent-workerrepo) override the URL there.
[2.10.0] — 2026-09-18
Added
- MCP agent-activity dot: the status-bar capsule gains a second dot that lights while an agent invoke is in flight (bridge tracks
lastInvokeAt/lastCapability; tooltip names the last call). The first dot keeps showing socket state + tab count. - Faster app-update prompts: version poll tightened to every 2 minutes and now also fires (throttled) whenever the MCP bridge connects, so open tabs notice a deploy without a manual full reload.
- Toast notifications + Settings → Notifications: transient bottom-right toasts for runs, data loads, DSM completion, stream connect, Pyodide ready, library save/delete/restore, layouts, workspace import/export, settings saves, screenshots, and git connect. Every toast is written to System Logs; warn/error log entries raise a toast automatically. New Notifications tab (modal dialog, studio Settings page, command palette) holds the master switch, severity floor, per-category toggles (run / data / stream / engine / scripts / workspace / system), and flood control (auto-dismiss duration, max visible, repeat-dedupe window collapsing repeats to ×N). Prefs persist; toasts stay in-memory only.
Fixed
- MCP
indicators.removeleft ghost panes: it deleted only the store row, so chart overlays and empty indicator sub-panes stayed painted (and the orphan panes persisted across reloads). It now uses the same detach path as the Layers panel (overlay cleanup, sub-pane destroy, orphan sweep).indicators.updateroutesvisiblethrough the chart-synced visibility path and rejects unknown ids. - MCP activity in System Logs + filters: bridge connects, disconnects, tab-count changes, and every agent invoke now log under source
mcp(with outcome + duration). The System Logs strip gains source and level filters (count showsshown/total, copy follows the filter);logs.getaccepts{ source?, level? }.
[2.9.0] — 2026-09-17
Added
- MCP drawings control: new
app_invokecapabilitiesdrawings.add(place a drawing by kind + time/price points, validated through the same normalizer as the chart layer),drawings.update(patch points/style/meta/visible by id; kind is immutable), anddrawings.remove(delete by id).drawings.listaccepts an optionalsymbolfilter. - MCP settings control: new
settings.get(sanitized app settings — endpoint, engine, interval, history bars, live prefs, UI scale, label/strategy/telemetry prefs; secrets never exposed) andsettings.set(allowlisted patch with the same clamps as the Settings dialog; interval/history changes reload the chart, unknown engines and secret keys rejected). - MCP/session status-bar indicator: new
McpHudcapsule (socket state + attached-tab count for the key's session, click opens Settings → MCP). Backed byGET /api/mcp/bridgewithoutUpgrade, which now returns theMcpBridgeDO{ connected }count instead of426; the app polls it every 20 s while the bridge is open.
[2.8.0] — 2026-09-17
Added
- Script Library tabs: Builtin (110 first-party AXIS Pine scripts, searchable and grouped by category — Apply runs one onto the chart, Edit opens its source in the editor to fork under Personal) and Personal (the existing storage-backed user library: backend, save/import/export, published, version history).
- Unified Worker API key setting: Settings → General → Worker (cloud + MCP) is now always visible (no longer hidden behind storage engine
cloud). Onepn_…Bearer unlocks both cloud script storage (/api/scripts) and the MCP server (/mcp+ tab bridge); typing/generating/testing the key live-persists it and reconnects the bridge. MCP hints and the bridge idle error now point at this section instead of the stale “Data tab”.
[2.7.2] — 2026-09-17
Fixed
- Reload wiped all persisted state:
parsePersistedStatethrew during boot becauseclampUiScalereadUI_SCALE_STEPbefore its declaration evaluated (TDZ); the blanket catch returned null andloadPersisteddeleted the key, so every reload reset watchlist, settings, and everything else to defaults. Scale bounds moved above boot use; corrupt payloads are now backed up to<key>.corruptwith a console warning instead of silently deleted. Regression covered bytests/persist-boot.test.ts(fresh module-evaluation round-trip).
[2.7.1] — 2026-09-17
Fixed
- Watchlist venue coverage: MEXC now REST-polls at
refreshSec(badgerest) instead of freezing after one seed — MEXC WS is kline-only, so there is no ticker mux. Kraken gains genuine Kraken REST tickers (legacyXXBTZUSDkeys normalized; never mixed onto Binance) with the same REST poll. Data Manager quotes resolve to the underlying venue for both REST and WS (previously the REST seed always fetched Binance while WS followed the selection). CCXT-gateway status honestly reports no ticker endpoint yet. - Worker MCP Durable Object uses
new_sqlite_classes(MCP_BRIDGE/McpBridgeDO). Cloudflare® accounts no longer accept KV-backednew_classes(error 10099). - CLI
healthFeaturestest coversmcp/mcpBridgeflags (fixesv2.7.0npm publish which failed onhealth.test.ts).
[2.7.0] — 2026-09-17
Added
- MCP server on the Worker (
POST /mcp, Bearer API key): tools for health, generic allowlistedaxis_request,/api/run, scripts, keys, onchain, and market; resources and prompts. Live-app tools (app_invoke/app_get/app_set) RPC to a connected PWA viaMcpBridgeDO(/api/mcp/bridge). In-app host (window.__AXIS_MCP__) covers chart, editor, indicators, alerts, library, watchlist, panels, plugins, theme, drawings, workspace, logs. CLI:axis mcp(stdio proxy) andaxis mcp config(@hoox-sh/axis-cli0.3.3). Settings → General → Connect this tab to MCP. Docs: MCP (agents) · MCP server. - Pane script badge load source (file icon, second after settings) opens that script in the docked editor.
- Topbar and editor Add ▾ menus include Add built-in script… (same picker as ⌘K).
- Screenshot (camera, module bar, before Studio): capture price / all panes / workspace, drawings overlay, watermark, 1×/2×, copy or download PNG. Session icons (theme, fullscreen, chart-only, screenshot, Studio) sit on the right of the second topbar row.
Fixed
- CI unit: SVG stub implements
replaceChildren; on-chain health probe assertion no longer treatsworker.axis.hoox.shas the SPA host; Python runtime locks skip when numpy/antlr4 are missing on the runner. - E2E:
/runmock matches engine paths only (not Viterunner-*.js); Script Logs waits for the docked editor instead of toggling it closed while the lazy pane loads.
[2.6.8] — 2026-09-16
Added
- First-party built-in scripts: original AXIS Pine catalog (moving averages, channels, oscillators, trend, volume, volatility, pivots, session, and classic strategies). Scripts panel Built-in picker and command palette Add built-in script… (type
rsi/macdto jump). These are AXIS originals — not TradingView® templates. - Named watchlists: create, rename, duplicate, and delete lists. Lists persist with the workspace (
watchlist.lists+activeId); a legacy symbols-only payload becomes a Main list. - Watchlist alerts: row bell opens a compact price alert (cross / above / below) at last.
- Autoload chart (Settings → General): when on (default), symbol / interval / venue changes fetch immediately and the topbar Load button is hidden; when off, Load is shown and field changes wait for an explicit load.
Fixed
- Built-in studies: Sessions uses
hour/minutein the chosen timezone (PYNEtime()ignores session args); VWAP / VWAP bands reset on the daily change; TRIX is triple-smoothed EMA (not TEMA); Rel Vol is Dorsey RVI; ZigZag plots confirmed pivots; pivot-reversal latches the last pivot for exits; applying a built-in no longer copies the editor input bag. - Watchlist rename Escape cancels (blur no longer saves the in-progress name).
- Overlay opacity slider is shown only for edge chart overlays — float/window docks no longer expose a no-op control.
- Hover-slide idle hide waits while the panel has focus, the dock menu is open, a drag is active, or the watchlist alert popover is open, and re-arms when those end so typing (or the portaled form) does not collapse the peek strip.
- Watchlist price-alert popover is portaled (and flipped above the row when it would clip) so last-row Create is reachable; outside click / another row / removing the symbol dismisses it.
- System Logs strip was locked to 28px and horizontally centered, so it could not expand. It is left-aligned and expands to the log body again.
- Status-bar N bars opens the Data Source Manager.
- Library script cards were forced into a 32px centered row.
- Layouts menu is portaled out of the command bar so it is no longer clipped.
- Plugins catalog Use activates the selected source / stream / engine / storage again.
- Pyodide interpret no longer crashes at bar 0 with
_expect_series() got an unexpected keyword argument: the runtime was replacing pyne 0.5's helper (which takeslast_sample_ok/allow_period_only) with a 2-arg shim. Drop the staleta.stdevfull-recompute override and wrap_expect_number(dict-defaultunwrap, then the wheel helper) instead of replacing it.
Changed
- Topbar / editor Run controls are labeled Add (same apply behavior; ▾ still adds another instance).
- Chart overlay keeps dock layout: overlay-on panels sit on the chart inner edge (plot does not shrink). Overlay-all makes the chart full width with panels on top. Overlay-off panels stay on the outer frame. Overlay panels stack in a single column (not side-by-side). Menu includes an opacity slider (default 75%). Slide-on-hover hides after 3s idle.
- Panel chrome: the per-panel icon is the dock menu (click) and drag handle (hold). The extra hamburger glyph is gone.
- Topbar command bar: venue + symbol (search inside the field) + interval; type + compare + reload; thin dividers between groups; theme / fullscreen / chart-only / Studio (gear, accent, trailing) on the right.
- Workstation chrome polish: two-bar command + module switch, 4/8px grid, 1px hairline, periwinkle
#8B9CFFon#07080C/#0C0E14, Live as a 6px pulse, HUD capsules, 32px watchlist rows, 32×32 drawing rail, 26px status strip, tablet overlay drawers, mobile Chart/List/Editor/More. Same product — density and grouping only. - Engine picker is a 3-icon switch (server / edge worker / Pyodide). Run, Live, and Replay share one action cluster.
- Default history depth is 5000 bars (
HISTORY_BARS_DEFAULT). Existing savedhistoryBarsvalues are unchanged.
[2.6.7] — 2026-09-14
Added
- Convert to Pine v6 in the editor overflow menu and command palette. Rewrites any older version (missing
//@versioncounts as v1) toward v6: colors /n/ timeframe names,ta.*/math.*/request.*,study(→indicator(, typedinput.*(),iff/offset. Not a semantic v6 migrator. - Alerts panel: create form covers percent-change, drawing-touch, and indicator plot conditions (not only price / on-chain). Delivery is browser notifications, a compact HTTPS webhook, and an optional L2 webhook (
channel: "l2"payload). Live stream ticks evaluate the book. - Pine
alert()/alertcondition(): interpret runs export enginealertsinto the book (pine_alert). Arm any/alert()/alertcondition()(optional title) on a strategy or indicator; last-bar watermark avoids backtest spam. Strategyalert_messagefills are included. Pyodide interpret exports the same payload. - Compile-path Pine alerts: in-browser compile records
alert()/alertcondition()in the object-mode bar loop (PYNE's compiler used to emit empty statements for Numba). Remote Flask/pyne-worker compile still cannot collect Python alert events, so AXIS sends interpret when the script calls those builtins.
Fixed
- Fold marker labels were inverted (expanded blocks announced “Unfold”). They now read Fold block / Unfold block.
- Convert to Pine v6 no longer prefixes UDF definitions, parameters, or unpack targets that collide with
ta.*/math.*(set01–04 older scripts parse 323/323 after convert).
Changed
- Cloudflare® rename: Worker script
pynescript-axis→worker-axis(https://worker.axis.hoox.sh). Pages projectpynescript-axis→axis(https://axis.hoox.sh; previews*.axis.pages.dev). Health JSONserviceisworker-axis. Legacypynescript-axis.pages.devandpynescript-axis.*.workers.devhosts still match in CORS / client heuristics. - Live stream on by default: Settings → General → Enable live stream (
live.preferAfterLoad, default on). Saving starts or stops the venue WebSocket; boot starts live when history is already loaded. Persist no longer writes a false default when the flag was missing. - Server/Pyodide execution default is Auto (try compiler, fall back to interpreter). Strict Compiler is not the default: Flask/Numba is not prewarmed, first compile JITs, and some Pine stays object-mode. HUD and first-run now follow the engine schema instead of showing Interpreter when mode was never saved.
- Editor fold gutter: 14px Lucide chevrons replace the 10px
▾/▸glyphs (down = expanded, right = folded), with a clickable⋯placeholder on folded blocks. Tab close/add and header Run/Library/Profiler use the same icon set and size. - Editor color tools: one working color (picker overlay + any-form input + transparency) instead of a disconnected converter that stayed on
#939fff. Chip captions use#RRGGBB tNsocolor.new(...)is not truncated; formats list copies the draft; Replace / Replace all / Jump; checkerboard swatches show Pine transparency.
[2.6.6] — 2026-09-12
Fixed
axis secret put ADMIN_TOKENvs Cloudflare® 10053 (@hoox-sh/axis-cli0.3.2): empty[vars] ADMIN_TOKEN = ""is still a binding, so Wrangler refuses the secret.axis secret putnow comments the plaintext var, deploys once to drop it, retries on 10053, then sets the secret.axis keys createprompts for the token (CLI cannot read Worker secrets) and doctor warns on a leftover[vars]stub.
[2.6.5] — 2026-09-12
Added
- AXIS CLI cloud-storage bootstrap (
@hoox-sh/axis-cli0.3.1):axis setup --prod/axis setup kvcreate and bindAPI_KEYSKV;axis keys create/validatemint and checkpn_…keys;axis deployapplies the remote D1 schema first;axis health --scriptsandaxis doctor --remoteprobe/api/scriptsandfeatures.keys. Typical prod path is nowsetup --prod→secret put ADMIN_TOKEN→deploy all→keys create.
Changed
- CLI
run()now times out Wrangler (wrangler whoamiin doctor) with SIGTERM then SIGKILL soaxis doctor --jsoncannot hang.
[2.6.4] — 2026-09-12
Added
- Script version history on every storage engine: local (IndexedDB/
localStoragesnapshots, FIFO 50) and cloud Worker (script_versions+GET /api/scripts/:id/versions) now implement the samelistVersions/readAtRevisioncontract as git. The library history panel is no longer git-only. - Worker URL + API key in Settings: Script storage credentials for the cloud Worker (
endpoint+pn_…key, generate demo key, test connection) live in Settings → General / Studio Settings as well as Script Library. Switching to Cloud no longer requires hunting the library panel first. - Workers inventory skeleton: while backends are probing, the Workers page shows pulse placeholder cards (and an inspector skeleton) instead of an empty grid.
Fixed
- Cloud script storage used the Pine engine URL:
resolveCloudConfigno longer falls back tostore.endpoint(:5002//api/run). Worker base defaults to wrangler:8787on localhost or the production workers.dev host. - Storage engine switch deleted the source library: copy between engines never calls
removeon the source. Dialog primary action is Copy scripts; Switch without copying leaves both libraries as-is. - Minimap height restored: short files no longer stretch the overview canvas to the full editor column — 2px per line, compressed only when the document exceeds the host.
- Update reload asks first: Update now / Hard reload (and
hardReloaditself) open a confirm prompt with current → new version before the app reloads. - Studio always opens Settings: the topbar / palette Studio button lands on Settings every time (no longer restores the last rail page). Settings is the first Workspace rail item. The Studio click handler no longer falls through
??into Runtime then Workers because those callbacks returnvoid. - Workers is a full-viewport studio page: same overlay as Settings/Runtime/Wire/Plugins (not a half-width right sheet). Escape closes it.
Changed
- Storage-change dialog copy: “Copy scripts” / “Switch without copying” instead of migrate-and-delete / start fresh.
[2.6.3] — 2026-09-12
Fixed
- Docker image build failed on
sync:versions:bun run buildstamps Dockerfile/compose/bake VERSION fallbacks, but the image context did not copy those files →ENOENT /app/Dockerfileduring GHCR bake. Missing stamp files are skipped, and the three files are copied into the build stage.
[2.6.2] — 2026-09-12
Added
- Editor minimap (interactive): a scaled canvas overview docked to the editor right edge — one row per line, accent viewport box, cursor line. Click/drag scrolls the editor; auto-hides in narrow containers. Toggle in the editor overflow menu (View → Minimap, persisted
editorMinimapEnabled, default on). - Foldable code sections: indent-based folding for Pine (StreamLanguage has no syntax tree) with a void-styled fold gutter plus standard fold keybindings.
- Indent grid: faint
·dots for leading whitespace plus one vertical guide per completed 4-space indent level, width-preserving and viewport-scoped. - Per-tab version/type badges + Save: each editor tab shows
vN(from//@version=,–fallback) andSTR/IND/LIB(from the declaration call,–fallback) plus a Save button that persists that tab via the existing library/draft path. - Workers stays a right-side non-modal sheet: the Workers overlay remains a right-aligned sheet with a left scrim that click-throughs to the chart/editor (modality unchanged: no focus trap, no Escape-steal) — not a full-width page like Runtime/Wire/Settings/Plugins.
- Language Feature Bar above the editor status strip: a chip row (Hover · Sig · Complete · Lint · Marks · Chips · Remote) where each chip is colored only while the feature is actually firing (open card / list / hint, running check, rendered marks) — dimmed while switched off — so the bar shows which features the current script uses and which could be disabled. Click toggles the master switch, long-press (~550ms) or right-click opens a popover with the group's full settings. The row itself is a persisted “Language Feature Bar” option (Settings → Editor intelligence → View, default on;
editorFeatureBarEnabledin the store). - Standalone CLI binaries (bun compile):
bun run build:binaries(packages/cli/scripts/build-binaries.ts) cross-compiles the CLI into single-file executables for 7 targets — linux x64/arm64 (glibc + musl), macOS x64/arm64, Windows x64 — with a native--version/--helpsmoke test. Own package metadata is now embedded at bundle/compile time (src/own-package.tsstatic JSON import) and preflight recognizes a newbinaryinstall context ($bunfs→ "standalone binary (bun compile)"), so version display, engine checks, and drift detection work without a package.json on disk. Binaries ship asaxis-cli-<version>-bun-<target>release assets and are covered bySHA256SUMS(bare asset filenames — a flat download verifies with onesha256sum -c). VERSIONsingle source of truth: the repo-rootVERSIONfile now owns the release number —bun run sync:versions(runs withbun run build) stampspackage.json,src-tauri/tauri.conf.json,src-tauri/Cargo.toml,src-tauri/Cargo.lock,public/version.json(served at/version.json), and the generatedsrc/version.ts(APP_VERSION, also shown in About) +worker/src/version.ts(WORKER_VERSION, surfaced on/+/health).bun run check:versionsasserts the whole set; the Makefile readsVERSIONdirectly.- Update manager + notification: the app polls the deployed
/version.json(5 min interval + focus/visibility/online, cache-busted) and a waiting service worker routes through the same state — a new deploy shows anaxis-update-banner(Update now / Hard reload / Later), announces via screen-reader live region + status line, and fires a Browser Notification when permission was already granted (never prompts). Hard reload unregisters workers, deletesaxis-*caches, and navigates cache-busted. Covered bytests/update-manager.test.ts+tests/version.test.ts. - Window-close confirmation:
src/pwa/close-guard.tsinstalls onebeforeunloadlistener per page and prompts only while a registered predicate reports unsaved work — the tabbed editor registers dirty-tab state (both chart and popout shells). Master switch + custom message APIs included; covered bytests/close-guard.test.ts.
Fixed
- Pyodide engine crashed on bars containing null (
eval_codetraceback + empty drawings): the browser bridge interpolated rawJSON.stringify(bars)into Python source, so JSONnull/true/falsebecame PythonNameErrors before Pine ever ran — surfacing only as a truncated_pyodide/_base.pytraceback withdrawings: [].src/engines/catalog.ts,src/engines/index.js, andworker/src/pyodide_runtime.tsnow pass JSON strings viaglobals+json.loads, returndrawings: []on bridge failures, and surface the full Python traceback (newcallPyodideRunScript/formatPyodideBridgeError, covered bytests/pyodide-bridge.test.ts).pyodide/pynescript_runtime.pyre-synced with the deployedpublic/pyodidecopy (drawing-registry reset + compile-path GC). - Pyodide bridge errors kept their head, not the exception tail:
formatPyodideBridgeErrorcut long tracebacks withfull.slice(0, 6000), dropping the finalNameError: …line that users need; it now trims the middle (keeps theeval_codehead + the exception tail) insrc/engines/catalog.ts, the legacysrc/engines/index.js, andworker/src/pyodide_runtime.ts— which previously surfaced onlyerr.message(Pyodide's…-truncated form). Covered by a newtests/pyodide-bridge.test.tscase. - Language Feature Bar nits: clicking a chip whose settings menu is open now dismisses the popover instead of flipping the group's master switch (long-press opens the menu; the natural dismissal — a short press on the same chip — accidentally toggled the feature off). Number settings (Hover delay, Max options, …) now draft raw keystrokes and clamp/commit on blur or Enter, so values below a field's
mincan be typed instead of snapping mid-entry. Clicking a different chip while a popover is open switches the popover without toggling. - Test DOM stub Symbol-safety:
tests/setup.tsdatasetproxy now passes through non-string keys (e.g.Symbol.toPrimitive) instead of throwing onconsole.log/inspect, and the traceback-trim budgets are named constants kept in sync acrosssrc/engines/catalog.ts,src/engines/index.js, andworker/src/pyodide_runtime.ts. - Docker builds were broken by the git-hooks
preparescript: rootpackage.jsongained"prepare": "git config core.hooksPath .githooks"(commitd5f0cf89), which runs on everybun install— including inside the imagedepsstage, where git is not installed → exit 127 on every bake since then (v2.6.1 GHCR images never published). The Dockerfile now installs with--ignore-scripts(no dep needs lifecycle scripts; native binaries ship as platform optionalDeps) — verified with a localbuildtarget bake (dist/.version= 2.6.1). Stale version fallbacks (2.3.0/2.3.1) refreshed to2.6.1acrossDockerfile,docker-bake.hcl, anddocker-compose.yml. - Service worker no longer cache-first
/version.jsonprobes: cache-busted/version.json?t=*polls were classified as same-origin static and stuffed unique entries into the 96-entry runtime cache, evicting hashed chunks and/pyodide/*. Those probes now bypass the SW. Cache names bumpedv6→v7so poisoned runtime caches are dropped on activate. - Close-guard disabled before update hard/soft reload: the unsaved-work
beforeunloadprompt blocked Update now / Hard reload; the update path now disables the guard immediately before navigating. - Drawings e2e counts user-drawing SVG only: the chart overlay SVG count no longer includes chrome/grid paths, so the trend-line persistence assertion is not a false pass.
openStudiono longer toggle-closes a slow overlay: opening Studio while the sheet is still animating in no longer treats a second click as a close.- CLI binary install URL documents app tag vs CLI version: GitHub Release tags use the app
VERSION(v2.6.2); standalone binaries are named with the CLI package version (axis-cli-0.3.0-bun-linux-x64). The CLI README curl snippet now uses both current values so the documented URL is not a 404. - Docker build stamps
VITE_APP_VERSIONfrom theVERSIONfile: compose/bake ARG fallbacks are no longer the Vite stamp, sodocker compose up --buildwithoutVERSION=matches/version.json.DockerfileARG VERSION=, compose${VERSION:-}, and bakevariable "VERSION"defaults joinsync:versions/check:versions. - Feature bar long-press/contextmenu no longer race-dismiss; dialog focus: the chip popover no longer closes on the same pointer that opened it, and the settings dialog receives focus.
- Minimap pointercancel + full-height hit target:
pointercancelends a drag the same waypointerupdoes, and the hit area spans the editor height so clicks near the gutter register. - Workers non-modal sheet restored: Workers is a right-aligned sheet again (not full-width), with a left scrim that click-throughs to the chart/editor.
IndentWidget.eqso guides don't rebuild on every scroll: indent-grid widgets compare equal across viewport updates, so CodeMirror does not reconstruct guides on each scroll.
Changed
- Coverage push toward 80/85%: ~260 new unit tests across gateway, signed-fetch, pine-logs, watchlist tickers/live, symbol catalog, bars-cache, streams catalog, workers probe, git-oauth, dataset sinks/expand, data-source-manager, credentials, and drawing coords (
createCoordContextwith stub scales — coords 36% → ~100%) — scoped core gate up 82.8% → ~90% (passes the 80% and 85% floors), app lines 80.1% → 82.6%. Fixed 5 wrong expectations in the generated data-coverage tests (walk-loopexpandedsemantics, same-bucket merge conflicts, sink-merge unions) and 2 cross-suite leaks (probe teardown deleting the sharedwindowstub, a real-timer device-flow poll). Overall lcov sits at ~79.3% — the remainder is browser/chart/worker-runtime code (drawing layer, pane manager, pyne-lsp, durable session) that needs E2E rather than unit tests. - Drawings e2e + chart DOM harness: new
e2e/charts.tsharness (stubbed network, mock-bar boot, toolbar tool selection, chart clicks, SVG shape counting) ande2e/drawings.spec.ts— draws a trend line via the real toolbar, asserts the SVG overlay renders, reloads to prove storage persistence, and switches tools. Covers the browser-bound drawing layer that unit tests cannot reach (no layout engine/canvas in bun). - GitHub Release now carries CLI + worker artifacts: a new
release-assetsjob in.github/workflows/release.ymlattaches, for everyv*tag, the offline-install CLI tarball (axis-cli-<cli-version>.tgzvianpm pack— verified with an offlinenpm i -g+axis --versionsmoke), a deployable worker source snapshot (axis-worker-<tag-version>.tar.gz—LICENSE+worker/minusnode_modules/.wrangler/.dev.vars, sanity-checked forwrangler.toml.example+src/index.ts), andSHA256SUMS-<tag-version>.txt. The job waits for the desktop workflow to create the Release (creates it itself if desktop never does) and uploads with--clobber; existing tags can be backfilled viagh workflow run release.yml --ref main -f tag=vX.Y.Z. - CLI binary smoke test runs on host-native targets:
packages/cli/scripts/build-binaries.tsno longer assumes a linux-x64 host — it smoke-tests whichever wanted target matches the running platform/arch (e.g.bun-darwin-arm64) and skips cleanly when none does, sobun run build:binariescross-compiling on macOS/ARM64 dev machines no longer fails on the smoke step. Classic CI (ubuntu-x64) smoke is unchanged. - Pyodide runtime surfaces drawing-GC failures instead of silently no-opping:
pyodide/pynescript_runtime.py(synced topublic/pyodide) now warns once on stderr whenDrawingRegistryexport/limits or the compile-pathgc_exported_drawingscall fails (e.g. a pyne wheel API change/move) rather than swallowing the error whilemetastill advertises the caps.max_*_countdeclaration parsing now strips//and/* */comments so a cap mentioned in a comment isn't captured, and the redundant"mode"entry is dropped fromcompile_meta.
[2.6.1] — 2026-09-08
Fixed
- Desktop bundles carried the wrong version: the v2.6.0 GitHub Release shipped desktop assets named
2.4.1becausesrc-tauri/tauri.conf.json,src-tauri/Cargo.toml, andsrc-tauri/Cargo.lockwere never bumped past v2.4.1 —tauri-actionderives bundle versions from the Tauri config, not the git tag. All four version stamps are now2.6.1, and a newbun run check:versionsguard (scripts/check-versions.mjs, enforced in CI + the desktop workflow) fails on any drift.
Changed
- Script Logs moved into the editor: the topbar
Script Logsbutton and the shell-levelScriptLogsPanel(src/ui/ScriptLogsPanel.tsx, deleted) are gone — logs now live as an editor-localScriptLogsPane(src/editor/ScriptLogsPane.tsx) above the editor statusbar, toggled by aLogsbutton besideSymbols(axis-editor-logs-toggle+axis-editor-logs-badge) and via the palette (axis-editor-show-logsevent). Statusbar clusters now wrap as units (flex-wrap, actionsflex-shrink-0) so buttons stay clickable in narrow docks instead of squeezing under each other. Guards: Playwright smokeScript Logs lives in the editor bottom bar, not the topbar. - Default timeframe is now 15m: the chart-slot fallback (
src/chart/layout.ts) and workspace-snapshot build/parse fallbacks (src/storage/workspace-snapshot.ts) changed from1dto15m, matching the store default — new chart slots and restored snapshots without an interval start on the 15-minute TF. - Studio opens on Settings:
lastStudioPageinitial insrc/app.tsxchangedruntime→settings— the first Studio open (topbar button / palette) lands on Settings (subsequent opens still remember the last page). - Workers page is non-modal:
AppPage(src/ui/studio/AppPage.tsx) gains amodalprop;StudioHostrenders Workers non-modally — transparent click-through scrim (new.ax-page-backdrop--nonmodalsheet instudio.css), no focus trap, no Escape-steal — so the chart and editor stay interactive while Workers is open. Modality follows the active rail page; other pages are unchanged. - Workers top status grid removed: the aggregate Healthy / Health / Active backend summary deleted from
src/ui/workers/WorkersPage.tsx— status now exists only per worker (inventory cards + detail pane + footer probe line). Guards: Playwright smokeWorkers page is non-modal(scrim pointer-events, no top status, Escape does not close).
Added
- Versioned git hooks (
.githooks/): pre-commit runs Biome (check) on staged files only (fast); pre-push runsbun run typecheck(tsc) +bun run lint(errors fail, warnings pass). Auto-configured via the rootpreparescript (git config core.hooksPath .githooks) on everybun install; bypass with--no-verify. - CLI installation self-check (
@hoox-sh/axis-cli0.3.0): everyaxiscommand self-verifies on start (packages/cli/src/utils/preflight.ts) — engine compliance (Node ≥ 20 / Bun ≥ 1.2), install context (npm install / npx cache / repo checkout), and version drift when a globally installed CLI is older than the repo checkout it operates on (warnsnpm i -g @hoox-sh/axis-cli). Warnings-only on stderr: skipped for--version/--help, silenced by--quiet/--json, never blocks or fails a command.axis doctorgains the optional CLI installation row (cli-installin--json). Tests:packages/cli/tests/preflight.test.ts(context detection, engines, drift, doctor row).
Changed
- Docs are CLI-first: all
bun run axis:*invocations acrossREADME.md,AGENTS.md,docs/**(cloudflare, local-dev, index, topologies, vps-demo),worker/README.md, andpackages/cli/README.mdreplaced with the primaryaxis <command>form;bun run axis:*is now documented as a repo-only alias (alias tables flipped indocs/devops/cli.mdxanddocs/enduser/getting-started/installation.mdx). New Installation self-check section in the CLI docs.
[2.6.0] — 2026-09-08
Added
- Keypress feedback for consumed shortcuts:
dispatchShortcutnow emits anaxis-shortcut-firedCustomEvent (id / chord / description —src/ui/shortcuts/runtime.ts) and a transient bottom-center chip (src/ui/shortcuts/Feedback.tsx,data-testid="axis-shortcut-feedback",aria-live="polite", pointer-events none, ~1.2 s auto-hide, platform-correct chord glyphs viaformatChord) confirms every consumed binding — main shortcut keys no longer feel dead even when the side effect is subtle.
Changed
- Docs synced to v2.5.0:
docs/index.mdx+docs/enduser/getting-started/installation.mdxversion stamps;docs/ui/ui-shell.mdxgains Keyboard shortcuts (dispatch hub, scopes, recorder, Shortcuts modal) and Mobile shell (phones / tablets) sections;docs/ui/index.mdxsubsystem table mentions shortcuts + mobile shell;docs/ui/results-and-strategy.mdxanddocs/enduser/guides/strategy-and-results.mdxdocument the v2.4.3 results overhaul (Events Stream/Open⇄Close position-cycle views, single-column strategy tab, saved-run stats snapshots, sharedwalkStrategyEventswalker).
Fixed
- Keyboard shortcuts were dead:
ShortcutHubwas never mounted (the component had zero importers, so the capture-phase keydown dispatcher insrc/ui/shortcuts/Hub.tsxand the wholesrc/ui/shortcuts/actions.tsregistration side effect never loaded) —Ctrl/Cmd+K/Ctrl/Cmd+Shift+P(command palette),Shift+?(shortcuts help),Ctrl/Cmd+,(settings),Ctrl/Cmd+\(toggle editor),Esc, and everychart.*binding (zoom / pan / tool letters / grid layouts) silently no-oped. The product shell (src/app.tsx) now mounts<ShortcutHub />(plus<ShortcutFeedback />). Only the parallelMod-S/Mod-Ghandler insrc/editor/tabbed-editor.tsxsurvived, which is why save worked while the palette was unreachable by keyboard. Guard tests:tests/shortcut-feedback.test.tsand the Playwright smokeMod-K opens and closes the command palette (shortcut hub guard)ine2e/smoke.spec.ts.
[2.5.0] — 2026-09-07
Added
- Mobile-first responsive shell (phones ≤767px, tablets 768–1023px; desktop ≥1024px unchanged):
- Viewport signal (
src/ui/responsive.ts): reactivephone | tablet | desktopmode +pointer: coarsedetection — all JS layout branching keys off this instead of CSS-only hacks, because panel geometry is persisted px state. - App-style phone shell: compact header (brand · venue picker · symbol picker via SymbolModal · interval select) + bottom tab bar (Chart · Panels · Editor · Studio · More) replace the desktop Topbar/StatusBar; Panels list and More drawer (Run, Live, Studio pages, Settings, theme, About) open as nav overlays.
- Panel sheets (
FloatableShellmobile mode): docked/floating panels render as full-viewport sheets under the header / above the tab bar — one active sheet at a time (src/ui/panels/mobile-sheet.ts); drag-to-undock, resize handles, and hover-slide are disabled on phones. Sheets dismiss via swipe-down on the title bar (grabber affordance + live translate feedback) or the close button. Desktop geometry is bypassed, never overwritten, so phone↔desktop switches preserve the user's layout. Phones boot chart-first (open panels don't steal the viewport). - Chart force-single on phones: 2H/2V/4 layouts collapse to the active slot with a slot-switcher chip row; other slots stay preserved in
store.chartLayout. - Tablet: desktop shell kept, topbar becomes horizontally scrollable.
- Dialogs → bottom sheets on phones (
sc-dialog), safe-area insets (viewport-fit=cover,env(safe-area-inset-*)),100dvhapp root,interactive-widget=resizes-contentfor soft-keyboard handling, ≥44px touch targets in mobile chrome (pointer: coarse). - Studio tables wrap cells on phones instead of forcing horizontal overflow.
- Viewport signal (
- PYNE runtime 0.5.0: vendored
pynescript-0.5.0-py3-none-any.whl(built from the sister pyne repo viascripts/sync-pyne-wheel.sh); stale 0.4.x wheels dropped; hard-coded wheel paths insrc/engines/catalog.ts/src/engines/index.jsupdated (hoox_pyne-0.4.2/pynescript-0.4.0→pynescript-0.5.0). Pine language builtins unchanged.
Changed
- Zero Biome lint errors (368 → 0) across 641 checked files:
- Tooling: Biome 2.5 (
biome.json, lint-only — Prettier keeps formatting),lint/lint:fixscripts, Prettier 3.9 pinned (.prettierrcsingle-quote/width-100,.prettierignore), projectopencode.jsonenabling LSP + formatter. - Real fixes: regex
exec-loops →matchAll()(pine-colors, preevaluate, pyne-lsp, script-inputs, library-publish, …),type="button"on ~18 buttons, keyboard handlers (Enter/Escape) +tabIndex/ARIA value semantics on interactive widgets,role="region"divs →<section>, global-shadowing renames (escape→escapeHtml,valueOf→readFieldValue), duplicate object keys removed,const enum→enum(isolatedModules safety). - Justified suppressions (each with reason): intentional control-char sanitization regexes (injection safety), CSS
env(safe-area-inset)fallbacks, backdrop click-away surfaces, custom-widget rows with nested buttons, sticky-regex tokenizer. - Verified regression-free:
tsc --noEmitclean; test suite identical to pre-change baseline (pre-existingserver.test.tshook timeout +window.matchMediacross-file pollution unaffected).
- Tooling: Biome 2.5 (
[2.4.3] — 2026-09-07
Added
- Results panel — events views & richer reports (spec:
.opencode/specs/2026-09-07-results-views-design.md):- Events tab view switcher:
Stream(classic chronological list, now enriched with entry fill count / running position qty+avg,from_entry, remaining qty after closes, and per-fill P&L on close/exit rows) and Open ⇄ Close — a two-column grid with one row per position: entries on the left (fill list, pyramiding averages into the position, total qty @ avg price), exits on the right (each close fill with P&L, partial-close remainder, Open/Closed badge). Re-opens after a full close form new cycles (id #2);close_allflattens every open cycle; rows click-jump to the fill on the chart. - Strategy tab — single column: fill options → stats cards (12: trades, win rate, W/L, PF, net profit, Return % (compounded per-trade), max DD, avg trade/win/loss, gross profit/loss) → equity chart → closed-trades table below it, with new Bars (bars held) and Fills (pyramided entry count) columns.
- Saved runs tab — rich stats:
buildResultMetanow persists a JSON-safe strategy stats snapshot (StrategyStatsSnapshot) computed from the run's events (profitFactor: null= ∞); saved rows render a stats grid (trades, W/L, win %, PF, net P&L, return %, max DD, avg trade); legacy saved rows fall back gracefully. - Pyramiding parity: entry fills into a live position now average into the trade P&L (
entry = Σ(p·q)/Σq) matching the pyne broker(px − avg) × close_qty; per-position cycles and the enriched stream are computed by a single shared walker (walkStrategyEventsinsrc/results/strategy.ts, re-exported viasrc/results/positions.ts).
- Events tab view switcher:
[2.4.2] — 2026-09-07
Added
- Global keyboard shortcuts: app-level chord dispatch hub (
src/ui/shortcuts/) with capture-phase handling, dialog-skip guard, per-user persisted overrides on the app store, and a live shortcut registry — wired to the command palette, editor, and chart canvas.- Recorder: Settings → Keyboard → record chord, plus a Shortcuts modal listing default bindings with live rendering.
- Editor line ops (
src/editor/cm-line-ops.ts):Prec.highkeymap extensions (duplicate line, toggle comment, indent/unindent, delete line) for the Pine editor; palette extensions render recorded chords. - CLI npm release pipeline (
.github/workflows/release.yml): full publish flow for@hoox-sh/axis-clivia org secretNPM_TOKEN_HOOXSH—- tag/version guard:
cli-v*tags must equalcli-v<packages/cli version>(hard fail on mismatch); rootv*tags publish the CLI when its version is missing from the registry and skip cleanly otherwise;workflow_dispatchgains aforcere-attempt input. - post-publish registry verification (
npm viewwith propagation retries). - Node-only smoke test: fetches the published tarball, installs it into a clean directory with
--ignore-scripts, and assertsaxis --version/axis --helpon Node (no Bun, no repo). - step summary now reports tag kind, registry verify, and smoke results.
- tag/version guard:
@hoox-sh/axis-cli0.2.2 — first CLI-only release via the new pipeline:cli-v0.2.2tag →Release (npm)workflow (provenance publish, registry verification, Node-only smoke test of the published tarball). Code identical to 0.2.1; release exercises the full publish path end-to-end.
Fixed
- UI review findings (
/.opencode/review/) — 23 of the 25 consolidated findings (editor-audit + pass1/2/3) fixed across the editor, drawing chrome, panels, and themes:- Escape friction cluster (
AXIS-ED-ESC-OVERLAY,AXIS-P1-ESC-CANCEL-DRAFT,AXIS-UI-DRAW-MEASURE-ESC,AXIS-UI-KB-ESC-RECORD): new CodeMirror Escape command dismisses suggest / hover / signature overlays in order; drawing drafts (measure, …) now always cancel on Escape (capture-phase handler immune to other preventDefault consumers); the Keyboard shortcut recorder records Escape as a chord withstopImmediatePropagation+ adata-axis-recording-chordguard so the Studio page no longer closes mid-recording; conflict flags are scope-aware (app.escapevschart.cancel-draftlayer by design, no false conflict). - Drawing flyout clipping (
AXIS-P1-DRAW-MENU-CLIP,AXIS-UI-DRAW-MENU-CLIP): Line / Fib / Shapes / Annot / Measure flyouts and the settings popover clamp to the nearest clipping ancestor — tall menus (13-item Shapes) scroll instead of spilling past the viewport, and flip upward when there is no room below. - Callout / text prompts (
AXIS-UI-DRAW-CALLOUT,AXIS-UI-DRAW-TEXT-FOCUS): all blockingwindow.promptcalls (callout, note, anchored text, text tool, double-click re-edit) replaced with a non-blocking in-app dialog (src/chart/drawings/tools/text-prompt.ts) that autofocuses the input, commits on Enter, cancels on Escape — also fixes Tauri webviews wherewindow.promptis unsupported. - Bar replay dismiss (
AXIS-P1-REPLAY-DBL-CLOSE,AXIS-UI-REPLAY-DBLCLICK): topbar toggle trusts both the subscribed UI state and the module session (a missed emission can no longer turn an exit click into a start), and Escape exits replay. - Results staleness (
AXIS-ED-RESULTS-STALE): the store tracks the newest run (newestRunId); the script selector shows a "new run" marker when focus lags the footer. - Status / System Logs clash (
AXIS-UI-STATUS-LOGS-CLASH): the System Logs expanded body is clamped to the viewport (reserving topbar + workspace + status strip), so an oversized persisted height can no longer push the Status strip off-screen. - Diagnostic cascade (
AXIS-ED-DIAG-CASCADE): the pre-eval balance scanner no longer carries an unterminated string across lines (one bad quote suppressed every later structural diagnostic), and remote parse errors only shadow local errors on the same line — semantic errors after a parse failure now surface. - A11y live region (
AXIS-ED-A11Y-LIVE): diagnostics changes announce politely via the shared SR live region; the Problems count is arole="status"live element. - Long-line clip (
AXIS-ED-LINE-CLIP):overflow-wrap: anywhereon editor content so wrap mode breaks long tokens instead of clipping. - Contextual autocomplete (
AXIS-ED-AC-CONTEXT): completions rank by prefix quality, cursor position (statement start boosts keywords, value position boosts series / user locals) and in-buffer local bindings. input.inthover (AXIS-ED-HOVER-INPUTINT): exact catalog member beats the last-segment type fact, so hovers show signature + params instead of namespace docs.- Contrast (
AXIS-ED-LIGHT-CONTRAST,AXIS-UI-THEME-GRAPHITE-CONTRAST): theme bridge enforces contrast floors for--color-text-dim/--color-text-faint(WCAG-ish 4:1 / 3.2:1) with correct mix direction per base; static light-theme faint token darkened (#8b8e9c → #666a78). - Focus + labels (
AXIS-ED-FOCUS,AXIS-ED-ICON-LABELS): visible focus rings on editor tool / status / tabbar buttons and the CM surface; editor tab strip uses propertablist/tabsemantics with a real (non-nested) close button; aria-labels on icon-only git bar, topbar panel toggles, and Problems copy button. - Eraser on locked drawings (
AXIS-UI-DRAW-ERASER-LOCK): locked erase / delete attempts now show a transient toast + assertive SR notice instead of a silent no-op.
- Escape friction cluster (
- Architecture apply guard:
applyArchitectureonly preloads Pyodide when a real DOM append target exists. The unit-test document stub now exposesquerySelector, which previously flipped the browser-detection gate and madeprefetchPyodideAssetsthrow ondocument.head.appendChildunderapplyArchitecturetests (commits Offline Lab,clears the stream slot).
[2.4.1] — 2026-09-06
Fixed
- Worker typecheck: clean
tsc --noEmitunder the strictworker/tsconfig.json(noUncheckedIndexedAccess,exactOptionalPropertyTypes).auth.extractBearer: regex capture non-null assertion — match guarantees the capture.scripts.rowToMeta/listD1: buildScriptMetawith optionaldescription/pathonly when set (no implicitundefined).scripts.handleScriptsdraft put: includenameonly when defined;parts[0]non-null after the_draft/ empty-path guards.
No runtime behavior change; only type compliance so worker tsc matches the pre-push gate.
[2.4.0] — 2026-09-06
Added
- DSM-first data plane: AXIS now loads datasets first — the chart paints instantly from the DatasetStore (repaired), then a background DSM job auto-completes the series (validate → classify gaps → sliced walk-back backfill) with progressive repaints as slices land. Venue REST remains the fallback/seed path; the Reload button forces a venue refetch (
preferDataset: false). - DatasetStore (
src/data/dataset-store.ts): single source of truth for OHLCV datasets with pluggable persistence sinks —session(memory only),local(IndexedDB, default),git/worker(storage-plugin documents, debounced + retried, never blocking the chart). All consumers (chart loads, DSM jobs, live ticks, compare, CSV upload) read/write through it. - Dataset validation & repair (
src/data/dataset-validate.ts):repairBarsdrops corrupt rows, dedupes, sorts, and snaps individually off-phase bars to the series' dominant open-time phase (≥3 bars, ≥60% agreement — consistent venue offsets are preserved).classifyGapssplits gaps into fillable vs legitimate (weekend closures for session venues, declared maintenance windows) so the DSM stops chasing phantom gaps.validateDatasetcomposes both into a report. - Dataset merge with conflict detection (
src/data/merge-datasets.ts): overlapping timestamps whose OHLCV disagree beyond a relative tolerance are conflicts, auto-resolved by policy (newest-winsdefault,keep-current,prefer-incoming); non-overlapping bars always union. - Settings → Data → Dataset persistence main switch:
Session | Local | Git | Worker(default Local), persisted asstore.datasetPersistenceand synced to the DatasetStore on boot/change. - Sliced-backfill progress chip in the topbar Data group: live
loaded / expectedbar count while a DSM job completes the current dataset in the background.
Fixed
- DatasetStore reads are memory-first and never wipe the mirror on a sink miss; switching persistence copies in-memory series into the new sink.
- Git/Worker sinks return pending bars from
get(no empty merge during DSM walk-back), serialize per-key flushes so a retry cannot clobber a newer put, and write-through to IndexedDB as a durable front.onSinkErrorlands in system logs. - Progressive paint no longer goes through
loadBars(that re-fitted the viewport every slice).paintDatasetis generation-guarded so a slower prior load cannot paint over a newer symbol. - DSM jobs repair +
validateDataset/ fillable gaps (not rawvalidateBarCoverage);ensureDatasetCompleteuses the source calendar class. In-flight jobs for the same key are reused; the progress chip counts bars in the target window.
Changed
- Topbar order: the Venue picker moved to the front of the Market group (Venue → Symbol → Interval → Type → Compare) since it determines valid symbols/intervals; the Data group keeps plugin config, upload, datasets, Load, and Reload.
- DSM job engine reads and writes now route through the DatasetStore (session-mode safe, progressive repaints fire on every merged slice);
expand-cache(cache→now + live ticks) and the Data Manager source do the same, with a repair pass on Data Manager resolves and CSV uploads. - Reload shows the same loading spinner /
loadSeqas Load.
Tests
tests/dataset-validate.test.ts— repair (corrupt rows, phase-aware snap, dedupe, sort), gap classification (24/7 vs session venues, maintenance windows), dataset reports.tests/merge-datasets.test.ts— conflict detection tolerance, merge policies, union/sort behavior.tests/dataset-store.test.ts— sink routing (session vs local), conflict-resolved merges, replace/remove, change subscription, persistence-mode switch.tests/dataset-sinks.test.ts— remotegetreturns pending bars before flush.tests/integration/load-symbol.test.ts— DSM-first cache paint without hitting the venue.
[2.3.1] — 2026-09-05
Fixed
- Doctor: missing gitignored
worker/wrangler.tomlis a warning untilaxis setup, not a hard fail. - Live badge: show Reconnecting… / Offline while the websocket is down; one reconnect warning per disconnect burst.
- Static PWA server:
GET /healthisok(not SPA HTML); OPTIONS 204; favicon served. - Versions: product identity is 2.3.1 everywhere (About, Tauri, docs).
- Desk / PYNE path: sibling repo is
../pyne(fallback../pynescript).
Added
- Modest Install app chip on
beforeinstallprompt(no nag). - Quick Start defines SRC / STR / ENG / STO in plain language.
Changed
- Default editor width is 30% of the viewport (was 50%) on first load and layout reset.
- Default chart interval is
15m(was1d). - Timeframes: topbar / settings / DSM / watchlist interval list now includes
3m,30m,2h,6h,8h,12h,3d, and1Malongside the previous set. - Topbar Inputs: removed the Inputs button and its Studio/Settings visibility toggle. Script inputs still open from the Scripts list, command palette, and the existing inputs modal.
[2.3.0] — 2026-09-05
Fixed
- CI unit tests: plugin bootstrap now re-registers after
registry.clear()(catalog flags reset together). Store/runner tests resetresultsFocusIdsosetLastRunis not skipped. Data-manager selection is cleared in stream catalog tests. CI pins Bun1.3.14(latestwas racing global store/registry).bun test --max-concurrency=1for deterministic unit runs. Worker origin URL helpers live insrc/data/worker-origin.tssostore→ HTTP client → market-worker → on-chain proxy no longer hits a TDZ on import.
Changed
- Repo description & tags: GitHub / package / PWA / desktop copy no longer frames AXIS as “for Pine Script™”. Tagline covers CEX OHLCV, drawings, on-chain overlays, and Pine Script™ via PYNE; extra GitHub topics and package keywords.
Added
- Per-tool drawing settings: style bar grows context chips (extend L/R, show price, lock, custom color) plus a gear popover for fib levels, text/font size, risk/reward, arrow caps, and stats toggles. Last-used extras persist per kind in
drawingPrefs.byKind. Paint honors extend flags, custom fib ratios (including reverse), highlighter width, long/short RR, and double-click text re-edit.
Tests
- Tool settings catalog —
tests/tool-settings.test.ts: every drawing kind has controls; ray/highlighter/long defaults; fib + RR helpers; normalize keepsmeta.fibLevels. - Store
byKindmerge —setDrawingPrefsdeep-merges per tool without wiping siblings.
[2.2.0] — 2026-09-04
Added
- Results → Raw JSON tree: collapsible VOID-colored viewer (keys / strings / numbers / bools) with key filter, Expand / Collapse, and Tree | Source. Large
plots/seriesarrays page in (32 at a time) so a full run does not mount thousands of nodes. Click a primitive to copy it. Footer Copy / JSON still export the full payload.
Changed
- Studio fullscreen canvas: Runtime, Wire, Settings, Workers, and Plugins (plus nested tabs) now use the full overlay width instead of a reading-column cap. Wide layouts are two columns — Runtime (engine | endpoint), Settings General / Data / Topbar / Editor / Theme token grids, Workers inventory | inspector, Plugins catalog cards, Results strategy table. Legacy
sc-settings-*chrome on Editor intelligence, exchange keys, and stacked plugin fields is replaced with studioax-*primitives. - Theme color field: Settings → Theme uses one
StudioColorInputper token (swatch picker + text). Accepts hex,rgb()/rgba(), CSS names, and Pinecolor.*enums; the picker rewrites in the same form. - Editor color chips: clicking an inline script color chip opens a native picker and replaces the source in the original form (
#hex,color.red,color.rgb,color.new), keeping transparency. - Studio scroll: nested panes, tables, code blocks, and library lists no longer scroll on their own. Only the modal canvas (
.ax-page-canvas) scrolls. - Studio primitives: status pills stay compact (no longer stretch full-width inside cards); Idle/Skipped/Degraded get the same pill chrome as Healthy/Down; cards, stats, inline buttons, and feature rows tightened. Same language applied across Runtime, Wire, Settings (all tabs), Plugins (catalog / install / library), Workers, and Results: capability badges are
ax-cap, catalog rows are entity lists, chips-as-tags stay small, and compactsc-*chrome hosted in the overlay is scaled up to studio density. - Results overlay: true fullscreen studio shell (
.ax-page-backdrop+.ax-page, no dialog padding/blur). Header holds auto-open / script picker / saved-runs chip; Copy / JSON / CSV sit in the sticky footer. Canvas is the only scroller. Stacks above other studio pages (z-index: 1050) so a finished strategy is not trapped under Wire or Workers. Events, Strategy, Optimise, Plots, Metrics, Raw, and Saved use the sameax-*primitives as the rest of studio.
Fixed
- Strategy Saved-runs loop: live silent re-runs (every-tick / bar-close) were calling
setLastRunwithoutpersistence: 'skip', so localsaveResultwrote a new IndexedDB row about once a second. Durable save is now user-initiated Run only; deferred / skipped / superseded payloads never persist. Fire-and-forget saves are serialized so FIFO trim cannot race. - E2E smoke vs Studio overlay: topbar Architecture / Runtime / Settings / Plugins hooks are
hidden(Playwright no longer treatssr-only1×1 clips as clickable, which hung behind the fullscreen button). Smoke, critical, and library journeys open those pages through the visible Studio button + rail (axis-studio-rail-*). - Editor false typo on
syminfo.mintick: pre-eval “unknown built-in member” only knewsyminfo.prefix/syminfo.tickerfrompyne-builtins.json(the two callables). Host variables (mintick,tickerid,timezone,currency, …) are now allowlisted with the other Pine constants, matching pyne’s implementedsyminfo.*surface. - Market proxy (Worker): MEXC public REST is served through
/api/market/mexc/{klines,ticker/24hr,exchangeInfo}becauseapi.mexc.comdoes not sendAccess-Control-Allow-Origin.fetchMexcJsontries the Worker first and the direct host only as last-ditch fallback (offline lab,skipWorkerProxytests, or Worker 5xx / network). Worker 4xx (allowlist / not found) is thrown immediately so the browser does not pay a CORS-blocked extra hop. SharedresolveMarketWorkerBaselives insrc/data/market-worker.ts(re-exported frombinance-http.ts/mexc-http.ts). Call sites:watchlist-tickers.ts,sources/catalog.ts(mexcRest.fetchHistorical),symbol-catalog.ts(exchangeInfo). Interval allowlist is AXIS TFs plus venue1M(1h→60m,1w→1W; no1s/3m/2h/6h/8h/12h/3d).ticker/24hrallowlists optionalsymbol=(single object, cached per symbol); there is nosymbols=batch — omit the query for the full book. Watchlist uses per-symbol requests when the list is small (≤8), else the full book. No signed MEXC path yet.
Tests
- Run-result persist —
tests/run-results-persist.test.ts:persistence: 'skip'and ephemeral meta do not write Saved runs; interactive Run writes once; three silentliveTickre-runs do not append. - Studio / Results e2e —
e2e/studio.tsopens rail pages without clicking hidden topbar hooks; critical Results close usesaxis-results-closebecause the fullscreen overlay covers the topbar toggle. - Pre-eval
syminfo.*host vars —tests/preevaluate.test.ts:syminfo.mintick/tickerid/timezone/currencyare known;syminfo.minitckstill suggestsmintick. - MEXC client fetch order —
tests/mexc-http.test.ts: Worker first,skipWorkerProxygoes direct, abort does not try the next host, Worker 400 does not callapi.mexc.com, Worker 5xx still falls through. Watchlist small-listsymbol=vs full-book path. - MEXC Worker ticker
symbol=+1M—worker/tests/market-proxy.test.ts: optionalsymbol=forwarded and cached separately from the full book;symbols=rejected; native1Mklines accepted. - Color rewrite helpers —
tests/pine-colors.test.ts:formatPickedChipColorkeeps kind/transparency;rewriteColorKeepingFormatpreserves hex / rgb / rgba / named. - JSON tree helpers —
tests/studio-json.test.ts: kind / preview / stats, expand-all skips huge arrays, filter does not walk 200-point plot series.
[2.1.1] — 2026-08-29
Fixed
- CORS product-host list: retired the legacy
*.pynescript.aibranch fromPRODUCT_ORIGIN_RE(workerpickOrigin) — production URL ispynescript.online, not.ai. The fallback default inenv.ALLOWED_ORIGIN(and the inline fallback when none is configured) now also points athttps://pynescript.online. Legacy.airequests fall through to the allowlist fallback instead of being echoed.isOAuthProxyBase(git-oauth client proxy allowlist) drops the.aicheck for symmetry. Docs (README,docs/devops/cors-and-origins.mdx,docs/worker/bindings.mdx,docs/architecture/evaluation.mdx,docs/llm.txt) updated.
Tests
- CORS regression:
worker/tests/cors-origin.test.tsadds adoes NOT echo the retired pynescript.ai product hostcase that confirmshttps://pynescript.aiandhttps://app.pynescript.airequests fall back to the configuredALLOWED_ORIGINfirst entry (i.e. they're no longer auto-allowed as product hosts).
[2.1.0] — 2026-08-29
Added
- Local storage: persisted run results:
localStoragePluginnow implements the optionalsaveResult/loadResult/listResults/removeResultmethods so completed strategy/indicator runs can survive reloads. IndexedDB schema bumped from v1 → v2 — additive migration only, with a newresultsobject store keyed by compound[scriptId, runId]and abyScriptindex onmeta.scriptIdfor cheap per-script listing. FIFO trim keeps at mostMAX_RESULTS_PER_SCRIPT = 50runs per script (oldest bymeta.startedAtevicted; the just-saved run is always preserved). ALOCAL_STORAGE_VERSION = 2constant is exported for downstream schema detection, and the plugin now advertisescapabilities.results = trueso UI can render saved-runs affordances. localStorage JSON + in-memoryMapfallbacks mirror the same semantics for SSR/tests (pynescript.axis.results.v1key +_getMemResultsForTestshelper). Plugin contract extended insrc/plugins/types.tswith aresults?: booleancapability flag. - Storage change confirmation dialog: new
StorageChangePrompt(src/ui/StorageChangePrompt.tsx) is mounted once at the app root and hosts the existingStorageChangeDialogwhenever the user switches storage engines. A newpromptStorageChange(oldId, newId)helper insrc/storage/service.tsis the single entry point — short-circuits silently when the engine is unchanged or unset (first-time set), and otherwise opens the dialog so the user can pick Migrate scripts or Start fresh before the active plugin flips. The migration itself still runs through the existing dialog (it callsgetStorage(fromId)/getStorage(toId)directly so the active plugin stays pinned to the source until commit). All four storage-change call sites now route through it —ScriptLibraryPaneldropdown,SettingsDialoggeneral tab Save,PluginsPagecatalog activate,PluginManagermodal activate — so there is exactly one shared dialog state, not four duplicated ones. - Per-panel icon in FloatableShell header:
FloatableShellnow renders the panel-specific glyph next to the hamburger menu (testidaxis-panel-header-icon-{panelId}), matching the Topbar panel toggle. Single source of truth isPANEL_ICON: Record<PanelId, IconName>insrc/ui/icon-map.ts; TypeScript exhaustiveness guarantees everyPanelIdhas an entry. Missing mappings fall back to the hamburger-only chrome without breaking the panel.
Fixed
- Status bar chip 1px hover wobble:
ConnectionHudchips (ChipShell,TickPulse,LiveBadge) no longer shift pixels on hover/active. Locked border-width toborder-[1px](Tailwind v4borderno longer relies on theme indirection), addedtransition-noneto prevent any color flicker, and pre-applied thering-1outline onLiveBadgeso only the ring color animates betweenring-transparentandring-accent(no box-shadow structure delta)..sc-btn-ghostgained an explicitbox-sizing: border-box(defensive — Tailwind v4 preflight already sets it on*). - Duplicate "Open in new tab" in editor dock menu: the editor panel's dock menu showed both the generic DOCK_MENU "New tab" entry and an editor-specific "Open in new tab" entry passed via
menuExtra. ThemenuExtrais nowundefined(per theFloatableShellJSX usage inEditorPane), so the dock menu shows the generic "New tab" entry exactly once. The editor's own "Open in new tab" affordance lives only in the right-sideEditorOverflowMenu(testidaxis-editor-btn-new-tab-overflow), where it pops the editor out into a full browser tab instead of a docked window.
Tests
- Storage result round-trip + FIFO + fallback —
tests/storage-local-results.test.tscovers the new IndexedDB v2resultsobject store: add / load / list / remove, FIFO trim atMAX_RESULTS_PER_SCRIPT = 50, thebyScriptindex, SSR fallback (pynescript.axis.results.v1+_getMemResultsForTests), and additive schema migration from v1. - Service façade + plugin opt-in —
tests/storage-service-results.test.tsexercisessaveRunResult/loadRunResult/listRunResults/removeRunResult/supportsRunResultsthrough the active plugin, including the local-only fallback when the active engine (git / cloud) doesn't implement the optional result methods. - Storage change prompt —
tests/storage-change-prompt.test.tsasserts the dialog is mounted exactly once at the app root, opens on engine change, and short-circuits silently when the engine is unchanged. - Editor menu dedupe —
tests/editor-menu.test.tslocks the regression:EditorPaneno longer passes amenuExtrawith a "New tab" entry, the dock menu still contains exactly one "New tab", andEditorOverflowMenustill owns the overflow "Open in new tab". - Panel header icon routing —
tests/panel-icon.test.tsverifiesPANEL_ICONcovers everyPanelId, each value is a validIconName, andFloatableShellreadsPANEL_ICON[props.id]and emits theaxis-panel-header-icon-{panelId}testid.
[2.0.31] — 2026-08-28
Changed
- About modal: removed ethos manifesto list and author section; added AXIS + PYNE version badges and brief no-walled-garden philosophy.
- Studio consistency:
ThemePanel(Settings → Theme) now uses the studioax-*primitives (StudioSection,StudioField,StudioInput,StudioToggle,StudioChip,StudioButton,StudioHint) instead of legacysc-settings-*classes, matching Runtime / Wire / Workers / Plugins. Studio--ax-*tokens promoted to:rootso shared panels render correctly outside the modal. - Theme color input: each token uses a single color input (mono text field + non-interactive preview swatch) instead of a paired swatch picker and text box; number tokens use one number input.
- Studio modal radius: added a scoped
--radius-surfacetoken (8px inside.ax-page, defaulting to 3px elsewhere). Studio modal surfaces —.ax-card,.ax-btn, rail nav items, color preview, equity tooltip, HPO chart wrap, results auto-open toggle, and the Results error box — now use the higher radius. Chart/panel<input>/<select>and small chips/badges keep their existing 2–3px radius for hierarchy.
Added
- Results fullscreen studio modal: replaced the docked Results panel (
FloatableShell) with a focused fullscreen studio overlay (ResultsModal) opened viastore.resultsPanel.open. Six restyled subpages — Events, Strategy, Optimise, Plots, Metrics, Raw — share one studio canvas (ax-page/ax-*) withStudioStat,StudioTabs, andax-list/ax-cardprimitives.StrategyReportandHpoPanelrebuilt on studio classes (stat cards, equity card, ax-field controls, ax-list search space). Results auto-open only forstrategy()scripts (indicators no longer auto-open). - Results auto-open setting: new persisted
resultsAutoOpenflag (defaulttrue) gates the strategy-only auto-open in the runner. A visible toggle sits at the top of the Results modal header ("Auto-open on strategies"); the same control is exposed in Settings → General ("Auto-open results on strategies"). Persisted via localStorage and hydrated on boot. - Strategy equity graph: replaced the flat SVG line with a rich
EquityChart— gradient area fill, zero baseline, gridlines with money-axis labels, underwater drawdown shading, and a hover crosshair + tooltip showing time, equity, and drawdown. - Results subpage polish: Events list now shows colored type badges and long/short direction pills; Plots shows a sparkline card per series (positive/negative color); Optimise shows an in-sample / out-of-sample trial score chart with legend; Metrics shows a status pill plus derived stats (profit factor, max DD, plot points, script type).
- Results full-width + 2-column subpages: the Results modal canvas now uses the full studio width (
ax-page-canvas--wide, no reading-column cap). Optimise uses a two-column split — search configuration + search space on the left, live study chart + trials table on the right. Strategy uses a two-column split — stats + equity curve on the left, closed-trades table on the right (stacks to one column under 64rem). - Topbar settings: new 'Topbar' tab in Settings with toggles for each topbar group (brand, market, data, compute, layout, panels, system) and individual panel buttons (watchlist, editor, library, scripts, inputs, layers, DSM, on-chain, alerts, values, results, script logs, system logs, status). Settings are persisted via localStorage.
- HPO (Optimise) polish: the search config (trials, sampler, objective, validation, holdout/walk-forward sizes, min trades, and per-param bounds) now persists across reloads; the trial chart pins its x-axis to the trial index (IS/OOS lines no longer desync when trials error), marks the best trial, and shows the score scale; the results header gained a status chip, best IS/OOS scores, and a Clear button; categorical params show their choices and the search-space list has an empty state.
- HPO tier gating: added a persisted
tier(free/pro/self-hosted) — the free tier disables walk-forward validation (shown as "(Pro)") with an upgrade hint, while Pro / Self-hosted show a tier badge and unlock it. The engine-runs cap was raised from 400 → 1000 so walk-forward fits within the trial ceiling.
Fixed
- Workers probe non-blocking: initial backend probe deferred past first paint (double
requestAnimationFrame) and a non-blocking "Probing backends…" hint shows while the first snapshot loads — the Workers panel is ready and interactive immediately, regardless of slow/failed probes. - Indicator engine correctness: styled Pine plots (
histogram/area/columns/stepline/baseline/line/cross/circles) were silently dropped becausesplitSeriesByKindonly acceptedplot/hlinekinds — they now route to line plots (missing-kind and unknown-kind both default to line). Enginestatus: 'success'is no longer downgraded to error by a stray non-fatalerror/messagestring. Series↔time-axis alignment is now derived from the exact bars the engine evaluated (passed throughgetOhlcvTimesForApply), fixing misaligned/blank newest bars on live ticks and HPO holdout slices. Explicitoverlay=trueis no longer auto-demoted to a sub-pane by the oscillator-scale heuristic. ThegetOhlcvTimesForApplycache is reused for the commonstore.barscase (the bypass path only applies to a genuinely different axis) and invalidates on astore.barsreference change. - Results panel dock cleanup: removed the dead
resultspanel from the dock registry (PanelId,PANEL_META,PANEL_IDS,DOCK_STACK_ORDER) so the bottom dock no longer reserves ~220px of empty space — Results is now a fullscreen modal driven solely byresultsPanel.open. - Input override no-op on chart: changing a script input (e.g. RSI length 14→56) now repaints the whole series.
PaneManager.syncOverlayLines/syncOverlayOhlc/syncBgcolorBandsgained aforceFullowner option;runAndApplyInnerpassesforceFull: opts.liveTick !== trueso interactive runs / input recomputes bypass the tip-only smart-apply (which only patched the last bar when length + last time were unchanged). Live ticks keep the fast path vialiveTick: truein the multiplex re-run loop. - Wire page: removed separate scrolling sections; now uses single-scroll ax-page-canvas pattern matching Runtime, Settings, Workers, and Plugins.
- Settings polish: consolidated duplicate
sc-settings-*CSS into single definitions, addedsc-settings-section--plainandsc-settings-content--compactvariants to replace!importantoverrides, addedcolor-mixfallback,focus-visibleon color swatches, andprefers-reduced-motionguard; replaced!mt-0 !border-t-0 !pt-0with semantic variant and fixed ThemePanel compact gap override. - Workers probe: fixed
requestAnimationFrameleak — now cancels on unmount and removes deadhasLoadedsignal; probe defers to next frame without blocking studio paint. - Vendor: removed obsolete
pynescript-0.4.0/0.4.1wheels (replaced byhoox_pyne-0.4.2).
[2.0.30] — 2026-08-26
Fixed
- plot display= in compile mode:
plot(..., display=display.data_window)was not carried inplot_metabecause the compiler didn't fold Pinedisplay.*constants. Both axes now resolvedisplay.data_window/display.pane/etc. to bitfield integers and pass them through toplot_meta. - Pyne wheel — vendor
pynescript-0.4.2(was 0.4.1). Compiler display folding + server compile-path backfill.
[2.0.29] — 2026-08-26
Changed
- Engine default mode — both Pro and Pyodide engines now default to
auto(try compile, fall back to interpret) instead ofinterpret.
Fixed
- hline linestyle in compile mode: server-side compile path did not include
linestyle/color/priceinplot_metabecauseplot_attrsis empty for hline/fill. Backfill from__drawingsnow fills missing visual attrs. - Pyne wheel — vendor
pynescript-0.4.1(was 0.4.0). Server compile-path backfill fix.
[2.0.28] — 2026-08-26
Fixed
- hline linestyle in interpret mode: the interpret path (Pyodide default) did not synthesize
plot_metafrom__drawings, so LWC price lines forhline()always rendered solid regardless oflinestyle=. Both interpret and compile paths now synthesizeplot_metawith kind/linestyle/color/price for hline, fill, and plotshape drawing types.
[2.0.27] — 2026-08-26
Changed
- Pyodide PYNE wheel — vendor
pynescript-0.4.0(was 0.3.7). Engine catalog, legacy JS engine, SW cache test, and editor builtins synced from pyne 0.4.0.
Fixed
- hline linestyle:
hline(linestyle=hline.style_dashed)rendered solid — the drawing path only checkedr.style(neverr.linestyle), and the compile-mode overlay path lackedplot_metaentirely so the LWC price-line never received the style. Both paths now covered:pyne-drawings.tsfalls back tor.linestyle, and both Pyodide runtimes synthesizeplot_metafrom__drawings(hline/fill/plotshape kinds with linestyle, color, price). - multi-chart: same indicator on every grid cell showed identical values — the runner evaluates Pine against
store.bars(active plane), but focusing another slot never swapped in that slot's cached history, so every Run/add computed from whichever chart was loaded last.setActiveChartSlotnow restores the focused slot's own bars (+ gen bump), stops stale bar replay, restarts live for the slot's market, and silently re-runs applied scripts so each chart shows its own values. - multi-chart: switching to a 2×2 grid could overwrite the active chart with a sibling's symbol — ChartHost "prefetched" inactive slots via
loadSymbolData, which always writes the active plane (last-mounted sibling won). Removed; empty cells load on focus instead. - tables: render Pine
table.celltext_valign(top/middle/bottom) andtext_sizetokens/numeric points in the table HUD — parsed previously but rendered fixed 10px/middle; defaults unchanged when absent. - bgcolor: carry band titles into
syncBgcolorBandsand apply them as series titles on create/update, matching the offlinebuildPlotVisualspath.
[2.0.26] — 2026-08-24
Added
- Pages security headers —
public/_headersships the nginx/server.ts hardening baseline to Cloudflare® Pages deploys (CSP with self scripts +wasm-unsafe-evalfor Pyodide, https/wss connect, plus nosniff / referrer / frame-deny), so axis.hoox.sh gets enforced headers instead of only Page Shield's Report-Only placeholder. - Native MEXC CEX —
mexc-rest/mexc-wsas a built-in venue (public spot klines, no CCXT). Venue picker lists MEXC with Binance/OKX/Bybit; signed REST usesX-MEXC-APIKEY. MEXC was removed from the pinned CCXT shortlist.
Fixed
hline()levels vanished oncebgcolor()rendered —priceLineHostpicked the first non-overlay series, which after the bgcolor fix could be abgcolor_*histogram living on its own hidden 0–1 price scale; hlines attached there mapped far off-viewport. Host selection now skips bgcolor underlays (TradingView® paints backgrounds behind panes — they never host price lines).bgcolor()/barcolor()drew nothing — response normalization coerced every series sample throughNumber(), turning engine color arrays (rgba(8, 153, 129, 0.298)per bar) into all-null, so the histogram bands were filtered out before reaching the chart. CSS color samples now pass through normalization untouched (coerceSeriesSample); value plots still coerce as before.- Workers status chips stayed “Unknown” —
StudioStatusclosed over the firstprops.status(unknownwhile probes ran). The CSS class updated (green/red dot) but the word did not. Label is now read from props on each render.
Changed
-
Topbar Studio — one Studio button replaces Wire, Runtime, and Settings. Those pages stay in the studio rail (and ⌘K). The button reopens the last studio page.
-
Wire recipe grid — the hatch is a behind-layer at lower opacity. The right-edge fade no longer masks preset names.
-
PYNE Pro API origin is
https://pynescript.online— Hetzner VPS (nginx → gunicorn:5002).axis.hoox.shis the Cloudflare® Pages PWA, not same-origin with the API. Default Backend URL, Workers Manager probe, datafeed gateway, and Settings/Runtime presets follow the new origin; savedaxis.hoox.sh/ old VPS IP endpoints remap on load.
[2.0.25] — 2026-08-23
Fixed
- Script Settings inputs no longer lose to editor defaults — re-running an applied script from the editor keeps per-instance
inputValues/ strategy properties. Empty{}bags no longer wipe saved settings. Overrides persist only when they differ from the script default and are keyed by title, id, and LHS var name so the engine actually applies them.
Changed
- npm publish uses org secret
NPM_TOKEN_HOOXSH—release.ymlno longer looks for a repoNPM_TOKEN. Root Bun tests ignorepackages/**soaxis-clisuites run only afterpackages/clideps are installed. Authenticated skip detection treats an already-published version as success.
[2.0.24] — 2026-08-23
Added
- Production-grade
@hoox-sh/axis-cli0.2.0 — first npm publish of the AXIS CLI. Node-compatibledist/+npm i -g @hoox-sh/axis-cli, AGPLLICENSEin the tarball, provenance on tag publish, CI coverage forpackages/cli.
Fixed
-
plot(..., display=)was ignored at apply time — editor already knewdisplay.data_window/none/pane/price_scale/status_line/all, but every series still painted on the pane and in the Data Window. AXIS now readsplot_meta.display(token or Pine bitfield):nonehides everywhere,data_windowkeeps Data Window and skips the chart,price_scale/status_linegate last-value labels. PYNE packsdisplay=on plot/hline/shape/fill/bgcolor. -
E2E smoke vs studio pages — Plugins is opened from the Runtime rail (topbar hook is sr-only). Wire Apply waits for an exact
Wire · Offline Labtitle so a driftedOffline Lab +Nplan is not applied as Live Crypto. -
axis setup --remote-d1skipped local D1 —setup d1treated--remoteas exclusive of local even whenlocal: truewas passed, so bootstrap never applied the schema locally.--remotealone stays remote-only;--local --remoteand--remote-d1apply both. -
Worker
/healthprobe accepted any HTTP 2xx —probeHealthOR'd the JSONstatus === "healthy"check withres.ok, so a 200 HTML page counted as healthy. Requires a JSONstatusofhealthyorok. -
Failed
axis deploy workerran wrangler deploy twice — on inherit/quiet failure the CLI re-invoked deploy to capture stderr. Capture once, parse the workers.dev URL, never redeploy on error. -
--jsonmixed human banners into stdout —--jsonnow implies--quietso doctor/health/deploy emit parseable JSON only. -
Docker GHCR version tags could skip on
v*—docker.ymlpath-filteredpush, and GitHub applies that filter to tag pushes. Tag/main image builds no longer use a path filter (PRs still do). -
Unknown
axiscommands dumped a generic wrapper error — Commander help/version/unknown-command now exit with Commander's own code instead ofhandleError.
Changed
- npm release workflow — pin Bun 1.3.14,
id-token: write+npm publish --provenance, pack dry-run (LICENSE/dist/bin required), org secretNPM_TOKEN_HOOXSH. CI unit job now typechecks and testspackages/cli. Dead@clack/promptsdependency removed.
[2.0.23] — 2026-08-23
Changed
- Drawings / plot parity plan —
docs/architecture/drawings-parity.mdxlists P0–P3 work: user-tool move/handles, Pine pane routing, PYNE export gaps (tables, fill meta, plotshape Y, OHLC).
Fixed
-
Pre-eval treated
chart.fg_coloras a typo —pyne-builtins.jsononly listschart.point.*callables, sochart.bg_color/chart.fg_color(and thecolor_background/color_foregroundaliases, plus viewportleft_visible_bar_time/right_visible_bar_time/is_standard) were flagged “not a built-in member”. Allowlisted with the other Pine host constants. -
1-point drawings did not body-move —
shiftDrawingonly special-casedtext/priceLabel. Note, flag, crossline, anchored text, and arrow marks now shiftp1(andpoints[]when present). Third-anchorp3handles hit-test and resize. -
Linefill GC was a no-op —
garbageCollectScriptDrawingsignoredskip.linefillon the early-return. Overflowing linefill-only batches now trim oldest. Polyline/linefill copyforce_overlay; polylinefill_colormaps to fill. -
overlay=falsebgcolor/fill always hit the price pane — bgcolor bands takepaneId; fills use the script-pane drawing layer when the indicator is not overlay. Emptydrawings: []clears script SVG even on silent live re-runs. -
plotshape on oscillator panes —
setShapeMarkerstakes a pane id; overlay=false scripts attach LWC markers to the first overlay series onind_*instead of price candles. Trade markers stay on price. -
Drawing tool settings — Fill slider covers channel, triangle, gann boxes, ranges, highlighter; highlighter stroke no longer multiplies width ×6; horizontal ray places on one click; XABCD / head-and-shoulders commit on the 5th point.
-
Hide drawings hid only user tools — the eye toggle now also clears Pine script SVG and
fill()bands (every pane layer).label.style_text_outlinepaints outlined text;yloc.abovebar/belowbarsit 8px off the wick. First Add Indicator re-owns plotshape / fills / script drawings from__editor__to the script id. Histogram overlays honorplot_meta.histbasewhen the engine sends it.
[2.0.22] — 2026-08-23
Added
-
Topbar Venue picker — Data group is now Venue (native CEX / pinned CCXT / CCXT… / local) instead of a raw source-plugin list. Picking Bitget (CCXT) writes
ccxt-rest+ccxt-wsand bothexchangebags together;store.provider.idisccxt:bitget. Exchange ID stays on the topbar only for CCXT…. Stream remains hidden while paired. -
CCXT symbol browse uses that exchange's markets — with Source = CCXT (Gateway), the symbol modal loads
GET /markets?exchange=and keeps unifiedBTC/USDTtickers (slash is no longer stripped). Stream is hidden on the topbar while it still matches the source pairing (Wire / HUD Fix if it drifts). -
CCXT session API keys — Settings → Data can store a key + secret (+ passphrase / uid) per CCXT exchange id (
ccxt:bybit, …) in the RAM vault (neverpluginsConfig/ localStorage). Save POSTsPOST /datafeed/session; Load/Live then pass onlycred=on OHLCV/watch. Public candles still work without a key. Topbar shows an API key chip next to the CCXT exchange picker. Gatewayauto|pyne|sidecarmoved to Settings (advanced). Sidecar accepts/ohlcvand/datafeed/ohlcv. PYNEPOST /datafeed/sessionacceptsapiKey/secret/passwordin the JSON body. -
npm release pipeline for the AXIS CLI —
@hoox-sh/axis-cli(packages/cli) is now publishable: dropped"private", and a new.github/workflows/release.ymltypechecks, tests, builds and publishes the package to npm onv*tags using anNPM_TOKENrepository secret. A registry-version guard skips cleanly when the version is already published (safe tag re-runs); run summary reports local vs registry versions. -
"What's in this repo" section in README — table mapping every part of the repo to its role and paths: app/PWA, datafeed pipeline, calc engines + Workers Manager, vendored Pyodide runtime, on-chain plane, Cloudflare® Worker (API/WS/D1/KV/R2), allowlisted
/api/onchainproxy, Tauri desktop shell, npm-published CLI, tests/ops. -
Plugin config
advancedfields + Settings section — schema fields can now be markedadvanced(API base URL, Bars, Synthesize on failure, …). They are hidden from the Topbar config row and rendered in Settings → "Source & stream plugins" with full descriptions, using the shared form classes. -
Full unified ccxt exchange dropdown — the Exchange ID select is fed by the gateway's complete ccxt exchange list (
/health→ newccxt_exchanges, mirrorsccxt.exchanges; ~103 venues) instead of only the native adapters. PYNE side:backend/api/datafeed.pyhealth payload extended accordingly.
Changed
-
Studio full-page overlay — Runtime, Wire, and Settings are no longer right-edge drawers. They share one full-viewport page (
src/ui/studio/,ax-*classes) with a left rail, 16px type root, and large fields. Compactsc-dialogstays for Symbol / Script Settings / About / ⌘K. -
Runtime uncoupled from Workers and Plugins — Topbar Runtime is the active engine / endpoint / exec mode / health. Workers (backend inventory) and Plugins (contract catalog) are sibling pages from the rail, ⌘K, or Runtime cards — not tabs inside Runtime. Engine/endpoint/mode moved out of Settings → General; storage stays a Wire slot.
-
Workers studio restyle — Backend inventory is a full
ax-*studio page (WorkersPage): large health cards + inspector (probe features, numbered install steps, Use as calculation backend). No inner Overview/Detail/Install/Configure tabs and nosc-btnchrome. Engine writes go throughsaveEngineConfig. Open Plugins is a sibling cross-link, not a nested catalog. -
Plugins studio restyle — Contract catalog is a full
ax-*studio page (PluginsPage), sibling of Runtime. Catalog / Install / Script Library stay asStudioTabs(e2e tab name Catalog). Kind chips, large rows with Use + capability badges, URL install + example cards, Script Library embed, active src/eng/stm/stor footer + Done. Open Workers and Open Wire are sibling cross-links. Close stays on the AppPage shell (axis-plugins-close);axis-managerstays on the dialog only. -
Data tab no longer duplicates bar count or Binance API host — how many bars to load lives only under General → Historical bars. Native CEX host URLs stay as fetch defaults (Binance already has host + Worker fallback). Gecko proxy/network, CCXT gateway, and synthesize-on-failure remain.
-
Docker release images tagged by version, not sha —
docker-bake.hclrelease targets now pushpwa-v<VERSION>/pwa-nginx-v<VERSION>(e.g.ghcr.io/hoox-sh/axis:pwa-v2.0.21) instead ofpwa-sha-<sha>. The git SHA remains on the image as theorg.opencontainers.image.revisionlabel and build arg; workflow docs updated to match.
Fixed
-
Plugin config row — first-save, apply, and exchange dropdown — shared source/stream fields now (1) resolve a stored value from any target bag before falling back to a schema default (stream
exchangeis no longer hidden by a source default of''), (2) persist text/number keystrokes but only reload history on blur/Enter (selects/checkboxes still apply immediately), (3) treat the exchange control as a<select>only when the gateway list is actually populated, with a placeholderSelect exchange…option and a free-text fallback when the fetch fails or returns empty, (4) cache the ccxt list per gateway mode, and (5) wire stacked-formlabel forto the controlid. GeckobaseUrl/network, Coinbase granularity, and Mock Walk start price are Settings-only (advanced). Topbar Load no longer bails out while a previous fetch is in flight. -
Pre-eval scanners close paired typographic quotes —
“…”/‘…’now close on the matching right quote (not only the same code point), so a pasted title string no longer marks the rest of the file as an unclosed string and block Run. SharedisQuoteCloseinpine-scan-util.ts. -
array<string>survived asarrayafter Add types — the declaration rewriter captured the collection token but dropped<string>when inserting a missing type1 qualifier. Generic args are preserved (simple array<string> vals = …). -
Type methods and
export enummissed as user bindings — indentedmethod profit(this) =>inside atypebody andexport enum Namenow register as declared names (same asexport type). -
const(and line-leading keywords) colored inconsistently — green vs violet — a float ending in a trailing dot (2.,0.) was split into number + lone., leaving the tokenizer'safterDotstate set at end-of-line. The state leaked across comments/blank lines, so the next code line's first word (const,series, …) rendered as a member property (green) instead of a keyword (violet accent). Number scanning now consumes trailing-dot floats andafterDotresets at every line start. -
Pre-eval typo checker round 3 — validated against the real 2222-line grid strategy — found via local reproduction on
grid.pine: generic typed declarations (var series array<string> TP1 = …) were not parsed by the assignment regex, so TP1/SL1/TRL1-class vars were never registered as declared; library import coordinates (import cryptolinx/String/1 as strx) leaked into both the declaration collector and ident scanner (String/Hooxflags); and dotted method calls (.init(),.show(),.abs()) were treated as bare-call typos. All three fixed; checker now reports 0 typos on grid.pine, grid.2.pine. -
Pre-eval typo checker round 2 — declaration coverage + copy-paste resilience — remaining false positives addressed:
strategy(linktoseries=…)whitelisted (pyne accepts arbitrary declaration kwargs); typographic/curly quotes (“”‘’) now treated as string delimiters in every scanner so pasted text no longer leaks as identifiers;enummembers + enum/type names,typefields, andfor [i, v] in …loop vars are collected as declared bindings; dotted-path roots (Mode.SINGLE) skipped by the ident scanner. -
Pre-eval typo checker flooded false positives (~260 per script) — pyne's LSP metadata only lists functions/namespaces, so the pre-eval linter treated core built-in series variables (
close,high,low,open,volume,ohlc4,hl2…), type qualifiers (simple/series/const) andtext.align_*/text.wrap_*constants as unknown identifiers and suggested nonsense ("Unknownclose— did you meanCROSS?"). Added a curatedPINE_BUILTIN_VARSsupplement (OHLCV + derived, time/bar state, calendar parts) plus the missingtext.*members to the known-path index; qualifiers also join the bare-call skip set. Regression tests cover the reported failure pattern (now zero typos). -
Config row crash on first save —
setFieldwrotepluginsConfigthrough a deep Solid store path whose parent bag did not exist yet; solid-js/store does not auto-create intermediate nodes, so the first-ever config change threwTypeError: can't access property "exchange"and never persisted. Bag creation is now guarded (writePluginField), covered by regression tests and a browser e2e run. -
Gateway select had no choices — the ccxt
gatewayschema fields lackedoptions, so the dropdown rendered only its current value.auto | pyne | sidecarare now declared on bothccxt-restandccxt-ws. -
Config row deduplication — the Topbar renders one shared row for the union of active source/stream config fields (was: duplicated per picker); changes write through to all declaring plugins so ccxt-rest and ccxt-ws stay in sync.
-
Gateway loopback on remote pages (hardened VPS) —
gatewayBaseresolvedpyne/autotohttp://127.0.0.1:5002/datafeedeven when the page was served from a remote host, so browsers onaxis.hoox.shfired doomed cross-origin requests at the visitor's machine (CORS NetworkError). Remote pages now resolve to same-origin/datafeedon product hosts (nginx) or the product API origin cross-origin (Pages previews); loopback behavior is unchanged for local dev. Explicit endpoint overrides still win. -
ccxt-ws
require()actually removed — v2.0.21 added the staticgatewayWsimport but left the shadowing CommonJSrequire('../data/gateway')call in place, so the browser bundle still threw on stream start. Therequireis now gone.
Changed
-
Plugin config moved to Settings → Data — the "Source & stream plugins" section (with the advanced fields) now lives in the Data tab above Exchange API keys, with a note that CCXT Gateway venues serve public market data and need no keys. Native-source keys (Binance/OKX/Bybit/Coinbase/Kraken) remain session-only in the browser vault and sign requests client-side.
-
Config row styled like the Source picker — inline plugin config fields now render through
TopbarField(integrated uppercase label, sharedaxis-tb-fieldchrome, focus-within ring), matching Symbol/Source/Interval exactly; the Settings variant keeps the dialog's stacked form classes. -
Stream config pass-through —
multiplex.startLivenow passesstore.pluginsConfig[stream:<id>]intostream.start({ config }), matching the source path, so ccxt-ws receives the configured exchange id. -
ccxt plugins: clear error for unconfigured exchange —
ccxt-rest/ccxt-wsno longer fire requests with an emptyexchange=param; they surface "exchange id not configured" instead.
[2.0.21] — 2026-08-21
Fixed
- ccxt-ws stream:
require()in browser ESM — replaced CommonJSrequire('../data/gateway')with a static import. Therequirecall threwReferenceErrorin the Vite browser bundle when activating theccxt-wsstream (Bun tests masked it since Bun honorsrequirein ESM). - ccxt-rest backfill window inversion —
endTimewas forwarded as CCXTsince, which pages forward from that timestamp, breaking the Data Source Manager walk-back contract (bars must end at/beforeendTime). Now derivessince = endTime − limit·timeframeso each page covers[endTime − limit·tf, endTime]and gap-fill converges.
[2.0.20] — 2026-08-21
Added
-
Datafeed gateway transport (
src/data/gateway.ts) — resolves gateway URL (auto/pyne/sidecar/direct), probes sidecar health with 30s TTL cache, and providesgatewayFetch/gatewayWshelpers for plugins that route through the PYNE datafeed gateway or local sidecar. -
ccxt-rest source plugin — long-tail exchange OHLCV via the PYNE datafeed gateway or sidecar. Routes
ccxt:<exchange>venues throughGET /datafeed/ohlcv. Credentials stay server-side; browser never holds API keys. -
ccxt-ws stream plugin — real-time klines via the datafeed gateway WebSocket. Routes through
WS /datafeed/watchwith exchange+symbol+timeframe params. -
Gateway-aware active resolution — when provider gateway is not
directand the venue is accxt:<exchange>long-tail id, active source resolves toccxt-restand stream toccxt-ws. Native venues (binance, okx, etc.) are never swapped.
[2.0.19] — 2026-08-20
Fixed
- Hex color lint false positive — pre-eval
scanIdentRefsno longer treats hex color tails (e.g.d3eein#22d3ee) as unknown identifiers. Scans backward past hex digits to detect the#prefix and skips the entire literal. - Pre-existing TypeScript errors — resolved 23 type errors across 7 files: null barIndex guard, CSS
TextAlignnarrowing,PineTablecamelCase property reads viaRecord<string, unknown>,linefillmissing fromDrawingLimits/ drawing caps/counts/skip,cancelledstatus narrowed type assertions,ParamValueimport + explicit typing, legacypanelWindows/dockLayoutaccess onAppState.
Changed
- CI typecheck gate — added
bunx tsc --noEmitas a first step in the CI workflow (runs before unit + security tests). Addedtypecheckscript topackage.json.
[2.0.18] — 2026-08-20
Added
- Provider session — chart aggregators inherit a locked venue identity (
store.provider: source + stream + market + auth mode). Source changes re-pair the live stream; HUD shows venue and a Fix chip when stream mismatches. Data Source Manager follows the chart source unless "different source" is checked. kraken-rest— public Kraken OHLC source, paired withkraken-ws.- Exchange API keys — Settings → Data stores key/secret/passphrase in a session vault (RAM only). Saving a key for the active venue sets
provider.authMode = authenticated. Secrets never go to localStorage,pluginsConfig, or error-share dumps. - Signed Binance klines — with a vault key, history uses HMAC REST then the Worker
GET /api/market/binance/signed/klines(request-scopedX-Exchange-Key/X-Exchange-Secret, not a Worker vault). Public allowlisted proxy is unchanged. - Venue HMAC signers — thin Binance / OKX / Bybit / Coinbase / Kraken signers in
src/data/venues/(no CCXT). - Test key — Settings → Data Test key button verifies saved credentials with a signed 1-bar kline fetch. Shows success, 401/403 rejection, or CORS/network warnings.
- ADR-016 — provider-locked market data architecture decision record.
- SessionDO multi-venue — Worker Durable Object relay now supports all 5 venues (Binance, OKX, Bybit, Coinbase, Kraken) via
venuequery param. Each venue's WS URL and subscribe message handled correctly. Browser → DO control messages acceptvenuefor resubscribe. - Shared venue WS builders —
src/streams/ws-venues.tsprovidesbuildVenueWs()for both browser-side StreamPlugins and the Worker DO. Single source of truth for URL patterns and subscribe messages. @hoox-sh/axis-datafeed— optional Bun sidecar (packages/datafeed/) using CCXT Pro for users who want local WS without running Flask. Mirrors the PYNE datafeed gateway contract:GET /datafeed/ohlcv,GET /datafeed/markets,WS /datafeed/watch,POST /datafeed/session. Run withbun run dev:datafeed.
Fixed
- Mixed-provider quotes — watchlist no longer falls back to Binance tickers for Kraken (or unknown venues).
- Coinbase live bars — stream uses Advanced Trade venue candles folded into the chart interval, not ticker buckets.
- Binance synthetic fallback —
fallbackdefaults off, so a network error cannot look like real prices.
[2.0.17] — 2026-08-19
Visible pre-eval squiggles, Problems that drop on edit, and idle lint for named args and declared vars.
Added
- Named-arg typos — idle lint flags unknown function/method parameters (
plot(..., coltor=color.green)→color=) from curated signatures, builtin docs, and user-function parameter lists. - User-var typos — assignment /
var/ tuple-unpack / function-parameter names are added to the typo map (declaration is source of truth), solength = 14thenlenghtsuggestslength.
Fixed
- Lint underlines — pre-eval / error marks use a high-precedence squiggle (SVG + wavy underline + inset line), not CodeMirror
baseTheme, so they stay visible against Tailwind. Empty decoration sets rebuild when underlines are on. The tab-switch effect no longer loops and cancel the idle lint timer, which left the buffer with Problems but no squiggles. - Stale Problems — pre-eval rows drop as soon as the buffer diverges from the last linted source. Idle lint no longer stamps the live buffer onto old diagnostics, and rescheduling the same buffer does not wipe a completed lint (fixing a typo no longer leaves the old error in the list).
[2.0.16] — 2026-08-19
Restore editor hover, lint underlines, and chips after a dead Settings bag and clipped hover cards.
Fixed
- Editor intel dead bag — an all-off persisted Settings bag (checkbox mount / store-proxy spread) no longer disables hover, lint underlines, completions, and chips. Hydrate recovers defaults; user patches write
rev. - Hover cards clipped — Pine hover tooltips set
clip: falseso overflow-hidden editor chrome no longer hides cards on the first lines. - Hover hide-on-apply — decoration / selection transactions no longer dismiss hover cards; only document edits do.
- Editor hover catalog — bare
close/newno longer stealstrategy.close/array.new. Hover facts win; completions still walk modules. display.pane— real Pine v5+ member is allowlisted (no more typo →display.none). Inventedmath.isnan/math.isfinitecompletions removed.- Editor intel timeouts — Hover / Completion timeout settings now reach
/lsp/*(were hardcoded). - Intel number fields — clamp on blur / spinner, not every keystroke, so
1500is typeable. - Diagnostic hover — respects Show errors / warnings / typos / info flags.
- Pre-eval flags apply live — toggling lint generation immediately re-runs or clears marks.
- Hide + tables —
table.*HUDs follow visibility; drawings / fills /barcolorare owner-scoped so hide with a sibling no longer leaves the hidden script’s artifacts. - Hide exclusive pane — empty oscillator strip is destroyed; show recreates it.
*brprimitives —destroyPanedetaches line-break primitives; attach failure is not cached; segments are cached and culled to the visible range.- Last-bar drawings — script paint no longer grows
rightOffsetup to 500 bars for wall-clocktimenow(one-barbar_index+1still maps). - PYNE Agent seed — same-origin plugin install no longer writes a hardcoded Worker URL; chat stays off until the operator sets the endpoint.
- Unstamped last-run marks — engine underlines drop unless
meta.axisSource(orscript) still matches the buffer.
Improved
- Hover facts —
hl2/ohlc4/hlcc4use midpoint / OHLC / HLCC wording (onlyhlc3is typical price). plotshape/plotchar— curated params includeformat,precision,force_overlay;seriesisint/bool.- Docs —
areabris only on the broken-segment row; debugging mermaid includes pre-eval → Problems.
[2.0.15] — 2026-08-19
Editor intelligence settings, Supertrend linebr gaps, live script hide/run, and PYNE Agent chat markdown.
Added
- Editor intelligence settings — Settings → Editor (and command Open Editor Settings) exposes every lint / pre-eval / hover-card / signature-hint / autocomplete / underline / gutter / inline-chip option, plus idle / tab-switch / remote timeouts. Defaults match the previous hardcoded behavior; Reset defaults restores them.
- Strategy chart trade size — entry/exit markers print
Long N/Short N(and exit id + qty) so the filled amount is visible on the chart, not only the order id. - Hide plot names on last-value labels — Settings and chart [T] clear
RSI/Overboughttitles beside the last value; the numeric label stays. Independent of [N].
Improved
- Pre-eval timings — idle lint stays 1s after the last keystroke (configurable 200–3000 ms). Tab switch uses a separate 200 ms beat (was a hardcoded 120 ms). Remote
/lsp/diagnosticswait is 2s (configurable) and only starts after idle; local marks still publish immediately. Stale “2s idle” editor comment removed. - PYNE Agent chat markdown — replies render headings, lists, and bold instead of raw
###/1. **Foo**. Plugin default API endpoint is empty (set in plugin config); no hardcoded Worker URL. - Editor hover + param checklist — mouseover covers keywords, types/qualifiers, series (
close/bar_index), namespaces (ta.), user inputs/functions, and hex colors. Call hints list type + default; current param is emphasized (no strikethrough on used). Hover cards use a void-theme header, parameter definition list, and inset example. - Pre-eval / Problems / debug chips — idle lint is 1s;
study()and baresecurity()warn; duplicateindicator()/strategy()warn; Problems label pre-eval vs run; debug chips have higher contrast and truncate long logs. - Live scripts auto-run — starting a stream immediately re-runs every visible script on the chart (no extra Run). Hide pauses execution and clears that script’s plots (series, markers, drawings, fills,
barcolorwhen it is the last visible script); show re-runs it.library()sources stay out of the live / reapply loop. - Editor type highlighting —
int/float/string/ … and declared UDTs / enums (type Point,export enum Easing,m.Easing) use the type token in bold.series/simpleare bold only as type qualifiers;m.SuperTrend(stays a library member. - Strategy Properties wiring — Apply persists only changed
strategy()kwargs (so leverage is not overwritten by default margin %). Leverage ↔ margin UI no longer writes the auto-filled sibling into the bag. Editor Properties apply on isolate/HPO runs. Execution flags are documented as not implemented in PYNE.
Fixed
plot.style_linebr/steplinebr/areabronna— Lightweight Charts Line/Area series drop whitespace points and connect the remaining samples, so Supertrend-stylebull ? st : naplots drew a diagonal through inactive stretches.*brstyles now hide the LWC connector and stroke each finite run (isolated samples stay a tick).plot.style_linestill spansna.- Hide mid-run — an in-flight live apply no longer paints after Hide; empty-bar
startLiveno longer logsNo bars loaded. - Last-value names — compare / on-chain titles survive [T]; volume stays number-only unless a title was set. Default
exactOnCandleputsLong Non the side arrow, not the in-bar circle. - Isolate / HPO strategy bag — explicit
strategyProps: {}means no rewrite; isolate no longer merges leftover editor broker settings onto a baked study. - Last-bar /
varipdrawings —line.new/box.new/ polylines / linefills that extend one bar past the series (bar_index + 1) are no longer snapped to the last candle (that flattened them into a vertical tick). Labels still clamptimenowonto the last bar. Historicalvaripplots already render as a normal 1-sample-per-bar series (AXIS does not sendrealtime_last_bar; enabling it would reset last-barvaripcells under pyne’s current re-init). - Remote LSP cooldown — aborting an in-flight
/lsp/*request (typing, hover leave, pre-eval cancel) no longer starts the 30s failure cooldown, so idle parse diagnostics still reach pyne. - Editor Problems / underlines — a clean pre-eval no longer wipes last-run runtime errors. Pre-eval and last-run marks are unioned while the buffer still matches the run source.
plotshape/plotchar/plotarrowstyle=— completions offershape.*instead of invalidplot.style_*.- Pre-eval block comments —
"/*"andhttps://inside strings are no longer treated as comments (which could block Run). - Inline debug line parse — log values like
RSI: 55no longer become a chip on line 55; structuredlinewins. color=completions —color.new/color.rgb/color.from_gradientstay in the list (including aftercolor=color.).
See also
[2.0.14] — 2026-08-17
Strategy hyperparameter search in Results, isolated trial runs, and /optimize proxy wiring.
Added
- Hyperparameter Optimisation — built-in component plugin (
hyperparameter-optimisation) and Results → Optimise tab. Searchesstrategy()input.*values over N trials. Evaluation SoT is pynePOST /optimize(TPE / random / grid + holdout / walk-forward); Pyodide falls back to an isolated client loop. Apply winner merges into Script Inputs (unsearched fields stay). Strategies only. runScript({ isolate, bars, signal })— headless engine eval so HPO trials do not touchlastRun, the chart, or the engine HUD. Live multiplex defers while a study is running.- Vite / Docker nginx proxy
/optimizenext to/runso same-origin VPS andbun run devhit Flask.
See also
[2.0.13] — 2026-08-17
Call-parameter intelligence, richer Pine highlighting, script input enums, and Pine table lifecycle.
Added
- Call parameter intelligence — hover on
plot/input.int/ta.sma(and other curated calls) shows parameter docs + an example. After,inside a call, completions offer remaining named args (title=,minval=) in a Parameters section and already-used ones under Already used. A signature hint below the cursor lists every parameter and marks used / current / unused.
Improved
- Pine editor highlighting — series builtins (
close,bar_index,time, OHLC) use a cyan token distinct from user variables;import … asaliases and library export members (m.Easing) use a violet family; types get a warm token. New chrome vars--color-cyan,--color-editor-builtin,--color-editor-lib,--color-editor-lib-member,--color-editor-type.
Fixed
- Script Inputs enum values —
input.enum(m.Easing.linear)no longer shows the import/Python path as a text field. Members are inferred fromenumdeclarations (local + imported library source), values normalize toType.member, and the modal seeds from that script’srunResults(not another indicator’slastRun). - Script display name — applied scripts / pane badges always use the Pine
indicator()/strategy()/library()title (not engine"plot"or a stale file name). - Pine tables after delete — table HUD only shows tables from still-applied scripts’
runResults(orphans /__editor__leftovers no longer stick). First-run migrates run cache to the real script id; last-script delete clears script drawings. - Pine table layout — grid size expands from cell extents when engine understates rows/columns; frame/border and cell align improved.
[2.0.12] — 2026-08-17
Editor color chips + type declarations, chart label/marker lifecycle, topbar icon map, debug UX.
Added
- Editor inline color chips — square swatches (line-height × line-height) before Pine color literals (
#hex,color.red,color.rgb(...),color.new(...)). Click selects the range. - Add type declarations (editor overflow → Source) — after a successful first run, inserts missing Pine type1 (
series/simple/const) and type2 (int/float/bool/string/color/ …) on untyped assignments. ICON_MAP(src/ui/icon-map.ts+Iconsinicons.tsx) — one Lucide glyph per product intent; topbar panel buttons each use a unique icon.
Improved
- Panel hamburger menu — consistent 12px label / 14px icons; New window → New tab.
- System Logs — clear control uses trash icon instead of ×.
- Topbar panels — reordered List → Editor → Library → Scripts → Inputs → Layers → DSM → On-Chain → Alerts → Values → Results → Script Logs → System Logs → Status.
- Inline debug / Chart pins — overflow Debug items include full how-to on hover; toggle shows status-bar tips.
Fixed
- Duplicate last-value labels on indicator panes (editor owner + real script id stacked series).
- Strategy long/short labels stayed after remove — trade markers are owner-scoped and cleared on detach.
- Chart chrome — removed top-right fullscreen / chart-only icon buttons (F11 / Shift+F / Esc still work).
- Inline debug enable gate — chips only when explicitly on; pin gutter class clears when empty.
[2.0.11] — 2026-08-17
Chart script lifecycle, per-script indicator panes, pyne-worker engine, panel drag UX.
Fixed
- Panel title bar — a plain click no longer undocks the panel to float; undock/move starts only after the pointer is dragged past a small threshold (hamburger still: click = menu, hold/drag = move).
- Non-overlay indicators — each
overlay=falsescript gets its own sub-pane (ind_<id>) instead of stacking into a single sharedindicatorpane. Removing one script only clears that owner’s series and no longer destroys siblings’ full history (was left with 1–2 bars). - Chart scripts lifecycle — add/remove/reopen stays correct: owner-scoped detach (no leftover series/empty panes), ChartHost no longer wipes re-applied scripts after history paint, centralized
reapplyChartScriptsrestores visible scripts (with saved inputs/strategy props) after symbol load / cache paint; applied scripts are sanitized on hydrate and re-run on reopen.
Added
- Engine
pyne-worker— built-in edge evaluator plugin (defaulthttps://pyne-worker.cryptolinx.workers.dev), API key field, Workers Manager / Settings presets. Distinct from AXIS data-plane Worker and Flaskserver.
[2.0.10] — 2026-08-16
Market data resilience, Script Settings input layout, and local-first editor LSP.
Improved
- Script Settings → Inputs formatting — Pine
group,inline,tooltip(\\n), andactive/active=<ident>layout in the modal (TV Settings-style sections, same-line clusters,?help, grayed inactive fields).input.text_areauses a multi-line control. - Editor LSP hover + autocomplete — local builtins/doc annotations show immediately (no wait on a dead Backend URL); remote Pro API is short-timeout + 30s cooldown after failure. ⌘/Ctrl-Space triggers completion; tooltip z-index raised so hover/suggest stay above chart chrome.
Fixed
- Browser market data / plugins (CORS “status null”) — when the page cannot reach venue hosts or remote plugin modules (geo, firewall, port 9443, extensions), AXIS no longer hard-depends on a single cross-origin path:
- Binance REST tries
api.binance.com→data-api.binance.vision, then the Worker allowlisted proxyGET /api/market/binance/{klines,ticker/24hr,exchangeInfo}. - Binance WS rotates
stream.binance.com:9443→:443→ default 443 →data-stream.binance.vision. - PYNE Agent plugin ships same-origin at
/plugins/axis-pine-agent.js(legacy remote URL migrates on restore); agent API endpoint still defaults to the pyne-agent Worker. - Service worker navigation never rejects
respondWith(offline shell HTML); cache version v5.
- Binance REST tries
Added
- Worker market proxy —
worker/src/market.tspublic allowlist for Binance klines / 24hr ticker / exchangeInfo (short isolate cache; not an open reverse proxy).
[2.0.9] — 2026-08-15
Editor symbol catalog, richer Pine highlighting, CORS for Pages previews, and CI greens.
Added
- Editor symbol & emoji manager — status-bar Symbols catalog of TV-editor-safe arrows, box drawing, blocks, marks, spaces, and chart emoji (IBM Plex Mono vs wide). Insert raw, quoted, or
plotchar(…).
Improved
- Pine highlighting — stateful multiline strings +
/* */blocks,\\n/ hex-color atoms, namespaces (ta.,label.new), types, control vs definition keywords, function names before(. - Format — leaves continuation lines of an unclosed string or block comment untouched.
Fixed
- CORS — Cloudflare® Pages previews (
*.pynescript-axis.pages.dev) can callhttps://axis.hoox.sh. Pyne always allowlists those product Origins and treatsGET /healthas a free CORS path (AXIS Settings probe). WorkerpickOriginalready echoed the same hosts. - CI — library publish stays on the local cache when leftover/invalid git credentials fail remote (was
GitHub: Bad credentialsin the full unit suite). Smoke waits onaxis-status-messageand opens Plugins via Runtimes. Vite build on macOS/Windows resolves SolidTopbar.tsx/Watchlist.tsxinstead of same-stem legacy JS. Docker push uses thereleasebake group (no:localdocker.io tags) and bake-action v6 env overrides.
Changed
- Pyodide PYNE wheel — vendor
pynescript-0.3.7(Runtime.run(libraries=)forimport ns/Name/ver, ring-series skip, runtime hot path). Engine catalog, legacy JS engine, SW cache test, and editor builtins synced from pyne 0.3.7.
[2.0.8] — 2026-08-15
Architecture modal, library publish emulator, drawings/plot parity, and the post-2.0.7 hardening stack.
Changed
- Pyodide PYNE wheel — vendor
pynescript-0.3.6(UDTarray.binary_search*sort_field, drawing export/delete/fold). Engine catalog, legacy JS engine, SW cache test, and editor builtin metadata synced from pyne 0.3.6.
Added
- Library publish emulator —
library()scripts publish to git{basePath}/published/{ns}/{Name}/{1,2,3…}/lib.pyne(plus local cache). Library panel Publish library, or auto-publish on a successful Run. Consumersimport ns/Name/ver; AXIS resolves the folder and sends sources to the engine (register_library_source). - Architecture modal — topbar Wire (and command palette Open Architecture) opens a compose-recipe dialog: pick Offline Lab / Live Crypto / CSV Desk / On-Chain / Team Cloud, then swap or switch off any source · stream · engine · storage · dataset slot. Plan name records drift (
Live Crypto +1 −1); Apply commitssetActivePlugin+ optional on-chain panel. - Pine
labelstyles / yloc — normalize passes throughstyle,yloc,size/text_size,textcolor, andcolor; SVG paint supportslabel.style_label_up|down|left|right|center(and bare tokens), bubble tips, icon markers (xcross, triangles, …), andyloc.abovebar/yloc.belowbarwhen OHLC bars are available (defaultyloc.price+ bubble for unknown styles). - About AXIS modal — click the topbar HOOX/AXIS brand (or command palette About AXIS / Help → About) for product, author, and HOOX ethos from hoox.sh/manifesto, with links to AXIS / PYNE / docs.
- Script settings → Properties — when a
strategy()is loaded, a Properties tab exposes broker parameters (initial capital, order size / pyramiding, commission, leverage / margin, process orders on close, calc flags). Overrides persist per script and are merged intostrategy()on run without rewriting the editor buffer. linefill.newpaint — normalize + SVG quad fill between two lines (pairs with pyneexport_for_apilinefill serialization).barcolor()— per-bar candle body/wick tint fromkind: barcolorseries (LWC color fields).- plotshape multi-script — shape markers are owner-scoped by script id so one run does not wipe another script’s shapes.
- Non-overlay drawings —
overlay=falsescripts paint geometry on the indicator pane Y-scale;force_overlaystill routes to price. - Compile
line.set_*— pyne folds set events onto handles before export (final geometry for AXIS).
Improved
- Editor default width — factory / layout-reset docked editor width is 50vw (was fixed 460px); existing persisted widths unchanged.
- Editor gutters on demand — line-number column stays content-width (digits only; overrides CM 20px min); diagnostic + inline-debug gutters hide until they have markers (profiler-style).
- Modal focus trap — shared
installFocusTrapcycles Tab inside Settings / About and restores prior focus on close. - Chart a11y region — ChartHost exposes
role="region"with symbol/interval/bar-count label; load success/error uses polite/assertive SR announcer. - Session DO stream sanitize — symbol
^[A-Z0-9]{1,20}$+ Binance interval allowlist before upstream WS URL is built. - Prod plugin remote allowlist — production builds default-deny remote plugin hosts (same-origin
/plugins/*+data:+ allowlisted PYNE Agent host); override withVITE_ALLOW_REMOTE_PLUGINS/VITE_PLUGIN_REMOTE_ALLOW. - Plot fill apply — skip SVG rebuild when fill fingerprint matches; pan/zoom paint uses visible-range cull + pixel-budget stride on dense bands.
- Runner OHLCV times cache — live re-apply reuses the times axis when
chartDataGen+ length + last time are unchanged (open-bar ticks). - Webhook URL policy — https-only, no credentials, reject loopback/private IP literals; 8s AbortController timeout.
- Presentation Escape — detect AXIS modals via
[role=dialog][aria-modal=true](not only native<dialog open>). - CSP baseline — nginx + Bun static server send Content-Security-Policy (self scripts, wasm-unsafe-eval, https/wss connect, frame-ancestors self).
- First-paint bundle — chart PWA critical path no longer statically imports CodeMirror/editor or
@tauri-apps/*:index.tsxdynamic-imports App vs EditorApp;EditorPaneis Solidlazy+Showwhen docked editor is open; Tauri shell install is dynamic-imported after a lightweightisTauriShellcheck. - Multi-chart slot bars reactive —
chart-registrystores per-slotbars/chartDataGenin a Solid store so inactive ChartHost slots re-paint when those paths change. - PWA SW update UX — stop blind
SKIP_WAITINGwithout a reload path; post skip-waiting only when activating a waiting update, and soft-reload once oncontrollerchange(refreshing guard) so mixed old/new modules never stick. Still skips DEV and Tauri. - Heavy live re-runs — at ≥10k bars,
every-tickindicator re-runs are treated asbar-closeso full OHLCV is not re-encoded for the engine on every open-bar tick (effectiveLiveRerunMode). - Offline history fallback — when venue/source fetch fails,
loadSymbolDatapaints warmbars-cacheseries and reportsOffline · N cached bars(telemetrydegraded). - SW runtime cache cap —
axis-runtime-v4soft-caps at 96 entries (FIFO trim after put); version bump clears prior unboundedv3runtime caches. - Watchlist quote batching — multi-symbol WS ticks coalesce to one Solid price-map write per animation frame.
- Live tick rAF coalesce — multiplex keeps only the newest bar until the next animation frame so trade-ticker floods (Coinbase) do not thrash store + LWC every message.
- Pane resize coalescing —
PaneManagerResizeObserver callbacks flush once per rAF (was synchronousapplyOptionsper pane entry). - Smart series apply fingerprint — overlay tip no-op when length + last time and last value match (skips redundant LWC
updateon silent re-runs). - Overlay tip-only re-apply — silent live
syncOverlayLinesupdates the last point without a fulltoLwcLineDatamap when prior meta length + lastTime match (falls back to full setData on length/time change). - StatusBar / Results strategy reports — no longer rebuild trade pairing on every live
store.barspath update; depend onlastRun/chartDataGen/ fill-mode prefs. loadBarsbatching — multi-field store writes run inside Solidbatch()for a single reactive flush.- Vite critical path —
modulePreloaddrops CodeMirror / pyne-builtins from first paint; gzip enabled on Docker nginx;X-Frame-Options: DENY. - OHLCV bulk paint hardening —
mapBarsToPriceData/ volume mapper drop non-finite rows so one NaN bar cannot blank LWC series. - Plot styles — distinct series kinds for
plot.style_columns(vs histogram),plot.style_cross(discrete markers, no connector), andplot.style_stepline_diamond(stepline + vertex markers). plotshape diamond/cross map to square marks with optional+/✕glyphs. LWC still lacks column-width and native diamond/cross point markers — documented in charting docs. line.style_arrow_*— script lines paint SVG arrow heads forarrow_left/arrow_right/arrow_both(were solid stroke only).
Fixed
- Editor Problems panel close — auto-open only when diagnostic count increases; no longer re-forces open on every pre-eval/store tick while problems remain (statusbar toggle can stay closed).
- Worker
/api/runauth fail-closed — whenEXTERNAL_BACKENDis non-empty orPYODIDE_IN_WORKER=enabledandALLOW_OPEN_KEYSis not"1",POST /api/runrequiresrequireApiKey(also still forced byAPI_KEYS/REQUIRE_RUN_AUTH); local demos keep open keys without burning unauthenticated compute on a real backend. - Script-only drawing layer leak —
clearScriptPaneLayers()on ChartHost unmount (RO + time-scale subs were retained after multi-chart dispose). - Companion panel postMessage — origin-bound target + receiver check (no more
*). - Legacy results event CSS class — allowlist kind tokens before interpolating into
class(attribute breakout). - ResizeHandle multi-touch — ignore non-primary pointer ids mid-drag.
- bars-cache IDB integrity —
pagehide/beforeunloadflush pending debounced puts.
Tests
- CI workflow —
.github/workflows/ci.ymlrunsbun run test,test:security, and Playwright@smoke(Chromium) on PR / main. - Optional firehose bench —
tests/bench/(AXIS_BENCH=1/bun run test:bench): 500kappendBarticks on ~50k history with soft wall budget; skipped by default. - Drawings / plot-visuals — unit coverage for
force_overlaynormalize,linefilledge cases,barcolorSeriesToMap/coerceBarColor, and GC withlinefillpresent. - plot-visuals — columns / cross / stepline_diamond kind mapping, histogram-family helpers, diamond/cross shape + glyph defaults.
- chart-type / heavy-data — non-finite OHLC filter on price + volume mappers.
- store-append-scale — always-on
appendBarsame-time + cap invariants. - multiplex-heavy-rerun / sw-strategy / load-symbol — heavy re-run mode, runtime cache FIFO cap, offline bars-cache fallback.
- register-sw — pure update helpers (
shouldSoftReloadOnControllerChange, skip-waiting) + controllerchange single-reload mocks. - security / runner / fills — remote plugin prod allowlist, webhook URL policy, OHLCV times cache, plot fill signature.
[2.0.7] — 2026-08-12
Pine drawings diagnostics and plotarrow direction; pairs with pyne 0.3.5 last-bar line export.
Fixed
line.new/ drawings apply — prefer DrawingLayer fallback when applying Pine drawings; warn when the engine returns drawings that normalize to zero or when no layer is mounted (load bars first).plotarrow— negative series samples render as down arrows (were always up).
[2.0.6] — 2026-08-12
Test suite isolation and store hydrate defaults hardening.
Fixed
- Tests — isolate store/state defaults across suites; strategy-extra uses event prices (not bar fills); engine
isReadymock returns JSON health; hydrate never restores plane telemetry from disk. - Store seed — createStore no longer aliases nested
DEFAULTSobjects (telemetry/panels/live), so path updates cannot poison factory defaults.
[2.0.5] — 2026-08-12
Panel manager defaults / reset / chart overlay; classic Status + System Logs chrome; editor open-in-new-tab.
Added
- Panel manager — per-panel default position (dock + size + float coords in
PANEL_META); panel menu Reset to default. - Chart overlay — per-panel toggle and bulk Chart overlay: all on/off (
setAllPanelsChartOverlay); edge-docked panels float over the chart without shrinking it. - Editor — Open in new tab in overflow panel options (and left dock menu).
Changed
- Status bar — restored classic fixed footer look (no panel title chrome).
- System Logs — restored classic collapsible strip (expand/collapse body); topbar shows/hides the strip. Label System Logs (not bare “Logs”).
[2.0.4] — 2026-08-11
Script Logs / System Logs naming and Status / System Logs topbar toggles.
Changed
- Script Logs / System Logs — Script
log.*pane titled Script Logs (editor tool + panel); app telemetry titled System Logs. - Status & System Logs — topbar toggles for Script Logs, System Logs, Status.
[2.0.3] — 2026-08-11
Runtimes hub, editor save-before-run / idle lint, Workers Manager reliability, and hardened VPS probe routing.
Fixed
- Workers Manager endless probing — open-effect no longer tracks
store.endpoint(backend changes re-fired probes); every probe gets a hard timeout merged with the modal abort; Pyodide probe uses HEAD instead of downloading fullpyodide.js;probeAllWorkersusesallSettled. - Workers Manager / hardened VPS — pyne Pro probes use same-origin
https://axis.hoox.sh/health(nginx → loopback :5002) instead of client127.0.0.1:5002, which UFW correctly blocks on the public VPS.
Changed
- Runtimes hub — single dialog for Status (Workers Manager) and Plugins (catalog / install / library); topbar Runtimes entry; cross-links between sections; models remain separate.
- Save before Run — clicking Run (editor header, topbar split, Mod-Enter, command palette) auto-saves the active script to the library when the tab is dirty or not yet bound (
libraryId). Save failure blocks the run. - Idle lint — editor underlines (e.g.
plt()→ unknown) reappear after 2s without typing, not only after Save/Run. Mid-keystroke marks stay cleared; bare call typos likeplt()flagged locally. - Workers Manager UX — distinct icon per catalog worker; Usage / When to use copy on Detail + Install; docs updated.
- Editor line numbers — gutter width follows digit count only (no fixed min-width / heavy padding).
[2.0.2] — 2026-08-11
Editor chrome polish and docked panel hover slide.
Added
- Panel hover slide — docked panels can collapse to a peek strip and slide open/closed on pointer enter/leave. Toggle Slide on hover in the panel hamburger menu (left/right/bottom only). Store APIs:
setPanelHoverSlide,togglePanelHoverSlide,isPanelHoverSlide. Preference persists onpanelChrome[id].hoverSlide.
Changed
- Editor chrome polish — header tools always show icon + label (no hover slide-in); Format removed from the editor header and tab strip (still available via overflow Format document and
Shift+Alt+F/Mod+Shift+F). - Library from editor — primary Library tool opens the script library panel; overflow menu also has Open library.
Fixed
- Tab close / switch — closing a tab left of the active one no longer jumps focus; CM buffer is snapshotted before add/close/switch;
setDocno-ops on identical content.
[2.0.1] — 2026-08-11
Security and performance release from the multi-agent harden-perf audit
(4 specialist scanners + synthesis). Report: docs/devops/harden-perf-audit-2026-08-11.md.
Security
- Fail-closed Worker auth when D1 is bound without
API_KEYSKV (API_KEYS_REQUIRED). - CORS: product hosts + project-scoped
*.pynescript-axis.pages.devonly (no open*.pages.dev). /api/run: auth whenAPI_KEYSorREQUIRE_RUN_AUTH; per-IP/key rate limit (30/min); script/bars size caps; upstream proxy timeout.- Git OAuth: env
GITHUB_OAUTH_CLIENT_ID/GITLAB_OAUTH_CLIENT_IDwins over bodyclientId; tighter start/poll rate limits. - Watchlist: build rows with
textContent(no unescaped symbolinnerHTML).
Performance & reliability
- bars-cache: memory-first
getCachedBars, IDB hydrate,getCachedBarCount/ range without full clone when warm. - DSM gap-fill progress uses
getCachedBarCountinstead of full series loads. - engine-ws: 45s dead cool-down — keep dead client so live re-runs do not thrash reconnects.
Commits
0a668e00— fix(security,perf): gate /api/run, OAuth clientId, WS cool-down156221b9— fix(security,perf): fail-closed Worker auth, CORS, bars-cache
Full history (recursive)
2026-11 (79 commits)
Features
afb8de3a(2026-11-04) — feat(live): anchor the window at TradingView® period boundaries3a23406b(2026-11-04) — feat(live): anchor chart window and chunk engine depthdabf463e(2026-11-04) — feat(studio): smart input system v2 with Storybook examplesda76b50d(2026-11-04) — feat(watchlist): daily-close Prev, big price view, alert modal polishb321a2fd(2026-11-04) — feat(scripts): plot line type + display targets; fix width stepper5d53ded7(2026-11-03) — feat(chart): footprint overlay (A1.3.2) with estimate-aware cells69c788d3(2026-11-03) — feat(chart): footprint model, OHLCV approx, and binary row pack37246800(2026-11-03) — feat(chart): multi-grid 6/8 with sibling hydrate and live mux020f8c02(2026-11-03) — feat(theme): brand void/volt/signal packs, quiet borders, builtin candles32b85e01(2026-11-03) — feat(ui): polish Studio Settings tabs and harden shortcut recorderf7910387(2026-11-03) — feat(chart): generation readiness, live stream route, panel hairlines0bb2330c(2026-11-02) — feat(vscode-axis-ide): polish AXIS panels and Copilot tool wiringabc5d27d(2026-11-02) — feat(vscode-axis-ide): profiler gutter, debug chips, colors symbols logs, drop HPO0e7c9e98(2026-11-02) — feat(vscode-axis-ide): sidebar studios for the six activity viewsbc2a0b0d(2026-11-02) — feat(ui): open PYNE Agent from the module-bar AI button82c4c5fd(2026-11-02) — feat(vscode-axis-ide): view polish, icons, micro-feedback, profiler01ff41af(2026-11-02) — feat(vscode-axis-ide): PYNE icon, view context menus, on-save run796f2d04(2026-11-02) — feat(vscode-axis-ide): polish Plugins studio, status menu, chart HUD196d93b5(2026-11-02) — feat(ui): color the data-window close against the previous bar93966632(2026-11-02) — feat(ui): show the last price on the mobile header57f664a1(2026-11-02) — feat(ui): show an on-chart legend for visible on-chain series28ad1121(2026-11-02) — feat(vscode-axis-ide): Plugins studio WebviewView with kind cards3894be8d(2026-11-02) — feat(ui): show the editor shortcut on its topbar title0cf7b54f(2026-11-02) — feat(ui): mark alert rows armed, recently fired, or disabledda119ab5(2026-11-02) — feat(ui): show the chart grid as a glyph and name the matching layout617ee585(2026-11-02) — feat(ui): draw a session sparkline on watchlist rowsb64b6f53(2026-11-02) — feat(ui): remember symbol recents and favorites2cc6d85f(2026-11-02) — feat(vscode-axis-ide): per-kind plugin views and HOOX activity iconbfab4397(2026-11-02) — feat(vscode-axis-ide): MCP/LM tools, pack, CI, NOTICE, Pyodide flag (Tasks 21–27)546431fe(2026-11-02) — feat(vscode-axis-ide): MVP drawings, alerts, and watchlist quote (PR-08b/11)496b9adf(2026-11-02) — feat(vscode-axis-ide): builtins, live tickets, delta resume, plugins (PR-09/10)2d3554e6(2026-11-02) — feat(vscode-axis-ide): Results/Strategy panels + library TreeViews (PR-07/08)0f1c2947(2026-11-02) — feat(vscode-axis-ide): HistoryClient + seriesKey datafeed fetch (PR-06b)260cab8a(2026-11-02) — feat(vscode-axis-ide): RunOrchestrator + runScript on mock Pro /run (PR-06)b907f99f(2026-11-02) — feat(plugin): ship PYNE Agent 0.1.8 + sync install scripta209ba8d(2026-11-02) — feat(vscode-axis-ide): Pro /run adapter, chart bus, results focus (PR-06)93b5d393(2026-11-02) — feat(vscode-axis-ide): mock OHLCV chart.full paint + sibling openChart (PR-05)5baf7b2f(2026-11-02) — feat(vscode-axis-ide): axis.chart CustomEditorProvider + CSP webview (PR-04)63304c23(2026-11-02) — feat(vscode-axis-ide): SecretStorage for Worker/Pro API keys (PR-03)d1f246ee(2026-11-02) — feat(vscode-axis-ide): add axis.webview.v1 protocol and coverage ratchet (PR-02)461dbed1(2026-11-02) — feat(vscode-axis-ide): scaffold AXIS IDE extension package (PR-01)ff8f8155(2026-11-01) — feat(perf): performance audit Phases 4–5 on developmente9f8d6d9(2026-11-01) — feat(perf): performance audit Phases 0–3 on development
Fixes
f99769b6(2026-11-04) — fix(engines): keep known-good capabilities when /health fails9b7afec9(2026-11-04) — fix(ui): fire StudioInput stepper callbacks once7bcd8781(2026-11-04) — fix(indicators): invalidate the run cache on live appendsbd2d2c81(2026-11-04) — fix(watchlist): invalidate daily closes on source change53fdd063(2026-11-04) — fix(chart): key the footprint layout cache on OHLCV367afd6f(2026-11-04) — fix(indicators): clear stale declared plot meta on re-runb5425cda(2026-11-04) — fix(drawings): convert polyline xloc.bar_index through the run axis51af8bd6(2026-11-04) — fix(chart): respect hidden plots in overlay display/style setters900d8aba(2026-11-04) — fix(ui): apply plot display overrides in the Data Window03fe4657(2026-11-04) — fix(chart): scope the live window plan per chart slotd2d426be(2026-11-04) — fix(store): pin the at-cap window reset policy2251637d(2026-11-04) — fix(store): anchor the live window from untruncated history838ec465(2026-11-04) — fix(data): break data-manager-source ↔ store import cycle518996a1(2026-11-03) — fix(strategy): anchor bar_index to the run window (stable axis)1774feef(2026-11-03) — fix(data): cold-start DSM when Data Manager cache is emptybdb4c1d8(2026-11-02) — fix(vscode-axis-ide): drop double pane borders in webviews2413b647(2026-11-02) — fix(vscode-axis-ide): hide TV logo, open chart beside, paint plots59d1752d(2026-11-02) — fix(ui): take drawing-tool titles from the shortcut registry5c2cb035(2026-11-02) — fix(vscode-axis-ide): address finish-review live, MCP, CSP, coverage bugs99d59e04(2026-11-02) — fix(engine): stop /ws/live inbox from storming full snapshots3d26e366(2026-11-02) — fix(pyodide): vendor numpy wheel and stop SPA HTML for missing assets76ab605e(2026-11-01) — fix(indicators): keep catalog SMA adds after live epoch bumps2c11bab5(2026-11-01) — fix(data/stream): heal live chart holes and rejoin released overlayse0a6ed44(2026-11-01) — fix(indicators): never supersede catalog adds on live ticksee2f4f64(2026-11-01) — fix(chart): keep overlay SMA/Donchian aligned on live ticks
Documentation
4edddbcd(2026-11-04) — docs(changelog): review remediation for 1774feef..afb8de3a82e87c02(2026-11-02) — docs(changelog): note drawing chords, on-chain legend, mobile price, and close tonefaf8c884(2026-11-02) — docs(changelog): note the layout, alert, equity, and editor polish482c9c98(2026-11-02) — docs(changelog): note Results/Strategy panels and library TreeViews
Chores
a5c9c4b5(2026-11-04) — chore: extract AXIS IDE to hoox-sh/axis-ide-vscec16dbacd(2026-11-01) — chore(deps): pyodide PYNE wheel 0.6.29 + builtins syncd6a3f09d(2026-11-01) — chore(release): AXIS 2.27.0
Style
a6b9f6af(2026-11-02) — style(ui): align the equity curve with market color tokens7ec70034(2026-11-02) — style(ui): calm shell hairlines and semantic market colors
Other
deea38b1(2026-11-03) — merge(feat/vscode-axis-ide-pr01): land AXIS VS Code IDE on maind627bee7(2026-11-01) — merge: development into main
2026-10 (399 commits)
Features
9fc67af4(2026-10-31) — feat(editor): Pine symbol navigation (definition, references, rename, highlight)c5e67961(2026-10-31) — feat(editor): PYNE 0.6.28 wheel + offline W002/W104 lint parity319d3399(2026-10-31) — feat(editor): refresh Pine LSP/hover against PYNE 0.6.25ed8992a3(2026-10-31) — feat(mcp): add tab switch for multi-tab MCP sessions8ca2ca2a(2026-10-27) — feat(chart): mark previous tip closed when a new bar arrives2499d6bb(2026-10-26) — feat(mcp): expose rotating axis.debug.log via logs.debug.get22c10e48(2026-10-26) — feat(engine): expose queueWait and trim live apply hot path82e6a595(2026-10-26) — feat(chart): paint inactive slots without stealing getManager9517fe49(2026-10-26) — feat(mcp): put transport and layout on chart.lag v174bb1ef0(2026-10-26) — feat(chart): resize single-layout panes with LWC enableResizeaa9163d7(2026-10-26) — feat(engine): mint a Worker ticket instead of api_key on /ws/live5f45eb4c(2026-10-26) — feat(debug): log chart and engine actions behind VITE_AXIS_DEBUGbe4bfd55(2026-10-26) — feat(engine): commit live price, plots, markers, and alerts in one batch092b9dd6(2026-10-25) — feat(chart): pin each ChartHost slot onto the engine socket pool2ea224b8(2026-10-25) — feat(engine): share one socket per endpoint across chart slots41acd12e(2026-10-25) — feat(engine): cap tab engine sockets at two26427e47(2026-10-25) — feat(engine): default to the worker and one chartb0590389(2026-10-25) — feat(engine): flag the v1 worker, apply, and single-chart paths35a49f11(2026-10-25) — feat(engine): record v1 tick-to-apply and tick-to-paintc10c1399(2026-10-24) — feat(dev): add HP development tracer capability (hp2 tracer)816c976e(2026-10-24) — feat(hp): engage WebGL2 overlay on HP-3 selection (D1)b124fc5c(2026-10-24) — feat(hp): causal pairing for publish-to-apply stage6f76ca8a(2026-10-24) — feat(hp): calc session runs in a worker off cold history6470db21(2026-10-24) — feat(hp): AXB1 fuzz round-trip and relay reconnect proof1940e8c8(2026-10-24) — feat(hp): worker-side cold store with bar-close write-back2848aee9(2026-10-24) — feat(hp): split tick-to-frame latency into pipeline stagesb5849514(2026-10-23) — feat(hp): downsample cold viewports wider than hot+page3e8722ff(2026-10-23) — feat(hp): wire cold paging through the page slot6f32870e(2026-10-23) — feat(settings): expose the HP engine flags in Settings9620bbee(2026-10-23) — feat(hp): measure tick-to-frame latency for the PR-10 gatea1aef9cb(2026-10-23) — feat(chart): composite the HP GPU overlay into chart screenshots92bb7d6e(2026-10-23) — feat(mcp): expose the HP plane as read-only capabilitiesb1c33936(2026-10-23) — feat(hp): wire the WebGL2 candle overlay over the price pane (HP-3 parity mode)51d21023(2026-10-23) — feat(hp): wire the ingest hot SAB ring into the live chart (HP-2)ab3d45cf(2026-10-23) — feat(hp): flip COOP to same-origin — cross-origin isolation on8393e866(2026-10-22) — feat(hp): GPU rung — NDC visual-attr staging + WebGL2 renderer (PR 8)b634e656(2026-10-22) — feat(hp): calc worker scratch vs commit (PR 6)a569f233(2026-10-22) — feat(hp): viewport page + cold IDB (PR 5)7d151c98(2026-10-22) — feat(hp): generation double-buffer + main rAF copy-out (PR 4)63204c73(2026-10-22) — feat(hp): AXB1 codec, isolation headers, and off-thread ingest (PR 1-3)ab974f14(2026-10-20) — feat(data): flush the engine's own copy of the venue data10b88f78(2026-10-19) — feat(ci): GitHub Release notes from CHANGELOG version section6efb6967(2026-10-19) — feat(chart): topbar market select for spot vs USDT-M / COIN-M0984b889(2026-10-17) — feat(mcp): live-plane capabilities — chart.lag/bars, engine., stream.5be393e1(2026-10-16) — feat(engine): three-tier close verification with strength surfacing (F-28)490051fe(2026-10-16) — feat(engine): engine-owned stream data plane (F-01–F-20)035d9659(2026-10-16) — feat(maintenance): build-time maintenance mode page (VITE_MAINTENANCE_MODE)6427387c(2026-10-16) — feat(engine): delta envelopes for session ticks with merge + fallback7684ff8d(2026-10-14) — feat(engine): PYNE endpoint parity — health, prewarm, batch, sessions, Pro, runnerdc5402b4(2026-10-13) — feat(engine): wire every-tick Pro mode through runner, chart, and live ticks4091d4cf(2026-10-13) — feat(theme): sync community presets to OpenCode palettes1f5b299e(2026-10-13) — feat(engine): skip local OHLCV persistence in engine-owned live moded6a1f701(2026-10-13) — feat(engine): engine-owned data sessions and capability-driven live modes49e1f7be(2026-10-13) — feat(theme): vendor 24 community chart presets (hoox-themes)db0ec11e(2026-10-13) — feat(engine): O(delta) live ticks via PYNE live sessions160df5c3(2026-10-13) — feat(data): dataset depth vs chart window split, reload page walk, interactive status bar6a2ec9e5(2026-10-13) — feat(data): rolling window for engine input, and dataset size in the status strip16ec9fc4(2026-10-12) — feat(cli): axis setup artifacts for git-native script storagedc2f1eb1(2026-10-12) — feat(worker): git-native script storage on Cloudflare® Artifacts1bd573b4(2026-10-12) — feat(storybook): button design explorations, shapes, microfeedback8b587834(2026-10-10) — feat(release): AXIS v2.22.0 — chart settings panel, review fix batch, hardening18d589fc(2026-10-09) — feat(edge): live tenant verify + usage flushcc1e1b34(2026-10-09) — feat(entitlements): tenant scope types + bearer classification (no enforcement)fa15352b(2026-10-08) — feat(chart): pine tables in bottom panel (September 2026 parity)5ab80821(2026-10-06) — feat(ui): ticker band controls, drag handle, and menu polish3dbeacfb(2026-10-06) — feat(ui): smooth clean snappy panel open102d7542(2026-10-05) — feat(extras): float-only time + price panels under topbaraa66af89(2026-10-04) — feat(watchlist,extras): big-price rows + ticker direction/change options1d63aabd(2026-10-04) — feat(extras): polish Extra menu + harden quote sheet92fdb90b(2026-10-03) — feat(extras): Quote dock panel with full quote sheet5c5f0b51(2026-10-03) — feat(extras): fullscreen alert overlay + stylesea7139be(2026-10-03) — feat(extras): watchlist ticker marquee bandf04c1d20(2026-10-03) — feat(extras): current-price trend card overlay095576c0(2026-10-03) — feat(extras): Extra menu + module-bar button after Resultse4dd2867(2026-10-03) — feat(alerts): subscribeFiredAlerts broadcast for UI overlayse8ede505(2026-10-03) — feat(extras): trendOverTicks helper for price-card arrow704e770d(2026-10-03) — feat(extras): persisted extras slice + panelsExtra topbar flagf3d2133d(2026-10-02) — feat(licensing): add Commercial (EUR 349) + Pro SaaS plans
Fixes
3c929f2a(2026-10-31) — fix(cli): align default PYNE URL test and bump CLI to 0.3.69355de14(2026-10-31) — fix(shortcuts): require Alt+Shift for help, not bare Shiftc7218f32(2026-10-31) — fix(mcp): restore ticket minting on GET ?issue=ticketa67325ee(2026-10-31) — fix(mcp): route /api/mcp/bridge sub-paths to bridge DO24e20b61(2026-10-31) — fix(mcp): route /api/mcp/bridge sub-paths to bridge DO49e59814(2026-10-31) — fix(engine): extend indicator series on new live bar instead of shifting right880655f2(2026-10-31) — fix(engine): preserve venue OHLC when engine updates forming candleb25213e7(2026-10-31) — fix(engine): persist live-tick OHLC to store.bars via setStore698abf30(2026-10-31) — fix(engine): drop-only guard before LWC setData to prevent Value is null68d91341(2026-10-31) — fix(engine): remove sanitizeSeriesDataForSetData, donor fill, and OHLC clampingbf1c74bc(2026-10-31) — fix(engine): merge engine bars instead of full-window replace in engine-owned live modee6e494b5(2026-10-27) — fix(store): preserve forming candle OHLC on degenerate single-price live ticks0b95333d(2026-10-27) — fix(engine,store): point pyne endpoint to pynescript.online; cap rolling window <5000 barsdf7b6edd(2026-10-27) — fix(chart): log raw venue OHLC before repairOhlc, reduce repairOhlc noise7b6e82ff(2026-10-27) — fix(chart): repair only missing OHLC fields in sanitizeSeriesDataForSetData0408b414(2026-10-27) — fix(chart): add comprehensive debug logging to candle paint path7bf67689(2026-10-27) — fix(chart): stop run superseded when adding second indicatord3158b5b(2026-10-27) — fix(chart): lower default rolling window to 5,000 bars for free tierb702518c(2026-10-27) — fix(chart): re-apply time scale options on live ticks to disable conflationeb13eb5e(2026-10-27) — fix(chart): disable conflation, auto-scroll on new bar13a7ad08(2026-10-27) — fix(chart): log full OHLC on live.append debug entries93cdf048(2026-10-26) — fix(chart): stop view reset on new candle and preserve live candle openfbd451eb(2026-10-26) — fix(chart): clamp OHLC on live candle paint to stop shredded candles37b6115d(2026-10-26) — fix(engine): stop series.full setData storm from gap resync7b1650a9(2026-10-26) — fix(engine): keep script live after input-change resubscribea382e98f(2026-10-26) — fix(stream): venue owns live candles; stop engine setData gapsca49c08a(2026-10-26) — fix(ui): pane-manager refresh badges on layout change457809f3(2026-10-26) — fix(engine): session-broker guard, drawing-layer parity guard, client-apply replay context8cda212b(2026-10-26) — fix(streams): re-eval idle/released scripts on live start6fe6604b(2026-10-26) — fix(engine): stop live-wire delta-gap thrash blanking indicatorsd0461f50(2026-10-26) — fix(engine): paint live overlays when engine series outgrows charte2ed87c0(2026-10-26) — fix(engine): soft-authBlock UNAUTHORIZED on JSON live WS too7e134626(2026-10-26) — fix(streams): retry degraded websocket scripts with backofff0327488(2026-10-26) — fix(engine): retry auth-race subscribe and clear reconnect cooldownb93e4390(2026-10-26) — fix(engine): do not authBlock ingest on PYNE UNAUTHORIZED93bd38f2(2026-10-26) — fix(engine): cut tick→paint p95 from hidden stalls and slide setDataece068a1(2026-10-26) — fix(engine): close same-slot queueWait synchronouslyafbbafbe(2026-10-26) — fix(engine): hit tip-cache when stream-delta lacks tip stampacd6c18e(2026-10-26) — fix(engine): cut residual droppedTip and pan queueWait6c08a562(2026-10-26) — fix(engine): throttle series→script kick to cut queueWait p50abaf4a3c(2026-10-26) — fix(hp): honor JSON encoding omit and decode misaligned AXB14df26790(2026-10-26) — fix(engine): close queueWait after series-only live tipse74a860e(2026-10-26) — fix(chart): arm panFrame sampler on single-chart layout281f9660(2026-10-26) — fix(engine): keep v1 latency p95 on a recent windowe8e48c17(2026-10-26) — fix(engine): buffer live auth until upstream open1203c6f7(2026-10-26) — fix(engine): release tips sooner under coalesce and send-buffer load05cecf1d(2026-10-26) — fix(engine): wait for auth.ok before ingest host opena0d53d3a(2026-10-26) — fix(streams): re-pin ChartHost slots after streamStopcc6fe955(2026-10-25) — fix(streams): re-pin ChartHost slots after stream.resete0a0f70d(2026-10-25) — fix(chart): keep forming ticks out of the closed-bar hash00eb0e2a(2026-10-25) — fix(chart): warn when a pane logical range skews the price axis1ef93781(2026-10-25) — fix(chart): warn when a plot column is short of the axisa14a2805(2026-10-25) — fix(chart): wire one crosshair listener on the single chart03a2d6a3(2026-10-25) — fix(chart): warn when a series runs past the price tip322c633d(2026-10-25) — fix(hp): decode Blob inbound frames like ArrayBuffer3b8f2672(2026-10-25) — fix(chart): commit strategy and plotshape markers with plots5eb5a2a7(2026-10-25) — fix(engine): hold a price seq gap until the next snapshote8e6d00f(2026-10-25) — fix(engine): paint the first script snapshot on the live pumpe36861ef(2026-10-25) — fix(engine): paint a stream snapshot as a full indicator327aba66(2026-10-25) — fix(engine): type the script paint chain as Promise97f382da(2026-10-25) — fix(engine): do not paint a late forming tip over a newer snapshotfddaad8d(2026-10-25) — fix(engine): stay on the socket when replay is refused7ef6980e(2026-10-25) — fix(engine): resync when the send buffer or the inbox is full096a8e5f(2026-10-25) — fix(chart): keep pane badges on the shared plot cella628f144(2026-10-25) — fix(engine): keep script runs on the WebSocket49d7fc91(2026-10-25) — fix(engine): cap the live inbox and drop extra forming tipsbe7acb58(2026-10-25) — fix(ui): keep the quote on the candle while the stream is down95fad86b(2026-10-25) — fix(chart): do not mirror range or crosshair on one chart6e5aa6bc(2026-10-25) — fix(engine): hold deltas after a seq gap until the snapshot6b27f3fb(2026-10-25) — fix(engine): drop a snapshot older than the applied seqed4fb1e8(2026-10-25) — fix(chart): warn when a closed bar changes after commit85ef0a91(2026-10-25) — fix(chart): warn when a plot runs past the price axis3afedadd(2026-10-25) — fix(chart): paint one live burst and name a quiet stream89a31ed9(2026-10-25) — fix(engine): jitter socket reconnect and stop a rejected login08878da5(2026-10-25) — fix(engine): mark a bar closed only on a close8b9bf555(2026-10-25) — fix(alerts): evaluate only a closed bar6aee416b(2026-10-25) — fix(engine): take live bar time from the server74df56ee(2026-10-25) — fix(chart): restore the visible range after a live setData87dcbe68(2026-10-25) — fix(engine): narrow an unknown AXB1 frame before the opcode switch2306ed5d(2026-10-25) — fix(engine): retry a missed snapshot, then ask in JSON7dc0053d(2026-10-25) — fix(engine): do not replay ticks missed while hidden531a49be(2026-10-25) — fix(engine): replace a dead ingest workerf62d728a(2026-10-25) — fix(chart): import Bar from the store typesd5f49ff5(2026-10-25) — fix(engine): hold the live plot on the tip and ack folded seqsa3c38dd0(2026-10-25) — fix(engine): repair a series whose tip is fresh but holedd31c424c(2026-10-25) — fix(engine): read tip seq only on frames that carry onef7cdb71f(2026-10-25) — fix: green the suite — tail-align head-pad, viewport fill hang, ColdStore test21ab0d09(2026-10-24) — fix(hp): audit A01/A04/A05/A06/A10/A23/P01 + changelog + testscfa20e61(2026-10-24) — fix(hp): normalize worker clock origin in publish-to-applyefad345b(2026-10-24) — fix(hp): skip tab-suspended bursts in paint latency068972e5(2026-10-24) — fix(hp): last ingest-publish stamp wins per paint burst41647761(2026-10-24) — fix(engine): invalidate sessions on series repair32a5224b(2026-10-24) — fix(chart): skip axis-less session applies on gross window mismatchce6a0c35(2026-10-24) — fix(stream): stale axis-less pushes skip paint and resubscribedb657d98(2026-10-23) — fix(engine): reconcile IngestHostClient with the wire-bookkeeping APIfd85003e(2026-10-23) — fix(hp): report WebGPU as selected, without a painter749f8696(2026-10-23) — fix(hp): draw LWC's own candle widths in the GPU overlayfc1b65db(2026-10-23) — fix(hp): anchor the GPU overlay to LWC's exact index-to-coordinatef8e086ca(2026-10-23) — fix(hp): place the hot ring as the chart tail window in the GPU overlaya4b31a77(2026-10-23) — fix(hp): allocate the ingest hot ring at construction0fd68177(2026-10-22) — fix(worker): relay live WS binary frames untouched (PR 7b)387f42d5(2026-10-22) — fix(test): stop a leaked DSM walk from failing unrelated fetch mocks4d417b81(2026-10-21) — fix(storage): declare backend capabilities instead of probing for them33a278bd(2026-10-21) — fix(app): drop a malformed live bar instead of painting it40eb507b(2026-10-21) — fix(app): bound the module-level maps that grew per key92db96c4(2026-10-21) — fix(worker): cap every route that accepts a body215c88e1(2026-10-21) — fix(live): keep live indicators aligned with the candles533c2af8(2026-10-21) — fix(chart): new candle advances chart; strategy events persist on every tick69e9cb13(2026-10-21) — fix(fill): resolve plot bands from plot_meta; skip redundant reapplies6d8ae15f(2026-10-21) — fix(plots): preserve stored script colors on chart when meta.color missing81013fd8(2026-10-21) — fix(indicators): single reapply pass, preserve script colors on re-rune89b9afb(2026-10-21) — fix(plots): respect Pine meta.color in legacy plots[] overlay pathe4e303bf(2026-10-21) — fix(indicators): default linewidth to 1 instead of 2/auto5291b80d(2026-10-21) — fix(engine): preserve plot_meta in session delta mergeseed5e248(2026-10-21) — fix(indicators): preserve plot width/visible on re-runbd8137dd(2026-10-21) — fix(indicators): replace width slider with +/- buttons, show 'auto' when not explicitly setd8e53c61(2026-10-21) — fix(indicators): color picker stays open, add line width slider and plot visibility toggle92d3956d(2026-10-21) — fix(plots): only apply custom user colors, respect Pine script colors on re-run49ffe663(2026-10-20) — fix(engine): count Donchian from the plots, not the requestd18fb622(2026-10-20) — fix(engine): keep Donchian moving after a reload2b94b3e2(2026-10-20) — fix(chart): keep candles visible when a load meets a zoom2286b879(2026-10-20) — fix(engine): keep closed Bollinger %B on its own bar2aa7bdb1(2026-10-20) — fix(chart): keep the indicator axis on one row per candlecdb9f5d2(2026-10-20) — fix(data): keep a flush from rewriting the wrong seriese0ef21a8(2026-10-20) — fix(data): a remote dataset document that is already gone is not a failured663bc6d(2026-10-20) — fix(engine): keep the script's input descriptors across a live applyf70ea949(2026-10-20) — fix(test): drain the dataset writes before resetting the store85bc8547(2026-10-20) — fix(test): drop a dataset write that was queued before a reset453258f9(2026-10-20) — fix(stream): subscribe scripts with the chart window and their inputscf66630c(2026-10-20) — fix(chart): keep every painted series on the candle axis7fe4de9e(2026-10-20) — fix(test): reset the shared singletons the suite leaks between filesa6592d2a(2026-10-20) — fix(data): drop a dataset repaint that would paint over a moved-on chart366bb039(2026-10-20) — fix(test): restore the shared store after the chart-window test files2fb50d41(2026-10-20) — fix(chart): re-join cross-indicator series by time, and close the apply gapse08cf3c8(2026-10-20) — fix(chart): keep the apply path aligned with the sliding chart window54d52efe(2026-10-20) — fix(data): harden the dataset manager, the chart window and the live bar path4fb146c1(2026-10-19) — fix(test): give the mock-walk reload test room on a loaded runnerce6c8a67(2026-10-19) — fix(ci): the desktop release pipeline must not fail on the changelog86cd2c1c(2026-10-19) — fix(ui): the feed chip says what the data path is, not a stale state worda2a4efbd(2026-10-19) — fix(test): pin the engine identity in the live-plane wire test6a94ca03(2026-10-19) — fix(test): reset the painted-candle range before each clamp test36bc73df(2026-10-19) — fix(ci): serialize the coverage test runbcc64534(2026-10-19) — fix(pyodide): compile mode no longer crashes on alert() / alertcondition()c858620c(2026-10-19) — fix(engine): mark /ws/run dead after an abnormal close4c6f398e(2026-10-19) — fix(data): fill internal 1m holes; cap /ws/live snapshot to 500 barsc3bd51fa(2026-10-19) — fix(data): fill stale cache→now before live on interval switch6cd2b88c(2026-10-19) — fix(streams): keep /ws/live connectable after snapshot misses18198258(2026-10-19) — fix(streams): keep /ws/live up when a script snapshot misses200e27f5(2026-10-19) — fix(streams): 120s snapshot wait after datafeed-loading; tail-align fills1f20aad8(2026-10-19) — fix(ui): pass streamId when restarting live after a market switch8387a36d(2026-10-19) — fix(ui): MCP chip on the command bar; overlay plots above pricec2dd8746(2026-10-18) — fix(streams): handshake /ws/live with api_key instead of blocking on auth.ok5c713b67(2026-10-18) — fix(storage): keep Worker and engine API keys across restarts950957d2(2026-10-18) — fix(streams): wait for auth.ok and cap live subscribe historya89e66e9(2026-10-18) — fix(streams): pair-form REST keys and pause live-wire reconnects51b290b1(2026-10-18) — fix(streams): live P3 — no engine drive on venue ticks8b169927(2026-10-18) — fix(streams): live P1 — series/study push apply and reconnect resumef3826c4b(2026-10-18) — fix(streams): live P0 — venue-first paint, subscribe-once, pair-form keysfa885c34(2026-10-17) — fix(streams): append venue forming bar after engine snapshot in stream mode846ab06d(2026-10-17) — fix(streams): keep the venue's forming tip across engine-owned repaints020f2c12(2026-10-17) — fix(engine): drive the engine's series from the stream plane158dd86a(2026-10-17) — fix(engine): ignore a superseded engine socket's events and frames675364d4(2026-10-17) — fix(chart): prune co-painted series on a moved window and paint new engine windows3bf910e1(2026-10-17) — fix(engines): repair a stale engine series instead of back-dating the chart6008eb54(2026-10-17) — fix(engine): authenticate the live socket and accept flat engine error framesec33bdc0(2026-10-17) — fix(chart): close the pre-paint hole in the co-painted candle-range clamp0b419b89(2026-10-17) — fix(live): hoist appendVenueBars above stream-route early return (TDZ crash)4e342e91(2026-10-16) — fix(engine): honor every-tick indicator re-runs on heavy histories454c1260(2026-10-16) — fix(engine): engine-owned live throttle no longer fires every tick1e4c9848(2026-10-16) — fix(engine): stop per-tick full-window tick+download in engine-owned lived77772ba(2026-10-16) — fix(data): engine-owned load flips status to ready and reapplies scripts19f956b1(2026-10-16) — fix(engine): end add-indicator lag, time-join session series, per-chart clamp73b44ae4(2026-10-15) — fix(chart): clamp co-painted series to the candle time range58a4459a(2026-10-15) — fix(engine): persist Pro API engine key across reloads1b28d5e4(2026-10-15) — fix(engine): unblock engine-owned data plane and honor free-tier capscfa7daed(2026-10-15) — fix(engine): live-mode regressions — frozen overlays and reset re-run settingde198e3b(2026-10-14) — fix(workers): carry compile readiness through the probe result; use the rest of /healthf4925103(2026-10-13) — fix(engine): harden O(delta) live sessions and engage them under auto8384ab86(2026-10-13) — fix(engine): one live session per script instance, rebase on rewritten history91000a9c(2026-10-13) — fix(chart): repair setData payloads — LWC asserts at render, not at setDataaa39ca1f(2026-10-13) — fix(engine): session eligibility recorded up front, not via live array identity81ccb8ad(2026-10-13) — fix(engine): forward liveTick for chart-bars runs so sessions engage083d3749(2026-10-13) — fix(ci): pin Bun, and enforce the coverage gate at its measured floor1d2036a1(2026-10-13) — fix(ci): install worker dependencies before the root test run7a12cabe(2026-10-13) — fix(health): probe upstreams with HEAD, not GETc4ccfce9(2026-10-13) — fix(worker): repair dead market + on-chain proxies and get CI green8b1b07cc(2026-10-11) — fix(chart): sanitize setData payloads, per-series failure contextse6e32fd0(2026-10-11) — fix(chart): fib circles/arcs X/Y radii, fills relocation, n-point drag75f580b5(2026-10-09) — fix(edge): run waitUntil ctx, hashed rate keys, scope fail-closed2f892a42(2026-10-06) — fix(ui): seamless ticker loop and calmer price band2969fef5(2026-10-05) — fix(tests): isolate cloud-config and cache keys across suitesd7d2ed17(2026-10-05) — fix(build): add vite-env.d.ts for vite/client types60f1fe56(2026-10-05) — fix(tests): de-flake snapshot, menu and slot-switch tests5c8ce723(2026-10-05) — fix(datafeed): plug WS close-leak, version from package.json34c65617(2026-10-05) — fix(extras): live-tick trend, countdown prefix, ticker under topbardac8a48d(2026-10-04) — fix(tests): derive panel-icon count from PANEL_IDS1977e11b(2026-10-04) — fix(extras): harden extra chrome widgets after reviewed39d933(2026-10-03) — fix(extras): align price card right, clear of price scaleec1c4994(2026-10-03) — fix(extras): ticker auto-seeds watchlist symbols, single mux lifecycle85358351(2026-10-03) — fix(live): keep every tick in background tabs + DSM auto gap repair54ae5acc(2026-10-03) — fix(chart): harden engine overlays, HA cache, paint and lifecycle
Performance
b261206c(2026-10-31) — perf(engine): skip re-sanitizing already-clean bars in appendBar patha25c5eb2(2026-10-26) — perf(chart): tip-only paints while ranging for G2 panFrame463b2ba5(2026-10-26) — perf(engine): tip-cache live apply to cut G1 apply workee206d4f(2026-10-26) — perf(chart): defer slide setData while ranging for G2 panFrame1d3edb04(2026-10-26) — perf(chart): defer rolling-window slide setData to one rAFe871173a(2026-10-25) — perf(store): notify Solid once per frame for a forming tick33cc4269(2026-10-24) — perf(hp): P03 single-pass fill, P07 overlay cache, P09 replay watermark, P13 encoder SPDX-License-Identifier: AGPL-3.0-only0d7cfb1e(2026-10-20) — perf(store): copy the sliding window once, and keep the benches honest0d0ffd1c(2026-10-13) — perf(engine): run Pyodide in a dedicated Web Worker5ed34ef1(2026-10-05) — perf(scripts): prefer Bun-native file, hash, spawn and image APIs
Refactors
82b019c8(2026-10-31) — refactor(engine): retire multi-chart layout, strip v1 hot-path samplingeae0462f(2026-10-26) — refactor(chart): split price print path and unfreeze short live overlays73bf272f(2026-10-25) — refactor(chart): remove the HP overlay and shared-memory ring3ea34fc0(2026-10-23) — refactor(app): last hand-rolled LRU trims use BoundedMap09f657ef(2026-10-23) — refactor(app): cloud client uses the shared JSON fetch3e0e31e1(2026-10-23) — refactor(app): one flag pair instead of two state machines005f51ae(2026-10-23) — refactor(app): one storage accessor instead of eight helpers3efd4fe9(2026-10-23) — refactor(app): nine listener sets share one emitter70132762(2026-10-23) — refactor(engine): one wire-bookkeeping core for both socket clients83fbd5a6(2026-10-23) — refactor(data): one public-REST definition per venue460c19aa(2026-10-21) — refactor(app): a network failure carries its status code4c53aeff(2026-10-21) — refactor(ui): one popover shell instead of four copies of the menu skeleton70360db7(2026-10-21) — refactor(worker): one route table instead of four copies of the route list5d88c490(2026-10-21) — refactor(engine): one run-result envelope instead of threeb7ed5788(2026-10-21) — refactor(ui): one modal shell instead of six copies of the dialog chrome8448c6ef(2026-10-21) — refactor(ui): one event-listener primitive93ebf296(2026-10-21) — refactor(worker): one proxy plane behind the on-chain and market routescbfa1cc7(2026-10-21) — refactor(engine): declare an engine family instead of guessing from its id6569fdc1(2026-10-21) — refactor(storage): one git backend instead of two near-identical files98e665c9(2026-10-21) — refactor(ui): one cached-datasets state, one labelled fielddcdc359a(2026-10-21) — refactor(app): one time normalizer instead of five3574f7a9(2026-10-21) — refactor(worker): one JSON builder per CORS policy, one error envelopef1ce2495(2026-10-21) — refactor(worker): one error envelope, one rate limiter, one abort combinerd5b40875(2026-10-17) — refactor(hud): unify chips on ChipShell, stable footprint, slimmer panelsf0b7ff4e(2026-10-16) — refactor(indicators): decompose runAndApplyInner into apply stages (F-25)b71179eb(2026-10-07) — refactor: remove the pre-Solid static shelle3553e41(2026-10-07) — refactor(worker): consolidate HTTP helpers and proxy routing
Documentation
97bd701c(2026-10-26) — docs(readme): use the HOOX stack banner and family badges3a206b58(2026-10-26) — docs(readme): add the GitHub tagline banner77468040(2026-10-26) — docs(readme): rewrite as an academic operator brief3f82cceb(2026-10-24) — docs(changelog): note D1 auto-rung overlayd89b47fe(2026-10-24) — docs(changelog): freeze HP-2/HP-3 gate numbers after live verificationae2f47b9(2026-10-24) — docs(changelog): record hp2 A1 controlled live runs8670b5c8(2026-10-23) — docs(hp): record the foreground tick-to-frame numbers7b4bb349(2026-10-23) — docs(hp): record the first production measurement of the GPU overlay62f9cb2d(2026-10-23) — docs(mcp): surface hp.get + hp.sample in the README and guidesfc72d731(2026-10-22) — docs(hp): tell the isolation truth — popup-safe COOP does not isolatec38d3a8f(2026-10-22) — docs(hp): architecture page + OKF playbook + rung chip (PR 9)6ae6ff34(2026-10-21) — docs(changelog): fold the refactor notes into one Added section3722bfea(2026-10-21) — docs(changelog): record the time normalizer, datasets modal and field merges4214148f(2026-10-21) — docs(changelog): record fill bands and reapply dedup fixes7bcc3c5a(2026-10-21) — docs(changelog): record chart overlay color preservation fix9962c2be(2026-10-21) — docs(changelog): record reapply guard and color-preservation fixes6d13a542(2026-10-19) — docs(changelog): refresh the recursive history block39a73dd6(2026-10-19) — docs(changelog): note the CI serialization and live-plane coverage in 2.24.76f4dfedc(2026-10-17) — docs: refresh landing index to 2.24.5 with a What's new section9719ad50(2026-10-17) — docs: regenerate llm.txt + llms.txt from the pynescript pipelinec8895052(2026-10-13) — docs(engine): mark migration steps done; note coalescing is the epoch mechanismc6770b50(2026-10-13) — docs: apply Cloudflare® / TradingView® / Pine Script™ brand marks across docsb3610844(2026-10-13) — docs: use Cloudflare® brand marks in worker + cli docs9503fe6f(2026-10-09) — docs: commercial licence upcoming, prices removed5fe7f2e7(2026-10-08) — docs: review against last 20 commits, fix repo identity and voicecbb5bff0(2026-10-05) — docs(readme): fix CLI release tag, add datafeed sidecar and recent highlights24422117(2026-10-05) — docs(changelog): record CI test-isolation fixesc7959187(2026-10-05) — docs(changelog): Unreleased entry for Bun-native session
Tests
d86eabf7(2026-10-26) — test(streams): pin ManualSocket wire tests to JSON transport69ed000f(2026-10-26) — test(engine): prove P6 json+multi flag flip57a83aa5(2026-10-26) — test(chart): cover v1 pane, drawings, and 10k pan/zoom e2e86bf66cb(2026-10-26) — test(hp): pin AXB1 OHLC as int64 ticks, not float326705e927(2026-10-25) — test(chart): prove axis.chart.layout single default and SlotChart5bc20a75(2026-10-25) — test(chart): pin one crosshair time on the single chart35367e02(2026-10-25) — test(engine): pin G1 no JSON.parse of live frames on the window048e3a13(2026-10-25) — test(engine): resume series and script after an ingest worker crashd9e87ee6(2026-10-25) — test(chart): pin update() vs setData() as a paint-action table4ad40b36(2026-10-25) — test(hp): union encoded AXB1 PLOT_TIP columns in the coalescer85e1eb98(2026-10-25) — test(engine): pin APPEND_BAR flush and SNAPSHOT_PLOTS timesd61935bf(2026-10-23) — test(hp): assert the cold viewport repaints without zoominga020dc3b(2026-10-22) — test(hp): AXB1 golden vectors pin every payload shape (PR 7 prep)3e51b6ab(2026-10-21) — test(engine): honor stored every-tick in liveRerunModeForEnginecf47c71f(2026-10-21) — test: cover stored-color fallback in overlay paths4c11493f(2026-10-21) — test: add coverage for plot width/visibility and apply-overlaysc719f49c(2026-10-19) — test(streams): cover the engine-owned live plane end to end03dc1405(2026-10-14) — test: stop cross-file store leakage from making CI order-dependente5453220(2026-10-13) — test(datasession): mint an API key in the e2e harnessc5100b3f(2026-10-13) — test(datasession): cross-repo e2e against the PYNE /data store3c7ed9f4(2026-10-13) — test(data): cover job validation and retention in the Data Source Manager
Chores
59bf3145(2026-10-31) — chore(release): AXIS 2.26.0d86eaf03(2026-10-31) — chore(pyodide): vendor pynescript 0.6.26 wheel (#4)60c8e57b(2026-10-31) — chore(pyodide): vendor pynescript 0.6.25 wheel (#3)05f625fc(2026-10-21) — chore(tooling): project references, compaction, destructive-git guard railsce8c9b51(2026-10-21) — chore(tooling): make the OpenCode project config actually work223d7108(2026-10-20) — chore(release): AXIS v2.25.2119c77b7(2026-10-20) — chore(release): AXIS v2.25.1069bd8d9(2026-10-20) — chore(release): AXIS 2.25.02aed84f1(2026-10-19) — chore(release): AXIS 2.24.80b851707(2026-10-19) — chore(release): AXIS 2.24.7833f0d6f(2026-10-18) — chore(release): AXIS v2.24.6 — live stream feed and MCP plane632aaca3(2026-10-17) — chore(pyne-wheel): vendor PYNE 0.6.12 for the in-browser Pyodide engined946a42e(2026-10-17) — chore(release): AXIS 2.24.513d89fdd(2026-10-15) — chore(release): AXIS v2.24.2 — engine data-plane fixes6bb1c521(2026-10-15) — chore(release): AXIS v2.24.1 — engine live-mode fixes7f16933c(2026-10-14) — chore(docs): regenerate llm packs with the fixed identifier minifierec0ec5c4(2026-10-14) — chore(release): AXIS v2.24.0 — PYNE endpoint parity4092018e(2026-10-13) — chore(brand): remove email-signaturesc906248e(2026-10-13) — chore(pyne-wheel): vendor PYNE 0.6.9 for the in-browser Pyodide engine85eab77e(2026-10-13) — chore(release): AXIS v2.23.0 — proxy repair, rolling window, CI greenbf010b3d(2026-10-13) — chore(okf): refresh the stale knowledge bundlee520a138(2026-10-08) — chore(deps): pyodide 0.29.5 + pynescript wheel 0.6.81709f092(2026-10-07) — chore(release): 2.21.0670fe2dd(2026-10-06) — chore(dev): add storybook scaffoldingeb4ace84(2026-10-04) — chore(license): unify headers under HOOX AXIS / axis8949d283(2026-10-03) — chore(release): 2.19.0 — Extra chrome widgetsdb4e3862(2026-10-03) — chore: ignore local git worktrees
Style
bd940d4e(2026-10-23) — style(settings): format SettingsPage with Prettier
Merges
427ee17b(2026-10-30) — Merge pull request #2 from hoox-sh/feat/concept-simplify5e604afc(2026-10-27) — Merge feat/chart-engine-simplify80e782cd(2026-10-23) — Merge branch 'engine/high-performance' — HP chart engine (AXB1, SAB memory, GPU paint, isolating COOP)129c9656(2026-10-04) — Merge branch 'feat/quote-panel' — Quote dock panel + Extra menu polish277bb465(2026-10-04) — Merge branch 'main' into feat/quote-panel6051219e(2026-10-03) — Merge branch 'feat/extra-widgets' — Extra chrome widgets
2026-09 (162 commits)
Features
bc2e748a(2026-09-29) — feat(brand): add email signature templates7551dfec(2026-09-27) — feat(ui): tune chrome motion and polish data, watchlist, and drawings4f2fc64b(2026-09-27) — feat(ui): quiet the trader shell and collapse the connection HUD00c19cce(2026-09-27) — feat(editor): function tree, sticky Problems, and quieter live runseaab5b13(2026-09-26) — feat(ui): use the HOOX mark as the favicon and show price in the titlea4df8e72(2026-09-26) — feat(chart): add right-click menus on the chart and panelsa6e06b62(2026-09-26) — feat(theme): save named chart themes and bar coloringsf88b9d4a(2026-09-24) — feat(chart): enhance drawing toolbar visual polish7f9c044b(2026-09-24) — feat(chart): drawing toolbar structure and pixel polish1d289805(2026-09-24) — feat(chart): center the loading splash and tuck the tool arrow in3ef62fe6(2026-09-24) — feat(okf): compile an OKF bundle on every commit15b2ba38(2026-09-23) — feat(ui): polish studio settings layout and live scalee3f8dea3(2026-09-23) — feat(chart): dockable drawing toolbar and classic level colors1f46feda(2026-09-22) — feat(ui): polish script library and layers panels12b6ab81(2026-09-19) — feat(plugins): PYNE Agent Persona field (pine/axis/trader)79f65da6(2026-09-17) — feat(mcp): drawings control, settings control, session indicator1d795fb6(2026-09-17) — feat(settings): unified Worker API key for cloud storage and MCPb215b995(2026-09-17) — feat(library): Builtin and Personal tabs in Script Library15be69c3(2026-09-16) — feat(mcp): remote MCP server for Worker APIs and live-app control96310628(2026-09-16) — feat(ui): load-source, Add built-in, and chart screenshot5de57792(2026-09-16) — feat(scripts): first-party AXIS built-in Pine catalogfdf3b656(2026-09-16) — feat(ui): named watchlists, overlay docks, Add, and pyne interpret fix3afd072d(2026-09-14) — feat: pine alerts, live-by-default, and worker-axis rename44bb0eaa(2026-09-14) — feat(editor): convert older Pine to v61dc6c089(2026-09-12) — feat(cli): cloud-storage setup, keys, and D1 schema deployc111371a(2026-09-12) — feat(storage): copy-on-switch, Worker settings, version historya9f7bb6e(2026-09-12) — feat(release): VERSION single source of truth with update manager and close guard319403fb(2026-09-10) — feat(editor): minimap, folding, indent grid, tab badges, workers full width8e9e8d6b(2026-09-10) — feat(editor): Language Feature Bar with live activity chips4d565427(2026-09-08) — feat(cli): standalone binaries via bun compile; fix Docker prepare-hook break1fef94c3(2026-09-08) — feat(ui): default TF 15m, studio opens on Settings, non-modal Workers, no top status77d8600e(2026-09-08) — feat(cli): installation self-check on start + doctor cli-install row716a26eb(2026-09-07) — feat(ui): mobile-first responsive shell (phone sheets, tab bar, force-single chart)74418b05(2026-09-07) — feat(results): events views (Open⇄Close), single-column strategy tab, rich saved statse00b6c97(2026-09-06) — feat(shortcuts): shortcuts modal + Settings → Keyboard recorder6efa590a(2026-09-06) — feat(shortcuts): palette extensions with live shortcut rendering47706d85(2026-09-06) — feat(shortcuts): wire chart keymap through the dispatch hubacc5ce93(2026-09-06) — feat(shortcuts): editor keymap extensions with Prec.high line opse07ecb8c(2026-09-06) — feat(shortcuts): wire app-level bindings through the dispatch hub2795ac72(2026-09-06) — feat(shortcuts): capture-phase dispatch hub with dialog-skip guard50467b3d(2026-09-06) — feat(shortcuts): persisted overrides slice on the app store902741d1(2026-09-06) — feat(shortcuts): chord parser, registry, and default bindingsda5e96d7(2026-09-06) — feat(data): route all dataset paths through the DatasetStoref597806b(2026-09-06) — feat(ui): dataset persistence switch + sliced-backfill progress chip257342d1(2026-09-06) — feat(data): DSM-first load pipeline with progressive painta5caf0ab(2026-09-06) — feat(data): DatasetStore with pluggable persistence sinksd105e161(2026-09-06) — feat(data): dataset merge with conflict detection & policies19c92dd1(2026-09-06) — feat(data): dataset validation & repair with gap classificationa63c3b69(2026-09-06) — feat(ui): move Venue picker to front of Market groupbe830abe(2026-09-05) — feat(chart): per-tool drawing settings (v2.3.0)
Fixes
b063f231(2026-09-29) — fix(chart): owner-clear drawings, fills, and barcolor on script delete6f829378(2026-09-29) — fix(editor): index every name in comma-separated Pine declarationscb9e0cf2(2026-09-28) — fix(chart): stop the drawing rail from clipping tool menus921cd58e(2026-09-28) — fix(chart): drop the unreachable drawing-clear status check5e7b07d7(2026-09-28) — fix(chart): place script drawings on the candle clock11f60823(2026-09-27) — fix(okf): match file names in query and align the agent guided18b6f7d(2026-09-26) — fix(editor): silence false-positive pre-eval typo diagnostics1b341a4b(2026-09-25) — fix(toolbar): snapshot gesture-start anchor for drag tracking095ccdc4(2026-09-25) — fix(plots): keep explicit panel picks, refresh auto colors on re-run869a7cfc(2026-09-25) — fix(plots): resolve Pine color forms to CSS at every apply point6f98cb3f(2026-09-25) — fix(data): preserve DEX symbol case in backfill and cache expand9c05483b(2026-09-24) — fix(okf): drop unused initializers6920e022(2026-09-23) — fix(chart): zigzag pivots, plotshape markers, and Pine bar timec0e07b55(2026-09-23) — fix(plugin): ship PYNE Agent 0.1.7ebe7d7a6(2026-09-23) — fix(indicators): plot Ultimate Oscillator without ta.uo7d02544b(2026-09-20) — fix(chart,ui): clip drawings to plot, catalog line.fill, log chromec23c446c(2026-09-19) — fix(chart): merge top-level compile plot_meta so fill bands applyc1b1fdc1(2026-09-19) — fix(chart): fill edges, color-split bands, and linefill quads8da70490(2026-09-19) — fix(mcp): 405 SSE GET on /mcp so streamable HTTP clients handshake5a122934(2026-09-19) — fix(mcp): single-tab invoke, key rotation, and WS tickets1b6c2093(2026-09-18) — fix(tests): deterministic fetch mocks + stale DSM status guard8c648982(2026-09-18) — fix(plugins): PYNE Agent endpoint seeding + Workers config UId758a6c6(2026-09-17) — fix(store): reload no longer wipes persisted state (TDZ in boot parse)695e22c4(2026-09-17) — fix(watchlist): venue-correct quotes for MEXC, Kraken, and Data Managerbf1984ad(2026-09-17) — fix(cli): cover mcp/mcpBridge in healthFeatures test07ee5f2a(2026-09-17) — fix(docs): escape < and > in generated changelog subjects76a4d753(2026-09-17) — fix(worker): MCP_BRIDGE Durable Object uses new_sqlite_classes0d99cdcb(2026-09-16) — fix(scripts): builtin formula correctness and editor-draft isolation74c7b947(2026-09-16) — fix(ui): watchlist rename Escape, overlay slider, hover-slide, alert popa19488bd(2026-09-15) — fix(ui): autoload, engine switch, layouts menu, and plugin Use14daa3d6(2026-09-14) — fix(ui): OnChainPanel error title rejects nullb3fc629b(2026-09-13) — fix(editor): Lucide chevrons for fold gutterc862227b(2026-09-13) — fix(editor): unify color tools around one working colorbbf7bc9d(2026-09-12) — fix(cli): drop empty ADMIN_TOKEN var before secret put99eb5f20(2026-09-12) — fix(ui): Studio Settings home, Workers fullscreen, minimap height9e963eab(2026-09-12) — fix(docker): skip missing VERSION stamp files in image buildff4674a1(2026-09-12) — fix: harden PWA, editor, e2e, and Docker; release 2.6.23a5de985(2026-09-12) — fix(pwa): retry transient fetch failures in SW cache-firstcb089b18(2026-09-12) — fix(e2e): retry twice on CI runnersb3915c90(2026-09-12) — fix(e2e): wait for boot in openStudio, upload failure artifacts8f8c91cd(2026-09-12) — fix(docker): copy version-stamped files into build context60aa2c4e(2026-09-12) — fix(ci, docker): copy VERSION into build stage, harden studio helperbd8d1c0c(2026-09-11) — fix(pyodide, editor, cli): bridge traceback tail, feature-bar popover, host-native smoke364776c5(2026-09-09) — fix(pyodide): pass bars via globals + json.loads, surface full tracebacke4888c72(2026-09-08) — fix(ci): SHA256SUMS used CI paths for binaries — sha256sum -c unusable7d16a7bb(2026-09-08) — fix(ci): CLI tgz mv target — packages/cli/.. is packages/, not the root144ef3d3(2026-09-08) — fix(ci): GITHUB_ENV var casing — release-assets referenced empty varscdb188b9(2026-09-08) — fix(ci): worker bundle sanity check — wrangler.toml is gitignoredcb6f00f5(2026-09-08) — fix(ci): check-versions fails on Windows runners — CRLF line endingsd4082f4c(2026-09-08) — fix(shortcuts): mount ShortcutHub + keypress feedbackc06a14cf(2026-09-07) — fix(test): make CI test suite green (server hook timeout, matchMedia stubs)81f24c76(2026-09-07) — fix(lint): resolve all Biome lint errors (368→0); add Biome + Prettier tooling7d9f2dec(2026-09-07) — fix(ui): resolve 23 UI-review findings across editor, drawings, panels, themes464a9497(2026-09-06) — fix(architecture): only preload Pyodide when the DOM can host asset links8d5daf5c(2026-09-06) — fix(worker): clean up tsc errors under noUncheckedIndexedAccess & exactOptionalPropertyTypes7ee49cc4(2026-09-06) — fix(data): DatasetStore memory-first, DSM paint/gaps, v2.4.0520869dc(2026-09-05) — fix: setup review — doctor, live HUD, static health, 2.3.116499ac7(2026-09-04) — fix(results): skip live-tick persist and unstick studio e2e
Performance
57741698(2026-09-25) — perf(chart): single-tick resize for charts, overlays and drawings09b00fb3(2026-09-21) — perf(chart): keep large OHLCV histories interactive
Refactors
eac5016e(2026-09-25) — refactor(patterns): share emitter/singleton, collapse registry, venue assertsac0d8a90(2026-09-08) — refactor(editor): move Script Logs into the editor bottom bar
Documentation
7643ae3c(2026-09-23) — docs: match AXIS 2.13.0 catalogs, engines, and repo pathsf16f868b(2026-09-19) — docs: pyodide-in-worker use case, NO_BACKEND contract, test conventions7655bb25(2026-09-14) — docs(changelog): drop raw <cmd> so MDX sync can parse477084bf(2026-09-08) — docs(changelog): note bare-filename SHA256SUMS for binaries055d73d5(2026-09-08) — docs: CLI-first command surface (axis <cmd> primary, bun run axis:* as repo alias)9b75c459(2026-09-08) — docs(readme): real badge row, stack cross-links, unified stack footerefa7e87c(2026-09-08) — docs(readme): add official Codecov coverage badgee38a72f9(2026-09-07) — docs: sync docs to v2.5.0 — shortcuts, mobile shell, results viewseecca17d(2026-09-06) — docs(changelog): regenerate full history for v2.4.0e9df22fe(2026-09-06) — docs(changelog): DSM-first dataset store, validation, persistence switchd10a9ea4(2026-09-05) — docs(screenshots): wire stills into README and matching guidesadcb846d(2026-09-05) — docs(screenshots): close editor except on editor-focused stills3211bfe0(2026-09-05) — docs(screenshots): recapture at 1920x1080 and 2560x14409b820a77(2026-09-05) — docs(screenshots): capture PWA, Studio, and CLI stills plus GIFs
CI
60a0c8d6(2026-09-08) — ci(release): attach CLI tarball + worker source bundle to GitHub Releasesd5f0cf89(2026-09-08) — ci(hooks): versioned .githooks — pre-commit biome (staged), pre-push tsc + lintcdb68ff9(2026-09-08) — ci: split Codecov uploads per flag, bump action to v7, target 75%9fe171a6(2026-09-08) — ci: upload unit + CLI coverage to Codecov637cfa87(2026-09-06) — ci(cli): full npm release pipeline with tag guard, registry verify, Node smoke
Tests
d3e21123(2026-09-16) — test(e2e): mock engine /run without intercepting runner.js077c6c69(2026-09-16) — test(e2e): open editor before asserting Script Logs toggle8638f3c0(2026-09-16) — test: wipe pluginsConfig with reconcile so engine mode stays auto1962bfb9(2026-09-16) — test: unstick CI unit failures on bun SVG stub and Worker URL079f1569(2026-09-12) — test(e2e): drawings overlay spec with chart DOM harness3e6778ea(2026-09-12) — test(coverage): lift scoped core gate 82.8% to 89.9%6527e3de(2026-09-10) — test(editor): cover folding, indent grid, minimap class2c9643b3(2026-09-05) — test(pwa): stub beforeinstallprompt without DOM Eventc0d88523(2026-09-05) — test(load-symbol): isolate live stream and leftover scripts
Chores
7ea9e8ef(2026-09-28) — chore(release): AXIS v2.18.0 — script drawings and watchlist flashb94be4bb(2026-09-27) — chore(release): AXIS v2.17.1 — quiet trader shell1f81f4c8(2026-09-26) — chore(release): AXIS v2.17.0 — context menus, favicon, and live titlec58e45a3(2026-09-26) — chore(release): AXIS v2.16.0 — named themes and bar coloringsd62a2ee6(2026-09-24) — chore(release): AXIS v2.14.0 — splash, toolbar, and studio9ad5b004(2026-09-23) — chore(release): AXIS v2.13.0 — drawing toolbar and classic colorsee32dab4(2026-09-22) — chore(release): AXIS v2.12.0 — library and layers panels6e28ccf6(2026-09-21) — chore(release): AXIS v2.11.0 — large-history chart, DSM, MCP1b9357fa(2026-09-18) — chore(release): AXIS v2.10.0 — toasts, notification settings, MCP activityb327a28a(2026-09-10) — chore: stop tracking .grok/ and AGENTS.md (local-only)28420c09(2026-09-08) — chore(release): v2.6.1 — sync desktop version stamps + check:versions guard67723020(2026-09-08) — chore(release): v2.6.0 — shortcut hub fix, keypress feedbacka78a7fca(2026-09-07) — chore(release): v2.5.0 — mobile shell, pyne 0.5.0, zero lint errors8d6a7c70(2026-09-07) — chore(engines): vendor pynescript wheel 0.5.0 from pyne9ae3e3da(2026-09-07) — chore(release): v2.4.3 — results events views, single-column strategy, saved statsaaf4d9ac(2026-09-07) — chore(release): v2.4.2 — UI review fixes, keyboard shortcuts, line opsfa49a6c1(2026-09-06) — chore(cli): release @hoox-sh/axis-cli 0.2.236ba501c(2026-09-06) — chore(release): v2.4.1 — worker tsc compliancee84b8325(2026-09-04) — chore(release): prepare v2.2.0
Other
9ee19452(2026-09-17) — release: 2.7.0 MCP server, docs, and CLI 0.3.3697e55ed(2026-09-16) — release: 2.6.8 first-party built-in scripts and watchlist chromed6e6fd9c(2026-09-14) — release: 2.6.7 convert-to-v6 UDF-safe prefixes
2026-08 (237 commits)
Security
d2b50220(2026-08-13) — a11y,security: focus trap, chart region, SessionDO stream sanitize4321c4b9(2026-08-13) — security,perf: plugin allowlist, webhook policy, CSP, fill/runner hot paths72869743(2026-08-13) — perf,security,ci: multi-agent optim stack (registry, overlay tip, auth, SW, bundle, CI)ebfc9955(2026-08-07) — harden chart, data, and run paths for performance and crash resilience
Features
783a1003(2026-08-30) — feat(ui): fullscreen studio overlay, Results JSON tree, and market proxy hardening9081a559(2026-08-29) — feat(worker): MEXC public REST through /api/market/mexc/* proxyfb193c25(2026-08-29) — feat(panel): per-panel icon in FloatableShell header9196e4cb(2026-08-29) — feat(ui): storage-change migration dialog008fb22a(2026-08-29) — feat(storage): persist strategy/indicator run results via StoragePlugineb86c103(2026-08-28) — feat(results): fullscreen results studio, HPO polish, and tier gating28a1f202(2026-08-27) — feat(settings): add topbar tab to SettingsPage (studio view)339f6a05(2026-08-27) — feat(settings): add topbar visibility settings tab67b1558d(2026-08-24) — feat(security): enforce CSP baseline on Cloudflare® Pages deploys3fb40122(2026-08-24) — feat(data): native MEXC spot venue + PYNE Pro origin pynescript.online09e047cd(2026-08-24) — feat(ui): single Studio button replaces Wire/Runtime/Settingscd0148ca(2026-08-23) — feat(cli): ship production-grade @hoox-sh/axis-cli 0.2.00d45a6f2(2026-08-23) — feat(ui): full-page studio overlay for Runtime, Wire, and Settings6900c093(2026-08-22) — feat(ui): slide-over drawers for Settings, Runtimes, and Wirec685a2d4(2026-08-22) — feat(ui): Venue picker pairs native CEX and CCXT in one controlfe0ed9c7(2026-08-22) — feat(ui): CCXT symbol catalog and hide paired stream95144094(2026-08-22) — feat(data): CCXT session keys and plugin-config/editor fixes339175f0(2026-08-21) — feat(ui): advanced config fields in Settings + full ccxt exchange list203b4492(2026-08-21) — feat(ui): topbar plugin config row for source/stream configSchema6b4d5543(2026-08-21) — feat(plugins): gateway-aware active source/stream resolution1631975f(2026-08-21) — feat(stream): ccxt-ws gateway stream pluginc1887afd(2026-08-21) — feat(source): ccxt-rest gateway source plugina47baf04(2026-08-21) — feat(data): gateway transport layer for CCXT datafeed3c72d089(2026-08-20) — feat(release): AXIS v2.0.18 multi-venue DO, credential vault, CCXT sidecarc42992e8(2026-08-19) — feat(release): AXIS v2.0.17 editor lint underlines and typos720237a1(2026-08-19) — feat(release): AXIS v2.0.16 editor intel recoveryfd23d4a8(2026-08-19) — feat(release): AXIS v2.0.15 editor intelligence and linebrcbe6369f(2026-08-19) — feat(editor): richer hover, param checklist, and pre-evala2178fc9(2026-08-18) — feat: live script execution, chart labels, and strategy properties35557be3(2026-08-17) — feat(release): AXIS v2.0.14 strategy hyperparameter optimisation9ec62e96(2026-08-17) — feat(release): AXIS v2.0.13 params, highlighting, input enums, tablesdb3b42c1(2026-08-17) — feat(release): AXIS v2.0.12 editor UX, chart lifecycle, icon mapf96319e3(2026-08-17) — feat(release): AXIS v2.0.11 chart scripts lifecycle and pyne-worker engine7f85d664(2026-08-16) — feat(release): AXIS v2.0.10 market proxy, inputs layout, local-first LSP2cae2b72(2026-08-15) — feat(editor): symbol catalog and richer Pine highlighting9411a16b(2026-08-15) — feat(pyodide): vendor pynescript 0.3.7 wheelfe6dfb18(2026-08-15) — feat(library): git versioned publish emulator for import ns/Name/ver4d1e8647(2026-08-13) — feat(ui): architecture modal to wire plugin slots from recipes814cb21f(2026-08-13) — feat(editor): 50vw default width; content-sized gutters80f25012(2026-08-13) — feat(pyodide): vendor pynescript 0.3.6 wheel904ec110(2026-08-12) — feat(drawings): paint line.style_arrow_left/right/both heads669ee31d(2026-08-12) — feat(drawings,plot): label styles/yloc, plot columns/cross/diamond8b80166b(2026-08-12) — feat(drawings): non-overlay pane Y + force_overlay routing46229069(2026-08-12) — feat(drawings,plot): linefill paint, barcolor, multi-script shapes4b379391(2026-08-12) — feat(ui): strategy Properties tab in Script settings4bf973e4(2026-08-12) — feat(ui): About AXIS modal on logo clicke96114b0(2026-08-12) — feat(editor): handle PYNE Agent insert/open script eventse29aa644(2026-08-12) — feat(ui,panels): panel defaults, chart overlay, classic logs chromea00ad891(2026-08-11) — feat(ui): dockable Status and System Logs; Script Logs naming7f44667e(2026-08-11) — feat(runtimes,editor): hub, idle lint, save-before-run, tight line gutters0c4a812d(2026-08-11) — feat(editor,panels): hover-slide docks and editor chrome polish7abea423(2026-08-10) — feat(chart): price-scale decimals with auto from symbol/bars235079b7(2026-08-10) — feat(editor): autoformat, color tools, and chrome redesign9ce5e0f3(2026-08-10) — feat: plot style chart parity, preeval constants, and storage versionsdee91540(2026-08-10) — feat(ui): fullscreen modes, price-pane scale controls, brand density8aa9dddc(2026-08-10) — feat(ui): symbol modal and richer Scripts panel cards60502d08(2026-08-10) — feat(cli): add AXIS CLI for install, setup, deploy, and doctorda9dd6fd(2026-08-10) — feat: Workers Manager modal and Tauri desktop shellcf938db1(2026-08-09) — feat(examples): add Pine v6 script library starter packa21dc811(2026-08-08) — feat(plugins): install PYNE Agent component and enable component URL loadfae48240(2026-08-08) — feat(onchain): refresh jobs, CSV export, and popular protocol presets61c9993c(2026-08-08) — feat(onchain): alerts UI, data view, commands, and ADR-0158ad97cb8(2026-08-07) — feat(onchain): alerts, layers, HUD health, and DSM walk-back polishf91a10f7(2026-08-07) — feat(onchain): data plane with DefiLlama TVL, DEX OHLCV, and eventsf62611a0(2026-08-07) — feat(editor): typo marks, wrap toggle, resizable Problems259204e5(2026-08-07) — feat(editor): pre-eval marks wrong code and disables Run6a945396(2026-08-07) — feat(drawings): merge parallel agent tool packs with full tests9427a565(2026-08-07) — feat(drawings): Gann, fib extras, patterns, and forecast tools963f5f87(2026-08-07) — feat(drawings): seed types and catalog for next parity packsaaa2cca5(2026-08-07) — feat(drawings): pitchfork, brush, callout, and more parity tools1abe0795(2026-08-07) — feat(drawings): registry-based tools toward charting platform parity807262a4(2026-08-07) — feat(data): dataset manager load window, venue live, expand to now0e4ae885(2026-08-06) — feat(data): raise historical bars cap to 100k58ff7be5(2026-08-06) — feat(data): cached datasets browser and Data Manager chart sourcec1d45d00(2026-08-06) — feat(ui): high-end theme presets, Scripts panel, Run accent only while busy4a6c5e49(2026-08-06) — feat(data): background Data Source Manager for multi-page OHLCV backfillb7887c92(2026-08-06) — feat(editor): slide-in tool labels on hover4d533975(2026-08-06) — feat(strategy): close fills, slippage next-open, marker prefs; fix script settings focus4705cb96(2026-08-06) — feat: symbol-scoped drawings, chart refresh, labels, editor docs43b0de57(2026-08-04) — feat(docker): full Buildx bake + Compose stack for AXIS PWAb51879b0(2026-08-04) — feat(ui): move chart Theme Manager to its own Settings tab5f6863f0(2026-08-03) — feat: chart Theme Manager (Pine chart.bg_color/fg_color) and UI hardening26a8358d(2026-08-03) — feat: GitHub/GitLab OAuth connect, multi-script results focus, hoox-sh org902f3d4f(2026-08-03) — feat: multi-agent PWA hardening for rock-solid reliabilityb844a76f(2026-08-02) — feat(ui): library panel, editor run/tab, live-after-load defaulted812a67(2026-08-02) — feat(chart): render Pine fill(plot1, plot2, color=…) as SVG bands7b481574(2026-08-02) — feat(library): default git scripts to .pyne under pyne-library52e2aa8c(2026-08-02) — feat(library): drag-drop .pine files into script library0b0df8e7(2026-08-01) — feat(editor,topbar): parallel polish — ruler, diagnostics, git, pins, problems10dd5074(2026-08-01) — feat(editor): show cursor line:col in stats stripc44f4cd9(2026-08-01) — feat(chart): script action icons on pane name badgesee31517a(2026-08-01) — feat: TV-class power features from parallel agentsa6cd8e1b(2026-08-01) — feat(editor): inline debug, wrap, stats; layers left slide-in9309f9a2(2026-08-01) — feat(layers): list and select user drawings in Layers panelf45bf320(2026-08-01) — feat(drawings): vline, extend, arrow, ellipse + toolbar polish40430f6b(2026-08-01) — feat(chart): multi-chart layouts, indicator align, editor full heightbacedac8(2026-08-01) — feat(ui): chart reflow with docks, reload/reset, float editor fixes0f9c4433(2026-08-01) — feat(ui): stack multiple docked panels one below the otherf982e0d8(2026-08-01) — feat(chart): multi-pane sync, scale controls, drawing GC, history depth
Fixes
defaccea(2026-08-29) — fix(data): try MEXC Worker proxy first, direct host as fallback167921f2(2026-08-29) — fix(worker): update CORS product hosts to pynescript.online (retire .ai)8426f3d9(2026-08-29) — fix(editor): remove duplicate 'Open in new tab' entry6178bced(2026-08-29) — fix(ui): status bar chips wobble on hovere0b8d4a8(2026-08-27) — fix(chart): repaint full series on input override recompute5fe39320(2026-08-27) — fix(ui): fine-tune settings polish and worker probead473102(2026-08-27) — fix(ui): polish settings pages and fix worker health probe blockingbb702efb(2026-08-27) — fix(wire): remove separate scrolling sections from wire pagef38a0239(2026-08-26) — fix(compile): display= folding into plot_attrs for data_window-only plotscb9c1773(2026-08-26) — fix(compile): hline linestyle via plot_meta backfill + default auto mode5bc18f5d(2026-08-26) — fix(hline): synthesize plot_meta in interpret path for hline linestyled417025f(2026-08-26) — fix(multi-chart): restore slot history on focus so each cell computes independently5025302f(2026-08-26) — fix(hline): carry linestyle through both drawing and overlay pathsb07befb9(2026-08-25) — fix(chart): render table cell valign/text-size; carry bgcolor band titles86b86bd7(2026-08-24) — fix(chart): never host hlines on bgcolor underlays53cef490(2026-08-24) — fix(chart): finish PlotSample widening in runner types2db34902(2026-08-24) — fix(chart): preserve engine color series so bgcolor/barcolor render68b13118(2026-08-23) — fix(settings): keep user inputs on applied scriptsea13ba50(2026-08-23) — fix(ui): seed Wire plan on mount and expose source for e2e70d0b378(2026-08-23) — fix(chart): honor Pine plot display.* flagsbed28c62(2026-08-23) — fix(chart): hide Pine drawings, label pad, histbase, re-owna05e42d8(2026-08-23) — fix(chart): plotshape pane routing and drawing tool settings098bfee4(2026-08-23) — fix(chart): drawing move, linefill GC, overlay pane routing55e26ddd(2026-08-22) — fix(editor): stop afterDot state leak from trailing-dot floatsd98048a8(2026-08-22) — fix(editor): pre-eval typo round 3 — generics, import paths, dotted calls47beaa62(2026-08-22) — fix(editor): pre-eval typo round 2 — decl forms, curly quotes, linktoseries5f0c3f36(2026-08-22) — fix(editor): stop pre-eval typo flood on built-in vars, qualifiers, text.*2f8ff1d2(2026-08-22) — fix(ui): plugin config first-save crash + gateway select optionsa6323558(2026-08-21) — fix(ui): single shared plugin config row + exchange dropdown7e74a266(2026-08-21) — fix(gateway): remote-page loopback resolution + drop shadowed require67361dc6(2026-08-21) — fix(datafeed): ccxt-ws browser ESM require + ccxt-rest walk-back windowaefa3baf(2026-08-20) — fix(editor): skip hex color tails in pre-eval identifier scannerd408f110(2026-08-20) — fix: resolve all 23 pre-existing TypeScript errors + CI typecheck gate804c5662(2026-08-20) — fix(test): reset store provider state in credentials beforeEachb160498e(2026-08-19) — fix: review follow-up for hover, hide, linebr, and agent seed35efaed7(2026-08-19) — fix(plugins): render PYNE Agent markdown and drop default endpoint7ccd745d(2026-08-19) — fix(chart): break plot.style_linebr on nadbcf1f0a(2026-08-18) — fix: review follow-up for live hide, labels, and strategy propsfa57f639(2026-08-17) — fix(editor): LSP pre-eval marks and last-bar drawingsf0235ac1(2026-08-15) — fix(ci): desktop Watchlist case clash and GHCR-only docker push42cb454f(2026-08-15) — fix(ci): green unit, smoke, desktop build, and docker bake4d34d82a(2026-08-13) — fix(editor): allow Problems panel to stay closedeb61d218(2026-08-12) — fix(drawings,plot): line.new last-bar export path + plotarrow direction299ec83c(2026-08-12) — fix(test,store): green suite — isolate defaults, health mock, bar fills41099e71(2026-08-11) — fix(plugins): clearer errors when cross-origin plugin import lacks CORS3bc43323(2026-08-11) — fix(panels): harden hover-slide reflow for non-DOM test windows0a668e00(2026-08-11) — fix(security,perf): gate /api/run, OAuth clientId, WS cool-down156221b9(2026-08-11) — fix(security,perf): fail-closed Worker auth, CORS, bars-cache9bfc801b(2026-08-10) — fix(editor,run): defer syntax lint; enum completions; protect interactive Run24f9cf68(2026-08-08) — fix(run): Re-run replace, add-instance menu, clear stuck Running77d2727e(2026-08-08) — fix(onchain): health probe always targets Worker baseb24fbd19(2026-08-08) — fix(onchain): honor empty health endpoint overridef8e3ae47(2026-08-08) — fix(onchain): always use AXIS Worker proxy; allow product CORS origins794ecadb(2026-08-08) — fix(onchain): do not proxy DefiLlama via axis.hoox.sh SPA host6d672e34(2026-08-07) — fix(editor): recognize strategy qty constants; copy problems3557ac99(2026-08-07) — fix(drawings): keep trendline draft when tool re-syncs mid-placeb81991da(2026-08-06) — fix(deploy): same-origin Pro API on https://axis.hoox.sh3e1f3a76(2026-08-06) — fix(git): harden storage integrity and OAuth device flow6f870e52(2026-08-06) — fix(data): backfill from now with gap validate; theme chrome; editor toolsebddd006(2026-08-06) — fix(data): multi-page walk-back without startTime+endTime trapfdb4c6b1(2026-08-06) — fix(ui): repair DrawingToolbar JSX after badge layout comment1fd8b64f(2026-08-06) — fix(ui): keep script badges above drawing toolbar chrome9140e31d(2026-08-04) — fix(strategy): correct PnL for Long/Short reverse entries260a59c4(2026-08-04) — fix(strategy): stop zeroing net PnL from placeholder prices/profit0867b1ce(2026-08-04) — fix(ui): make log copy buttons work on HTTP (clipboard fallback)10313f67(2026-08-04) — fix(strategy): qty-aware PnL, equity curve, and Strategy results tab4b32864d(2026-08-04) — fix(pyodide): align vendored wheel dist-info with pynescript filename7f516350(2026-08-04) — fix(ui): stop overlapping PRICE, indicator, and symbol badges73fd9d3d(2026-08-03) — fix: strategy events, plot colors, and price-scale labels16a381d1(2026-08-03) — fix(ui): icon-only editor tools; new tab in panel hamburgera4cc7b27(2026-08-02) — fix(chart): collapse stacked same-text Pine status labels146bb090(2026-08-02) — fix(results): drop no-fill closes; recover Source inputs; clamp future labels134414dc(2026-08-02) — fix(library): strip TradingView® Expand (N lines) chrome on import3d89f4ac(2026-08-02) — fix(library): open every dropped .pine as its own editor tababcf5eb2(2026-08-02) — fix(library): block browser file-open on pine drag-drop85e037be(2026-08-01) — fix(dock): side-by-side panels on left/right (indicators left of editor)33305aec(2026-08-01) — fix(chart): plot indicators on stable sub-pane id4fd29aea(2026-08-01) — fix(chart): stop oscillator scripts vanishing on the price pane8a11fdc5(2026-08-01) — fix(editor): restore full height so lines are editable3a77976d(2026-08-01) — fix(replay): start with full history, not a single candlef89b9cc6(2026-08-01) — fix(editor): sync full Pine builtins corpus from pyne
Performance
de00cbf3(2026-08-13) — perf(pwa): heavy live re-run throttle, offline bars fallback, SW v4 capa9375440(2026-08-13) — perf: coalesce live ticks, resize, and strategy UI; harden paint/securitycb901eb4(2026-08-10) — perf: full-app hardening — live ticks, overlays, load abort, boot splita9f57958(2026-08-10) — perf(chart): heavy history paint, conflation, and O(1) live ticks
Refactors
afb85816(2026-08-26) — refactor(about): remove ethos/author, add versions and philosophy6041c2d6(2026-08-22) — refactor(ui): move plugin config section into Settings → Data tabf19fe9a2(2026-08-08) — refactor: rename remaining pine-* modules to pyne-*18eedc78(2026-08-08) — refactor: rename pine-language/lsp/builtins editor stack to pyne-*096a799f(2026-08-08) — refactor: rename pine-editor CSS, DOM, and PyneEditor componentb52526af(2026-08-08) — refactor: rename pine-editor.js to pyne-editor.jsf0a1446b(2026-08-02) — refactor: rename chart palette TV→VOID; document Pine naming parity
Documentation
0a705de9(2026-08-27) — docs(changelog): add topbar settings and wire page fix entries35dacd21(2026-08-24) — docs: studio shell, MEXC venue, and PYNE Pro origin across guidesdce471b0(2026-08-21) — docs(devops): list /datafeed/ among nginx-proxied Pro API paths27b5ca13(2026-08-21) — docs(changelog): datafeed gateway phase 4 additions36900319(2026-08-15) — docs(cors): document Pages preview origins for axis.hoox.shd3172dbb(2026-08-11) — docs: AXIS 2.0.1 docs, CLI-first ops, Pine Script™ v6 examples78c22e85(2026-08-11) — docs(release): recursive changelog, AGENTS release workflow, v2.0.1f7c30e04(2026-08-09) — docs: prefix internal links with /axis/docs3d6fe1ab(2026-08-08) — docs(ui): clarify on-chain uses public APIs unless Worker endpointf21dcc47(2026-08-08) — docs: update llm.txt for PyneEditor rename95e181c3(2026-08-08) — docs: document on-chain data plane in README, hubs, and workerdf7b77ab(2026-08-07) — docs: add PYNE Agent (pyne-agent-worker) plugin guides5d1dc0f0(2026-08-07) — docs: add PYNE Agent (pyne-agent-worker) plugin guidesdc62332f(2026-08-03) — docs: regenerate llm.txt and llms.txt agent corpora5dc92693(2026-08-01) — docs: align UI and end-user guides with last 15 commits
CI
0c963fc6(2026-08-23) — ci(cli): do not fail release when access public is forbidden88ca0133(2026-08-23) — ci(cli): treat already-published 0.2.0 as success and set publicc35de1fe(2026-08-23) — ci(cli): auth registry checks and always set package publice07c59df(2026-08-23) — ci(cli): force @hoox-sh/axis-cli public after npm publishdd3ebf85(2026-08-23) — ci(cli): publish with NPM_TOKEN_HOOXSH and isolate CLI tests75c4543c(2026-08-22) — ci: versioned docker tags and npm publish for axis-cli3ddc4193(2026-08-10) — ci(docker): fix image build, enhance make targets, add GHCR workflow5b4bbef4(2026-08-10) — ci: build Tauri desktop app on every main push
Tests
5d5844ae(2026-08-23) — test(e2e): open Plugins via studio rail; exact Offline Lab apply
Chores
862631e6(2026-08-29) — chore(release): prepare v2.1.140668dbc(2026-08-29) — chore(release): prepare v2.1.0f34c76cf(2026-08-26) — chore(release): bump to v2.0.27 — PYNE wheel 0.4.0, hline linestyle, multi-chart fixd323c8fd(2026-08-24) — chore(release): v2.0.26a01be631(2026-08-23) — chore(release): AXIS v2.0.2322966e62(2026-08-23) — chore(release): AXIS v2.0.223e7236ef(2026-08-21) — chore(release): v2.0.20b4b3227b(2026-08-15) — chore(release): AXIS v2.0.8236d042f(2026-08-12) — chore(release): AXIS v2.0.75f6d7a9a(2026-08-12) — chore(release): AXIS v2.0.612cffe27(2026-08-11) — chore: ignore local multi-agent sync notice files33e11930(2026-08-06) — chore(worker): gitignore wrangler.toml and ship example9cb4dad0(2026-08-06) — chore: vendor latest pyne 0.3.0 wheel (parity Aug 6)9fba8aaf(2026-08-03) — chore: rename SuperChart branding to AXIS including CF project id0cbda10b(2026-08-01) — chore(pyodide): vendor pynescript 0.3.0 wheel with drawing GC
Style
21676494(2026-08-22) — style(ui): render inline plugin config fields via TopbarField
Merges
938835cb(2026-08-08) — Merge pull request #1 from hoox-sh/feat/onchain-data-plane
Other
e6491985(2026-08-08) — merge: resolve main conflicts for on-chain docs
2026-07 (79 commits)
Security
8c4a635c(2026-07-24) — Harden + bugs + polish (16 changes)
Features
593bb8aa(2026-07-31) — feat(inputs): OHLC source enums and cross-indicator plot sources501761db(2026-07-30) — feat(ui): make all panels border-resizable down to 1px852d2fa2(2026-07-30) — feat(ui): Scriptlogs rename; editor FloatableShell + tools745e3426(2026-07-30) — feat(ui): Pine Logs panel and Editor Profiler modea2ed35e6(2026-07-30) — feat(chart): multi-type price series and fix settings save raced0e52bb2(2026-07-29) — feat: live watchlist WS quotes and TV-style drawing chrome9487673c(2026-07-29) — feat(ui): softer inputs and collapsible panel dock menu4e9a6100(2026-07-29) — feat(ui): density scale slider and floatable dockable panels4e3f029d(2026-07-29) — feat(ui): data window, layers, script inputs; plot visuals + strategy60c7e2f9(2026-07-29) — feat(settings): presets for VPS UI + local pyne compile52cd2668(2026-07-29) — feat(editor): prefer pyne Pro API LSP for completion and hover08fdbf24(2026-07-29) — feat(editor): Pine completion and hover from LSP builtin metadataebb4db8d(2026-07-29) — feat(hud): ENG/RUN/MODE/PATH chips with sticky hover infoba480687(2026-07-29) — feat(settings): expose PYNE execution mode (interpret/compile/auto)7dc63117(2026-07-27) — feat(axis): connection HUD, WSS engine path, chart stability7cf3d357(2026-07-27) — feat(axis): quality stack at 95% coverage, e2e, and ops polish55a5a0c2(2026-07-26) — feat(axis): self-host Pyodide and preload on idlee264c91e(2026-07-26) — feat(axis): source-aware watchlist with interval and refresh settings5de17948(2026-07-26) — feat(axis): plugin capability badges and storage status chromeb56e4f61(2026-07-26) — feat(axis): git script storage and Pine table HUD3e00e1e1(2026-07-26) — feat(axis): script library, storage plugins, and drawing polish6322cd7b(2026-07-26) — feat(axis): render Pine drawings and drag user drawingsc9d2a0e8(2026-07-26) — feat(axis): interactive chart drawing tools31b7eed8(2026-07-26) — feat(axis): jump-to-trade from Results and status-bar P&L9c0e3b06(2026-07-26) — feat(axis): strategy trade markers, equity pane, and event normalizationbd0a106b(2026-07-26) — feat(axis): ship AXIS charting PWA, evaluator corpus fixes, and /run/batchc2f27a02(2026-07-25) — feat: SuperChart rewrite + Pine evaluator runtime fidelityacee8d1c(2026-07-24) — feat: slide-in editor pane with push-layout and drag resize2a6a2163(2026-07-23) — feat: Pyodide engine rewrite, watchlist sidebar, URL hash sync, demo scripts40db7f47(2026-07-23) — feat(pwa): wire multi-tab editor (CodeMirror 6 with tab persistence)68da3cce(2026-07-23) — feat(pwa): plugin manager, script library, theme switcher, symbol autocomplete, plugin examplesfe90be7b(2026-07-23) — feat(pwa): settings dialog, multi-pane, volume, time presets, strategy tester9be7d819(2026-07-23) — feat(frontend): modular PWA + Cloudflare® Worker backend28b57862(2026-07-20) — feat(v6): multiline strings, export const runtime, strategy state, library import
Fixes
d5e487f2(2026-07-31) — fix(profiler): compute gutter % from Σ line cost, not wall ms4d5ad0a0(2026-07-31) — fix(profiler): send profiler over WS and auto-run on enable34cb26a3(2026-07-31) — fix(editor): render LSP hover markdown and round tooltip corners0abecff8(2026-07-31) — fix(profiler): pass profiler flag to API and map profile/logs33308a88(2026-07-29) — fix(ui): stop pine drawing flicker; indigo loader accent3c6dbbae(2026-07-29) — fix(engine): REST fallback after WS timeout must not reuse dead AbortSignal7b4b951e(2026-07-29) — fix(settings): clearer endpoint probe errors for NetworkError156e8c7f(2026-07-29) — fix(hud): keep ENG plane in sync with selected engine8ffa6c5c(2026-07-29) — fix(ui): pyodide load hint, clarify MODE vs ENG chips, clip tick pulse9dfeab37(2026-07-29) — fix(pyodide): install local wheels with deps=falsef4dd5d0b(2026-07-29) — fix(settings): always show execution mode for server/pyodide57814bd1(2026-07-29) — fix(settings): show execution mode for pyodide engine too7d0021d2(2026-07-29) — fix(engine): tolerate bare NaN in REST /run responsesb97649fd(2026-07-29) — fix(engine): send execution mode in REST /run body22178b3a(2026-07-27) — fix(api): accept null optional symbol on WS live re-runs431b3da2(2026-07-26) — fix(axis): serve pyodide wheels from public/ and stop SPA HTML fallback2d10a39a(2026-07-26) — fix(axis): open Indicators panel from topbar togglef75867d8(2026-07-26) — fix(axis): return multi-plot series from /run and harden trade pairing49ddff4e(2026-07-26) — fix(axis): multi-plot series with colors and bar-mode crossovercf36ed70(2026-07-26) — fix(axis): serve example plugins from /plugins in production754b433c(2026-07-26) — fix(axis): split plugin-badge utils for bun tests; drop wrangler d.ts1ef06799(2026-07-26) — fix(axis): resolve marker time from bar_time and kind from parity events4252dc9e(2026-07-26) — fix(evaluator): Console UDT multi-dispatch, bare na, and bar-loop lock95479073(2026-07-24) — fix(chart): fix chart and volume pane CSS layout6642164d(2026-07-24) — Fix chart overlap: absolute-position the loading skeleton086277c3(2026-07-24) — Fix data window positioning: use fixed coords + getBoundingClientRectaaff9631(2026-07-24) — Fix 4 layout/UX issues: loading skeleton, auto-run demos, watchlist, data window4bc3132f(2026-07-24) — fix: stdev always returns None due to AdvancedIndicators dispatch + PineSeries initial Nonee0854e83(2026-07-24) — fix: dict/NoneType errors in Pyodide engine (input.int, color.new, arith)40568a06(2026-07-24) — fix: builtin dispatch only passes kwargs if handler accepts them7f2d7928(2026-07-24) — fix: SMA constant values and RSI None crash in Pyodide enginec35b12d6(2026-07-23) — fix: merge kwargs into positional args for builtins in Pyodide runtime
Documentation
6bd9c848(2026-07-29) — docs: inline TSDoc and module headers across the codebasea3ee4a19(2026-07-29) — docs(cors): allow any localhost/127 port on Worker; skip 0.0.0.02b09f248(2026-07-28) — docs(license): AGPL-3.0-only (not or-later)8fe2058f(2026-07-28) — docs: cross-link HOOX / pyne / axis with hoox.sh websites
Tests
1e314369(2026-07-29) — test(engine): assert REST /run body includes execution mode
Chores
42bf3ba9(2026-07-29) — chore(pyodide): vendor latest pyne wheel and sync bridgee6040569(2026-07-28) — chore: bootstrap standalone axis repo from pyne frontend/8dff3a71(2026-07-28) — chore(license): relicense to AGPL-3.0-or-later under jango_blockchained202a0499(2026-07-23) — chore: switch frontend + worker to Bun + TypeScript 6
Style
1ee7f8ea(2026-07-26) — style(axis): larger drawing toolbar icons432dcf51(2026-07-26) — style(axis): swap Hell Flieder for void indigo from landing pack
Other
514b90d1(2026-07-24) — Add copy buttons with SVG icons to status bar + results panels
2026-01 (1 commits)
Features
bb224279(2026-01-13) — feat: Google Server Setup and Closed Source Transition
2025-10 (2 commits)
Features
38a00b82(2025-10-24) — feat(script): Add overlay series for line chart and improve data handling
Other
5613175b(2025-10-20) — Add technical analysis built-ins and corresponding tests
How to update
- Edit [Unreleased] (or the new version section) by hand for the story.
- Regenerate the recursive history block:
python3 scripts/generate-changelog.py
- Commit changelog with the release; then tag, push, publish, sync
(see
AGENTS.md§ Changelog & releases).