Repository navigation
Releases: hoox-sh/axis
Release list
AXIS v2.27.0
[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.
Desktop installers (Tauri 2). CLI tarball, worker bundle, and checksums attach from the Release workflow.
AXIS v2.26.0
[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
-
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 (`enableConflat...
AXIS v2.25.3
[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.
Desktop installers (Tauri 2). CLI tarball, worker bundle, and checksums attach from the Release workflow.
AXIS v2.25.2
[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).
Desktop installers (Tauri 2). CLI tarball, worker bundle, and checksums attach from the Release workflow.
AXIS v2.25.1
[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.
Desktop installers (Tauri 2). CLI tarball, worker bundle, and checksums attach from the Release workflow.
AXIS v2.25.0
[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 tit...
AXIS v2.24.8
[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.
Desktop installers (Tauri 2). CLI tarball, worker bundle, and checksums attach from the Release workflow.
AXIS v2.24.7
[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.
Desktop installers (Tauri 2). CLI tarball, worker bundle, and checksums attach from the Release workflow.
AXIS v2.24.6
Desktop build of AXIS (Tauri 2).
- Commit: 833f0d6
- Workflow: https://github.com/hoox-sh/axis/actions/runs/39156652705
AXIS v2.24.5
Desktop build of AXIS (Tauri 2).
- Commit: d946a42
- Workflow: https://github.com/hoox-sh/axis/actions/runs/38988123441