Bicrypto 6.7.1

24 August 2026

This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.

ORDER BOOKCHART DATAMARKET DATASEARCHPAGESNAVIGATIONADMINPROFILEPRESENCELANDING-PAGEI18NUITRADING PRO

Core v6.7.1

Release Date: August 24, 2026 Tags: ORDER BOOK, CHART DATA, MARKET DATA, SEARCH, PAGES, NAVIGATION, ADMIN, PROFILE, PRESENCE, LANDING-PAGE, I18N, UI, TRADING PRO

Overview

A market-data release with a user-interface release wrapped around it. Two things a trader reads constantly were rebuilt: the chart's candle cache and the order book.

The spot chart's candle cache had four defects that each produced a chart which looked plausible and was wrong - it stored bars that had not finished forming and then refused to correct them, and it deleted doji candles as "anomalies", replacing a real bar with a flat line. Read Upgrade Notes before you update: candles already on your disk may be wrong, and there is a repair to run from the admin console.

The order book was gone through end to end - both terminals, the browser's connection to the market feed, and the three servers that publish a book. Some of what turned up had been wrong since the panel shipped: a grouped book could show a best bid above its best ask, the Pro terminal's ask ladder was hiding the ten levels nearest the spread including the best ask, one trader leaving a market stopped the feed for everyone else on it, and the futures order book had never reached a browser at all - it was addressed with a key no client has ever registered, and a broadcast that matches nobody raises no error anywhere. On a quiet market the book could be minutes stale, because it was fetched together with the trade tape and waited for it.

Alongside the corrections, the book gained the things a trader reaches for: click a level to fill the ticket, grouping that fits the market's own scale, the spread and mid between the ladders, hover to see what sweeping to a level would cost, and bids-only / asks-only. There is no migration and no configuration change for any of it.

The rest is what a visitor and an operator gain. A search box in the header answers "what can I do with this token?" across every feature you run. Add a page lets you write a page, give it a URL and link it from the header or footer without touching the menu editor. The profile dropdown becomes a full account panel - balance, security score, and the destinations that are not in the nav - and a presence dot says whether a person is here, around or long gone.

Eight more languages are genuinely translated, and the binary pattern library more than doubled. No money movement or permission changes, and there is no migration.

One thing to expect rather than to act on: if you run futures, the order book on those markets will start showing data where it has always been empty.

Update Instructions

pnpm updator

Restart the backend and ship the frontend build. The search endpoint, the rewritten chart cache and the order-book feed changes are on the backend; everything else is client-side.

Requires no configuration change. Read Upgrade Notes first - the chart cache entry asks you to run a repair from the admin console after the restart.


Upgrade Notes

Candles already in your chart cache may be wrong, and there is a repair to run

This is first because it is the only entry that says a number you have already shown a customer was not true.

The spot chart's OHLCV cache stored the bar that was still forming. A bar captured part-way through its own minute is a prefix of itself - its high is not yet the high, its close is not the close - and the merge that wrote it kept the copy that was already on disk whenever the same bar came back, so re-fetching could never correct it. Every mis-captured bar was permanent.

Measured on the platform's own BTC/USDT one-minute cache - 59,723 bars, December 2025 to August 2026 - 364 bars had a broken seam, meaning the next bar opened somewhere other than where they closed. 240 of those opened outside the stored bar's own high and low, so price demonstrably went where the stored bar says it never did.

A second filter deleted candles whose wick exceeded twenty times their body, calling them anomalies. That is not a description of bad data, it is the description of a doji - and the platform's own pattern library teaches people to look for one. A real bar with a 0.023% lower wick and 4.9 BTC of volume reached the browser as a flat line with zero volume, because a third routine then interpolated across the hole the filter had just made.

All of that is gone. Both defects are repaired for new data automatically; the bars already on disk need one pass:

  • Repair your cached candles after updating. The chart console has no menu entry of its own. Open Finance → Trading Infrastructure → Exchange Providers and press Charts in that page's Quick Actions grid, or go straight to /admin/finance/exchange/chart. Expand a market and press the spanner on each interval you care about. It now re-fetches bars that are present but discontinuous, not only the ones that are missing - which is the only way a bar captured mid-formation can be found, because a gap check cannot see it. The spanner is offered whenever a series holds data, where it used to appear only when the gap count was non-zero: a gapless series can still hold wrong bars, so the only repair path was hidden from exactly the series that needed it.
  • Expect the repair to take a while on a long one-minute history, and expect it to spend exchange API calls. It is paced to stay inside your venue's rate limit and can be run again; progress is durable.
  • If you would rather start clean, the page's Clean action now clears the cache file and the Redis mirror together. Clearing one alone accomplished nothing, because the other rebuilt it on the next request.
  • Connecting a spot exchange provider documents the console in full — what Build, Clean and Fix each do, what the cache will and will not store, and the other three exchange screens that have no menu entry either.

The right edge of the chart now ends at the last closed bar

The forming candle is no longer served by the history endpoint at all. It arrives on the market socket, which replaces it on every tick with the venue's own running aggregate. Serving a REST snapshot of the same bar alongside that gave the chart two competing versions of one candle - and the cache then froze whichever lost the race.

There is nothing to do about this, but the chart's right edge will look different: it consistently ends at the last bar that has actually closed, instead of sometimes carrying a partial one and sometimes not.

A gap in history is now drawn as a gap

Missing candles used to be invented - interpolated across, or filled with flat synthetic bars at the end of a series. Nothing is fabricated any more. A market that genuinely has no trades in a period shows a break in the series, which is what the chart already draws correctly and what an honest chart should show.

nginx has to allow a larger request body than it did

client_max_body_size 10m was right while every route shared the 5 MB platform floor. Routes that carry a file as base64 inside JSON now declare their own ceiling — P2P dispute evidence allows 36 MB so a screen recording fits — and whichever of nginx and the backend is smaller is the one that decides. nginx refuses with an HTML page the frontend cannot parse, so a customer sees an upload that spins and then fails with no message at all.

client_max_body_size 40m;

Then nginx -t && systemctl reload nginx. Set it at server level, or scope it to location /api/ if you would rather keep the rest of the site tight. The per-route table is in the nginx guide.

Presence adds a presence:<userId> key per active user in Redis, written at most once a minute and expiring after four days. It is a cache — flush it and presence falls back to sign-in time until people browse again.

CMS pages that used to 404 will start serving

Pages held in the CMS table were reachable only where the landing-page type had been switched to the page builder. On a stock install that setting is unset, so every such page answered 404 — including one an owner had just created, which is how the defect was found.

The gate belonged to builder documents and has been moved onto them, so builder pages behave exactly as before. A CMS page whose body is plain HTML is not a builder page and now serves on any install.

  • Look at your CMS pages before you update. If your install carries rows nobody has seen in a while because they were unreachable, publishing state is now the only thing standing between them and the public site. Unpublish anything that should not be live.

Added

One search box over every feature a token is offered in

A visitor who has heard of a token had no way to ask the platform about it. The markets page lists spot pairs, staking lists pools, P2P lists offers — each answers for itself, and none of them answers "where else is this?". Someone who wanted to know whether you offer staking on a coin they hold had to visit every section and check.

The header now carries a search button. It opens a panel with the field inside, and typing a symbol returns one grouped answer: Spot, Futures, Native tokens, Binary, Forex & Multi-Asset, Staking, Token sales, P2P, Copy trading, Trading bots, Earn and Swap, each with the matching rows and a link through to the feature, followed by deposit and withdraw for the token itself. Market rows carry a live price and 24-hour change.

BTC, btc/usdt, BTC-USDT and BTCUSDT are all the same question, and a half-typed symbol still answers, so the panel is useful before you finish typing.

Opened with an empty field it shows quick links to the features you run, the markets you have flagged Trending or Hot in the market editors, and the visitor's own recent searches.

  • Added GET /api/search, a public endpoint that resolves a token and reports every enabled feature offering it. A feature contributes results only when its extension is installed and enabled — the same gate the menu uses — so a group that is missing is missing because you do not run that feature.
  • Added the header search control to the Classic and Stacked navbar layouts. Centered and Minimal declare no room for it and do not show it; the layout picker says so on the card before you choose.
  • Added keyboard operation throughout — arrows walk one list across every group, Enter opens, Escape closes.

Add a page to your site

The Pages screen offered five pages — home, about, privacy, terms and contact — and no way to make a sixth. An owner who wanted a Cookie Policy, a fee schedule or an "About our licence" page had the page builder, which is a different tool for a different job, or nothing.

Default Pages → Add page now creates one. Give it a title, and the URL fills itself in and is checked against everything already on the site while you type, so a collision is a red line under the field rather than a save that fails. Two checkboxes put a link in the site menu and in the footer at the same time.

The page then opens in the same editor as the built-in five: a live preview, an HTML tab, an SEO panel and a publish switch. Your own pages sit in their own group in the sidebar, apart from the built-ins. Deleting one removes the row and tidies away the menu and footer links it added.

Links are written as ordinary custom menu items into the same documents the Menus and Footer editors use, so anything created this way can be moved, renamed, given an icon or removed there afterwards — it is not a second, invisible source of links.

  • Added the Add page dialog, with live URL availability checking and the option to link the page into the header menu, the footer, or both.
  • Added owner-added pages to the Pages studio and its editor, including the URL field, the publish switch, SEO fields and delete.
  • Added rendering for them on the public site, styled by the same rules the editor previews them with, so what you compose is what visitors get.

Twenty-six illustrated portraits have shipped in the platform since the first release and nothing referenced them. The only profile picture a customer could have was a photo they went and found, and clicking the avatar opened the file dialog immediately — one option presented as though it were the only one.

Clicking the avatar now offers Choose avatar and Upload photo. The gallery marks the one in use, saves on the click, and updates the header and sidebar at once. Upload is unchanged: PNG, JPG or WebP, resized to 400px.

  • Added the avatar gallery, reachable from the profile hero and from the Personal information card.
  • Added the same picker to both of those places from one component, so the upload rules cannot drift between them.

An icon picker for menu items

The menu model has always been able to store an icon per item, the navbar has always drawn one, and the documentation has always said you could set it — but no control ever wrote the value. It was reachable only by editing the stored document by hand.

Press the pencil on any row in Menus and the control to the left of the label box is the icon picker, sitting where the glyph sits in the rendered menu. It opens a searchable grid of roughly 350 icons; the search matches whole words, so credit card finds the card. The first tile is No icon, which removes one. The same picker sits beside the label on the Add item form, so an item you create can arrive with its icon.

Every row in the editor now draws its own glyph, so you can see the menu you are building.

  • Added the icon picker to the menu and footer-link editors, for shipped items and for items you add.
  • Added the icon column to the editor's rows.

"Coming Soon" for the mobile app band

The home page's Mobile app band renders nothing unless an App Store or Google Play link is set in system settings — which is exactly the state of an operator whose app is not built yet. The band an owner most wants while an app is in review was the one they could not show.

Default Pages → Home → Mobile app gains a Show Coming Soon switch. It replaces the two store buttons with a single plate carrying both store marks, reads Coming Soon unless you write something else in the field below it, and drops the "Scan QR to Download" line because there is nothing to scan yet.

It also wins over links that are set, which is the point of it being a switch rather than a consequence of the links being empty: you can hold the buttons back during a store review without deleting the URLs and having to find them again.

  • Added the Show Coming Soon switch and its Coming Soon text field.
  • Added the ability for that band to render with no store link configured.

The profile dropdown becomes an account panel

There were two signed-in profile dropdowns - one on the core routes, one on the binary, Swap and Pro terminals - and they had independently converged on the same four lines: name, email, Profile, API Management, Logout. The control carrying a person's entire account context was worth less than the notification bell beside it, twice over.

Both are now one panel, and it answers the questions a signed-in person actually has, in the order they ask them: who am I here (identity, public handle, role, account id, joined date), what do I have (total balance, its 24-hour move, unconfirmed deposits), am I safe (a security score, whether 2FA, email, phone and KYC are done, last sign-in), and where do I go - the destinations that are not in the navigation: deposit, withdraw, transfer, transaction history, referrals, API keys, sessions, support, and the theme switch.

  • Added the account panel, replacing both dropdowns from one component.
  • Added the same panel to the Forex & Multi-Asset terminal, which had no way to reach a profile at all - see that addon's own release note.

The binary pattern library more than doubled

The education page's chart-pattern library went from 62 patterns to 138. The new families are the ones a learner looks for and could not find: the three-bar confirmations, the gap and continuation patterns, the one- and two-bar reversals, the price-structure patterns and the remaining harmonics.

Each pattern now also marks which candles make it. A reversal has to show the trend it reverses, so the series around it is context - and without the marking a learner saw twelve candles with no clue which two were the pattern. The detail view reveals the series left to right, then holds the pattern's own bars lit while the rest recede.

  • Added 76 chart patterns, bringing the library to 138.
  • Added key-candle highlighting and the animated reveal in the detail view.

Eight more languages are genuinely translated

Persian, Arabic, Bengali, Bulgarian, Assamese, Greek, Amharic and Fijian were each about 53% untranslated - better than half of every string on the site was still sitting there in English for anyone who picked one of them. Six of the eight are now at 1% or below; Amharic is at 9% and Fijian at 17%.

  • Added roughly 11,400 translated strings to each of the first six catalogues, 9,600 to Amharic and 7,800 to Fijian.

Your chart layout follows your account, not your browser

The chart engine keeps drawings, indicators, templates, alerts and favourites in the browser. Core now stores a copy per account, so a second machine or a fresh browser opens onto your own markup instead of a default chart.

  • Added the chart_workspace table and the endpoints behind it. The server fills gaps and never overwrites what the browser already holds, and only changed keys are sent. See the Chart Engine release note for how the chart uses it.

A dot that says whether somebody is there

Every surface that showed a person showed them identically whether they had been online for an hour or absent since March. Three separate components drew a presence dot of their own and no two agreed on the rule; the P2P one drew a green dot when a trader was online and nothing at all otherwise, so idle and abandoned looked the same.

There is now one component with one rule: green within five minutes, grey between sessions, red after three days, and nothing when there is no reading at all — an honest gap beats a plausible wrong colour. Every dot carries its wording as an accessible name, so it means something to a reader who cannot see it.

  • Added the dot to blog post bylines, the author box, the comment list and the author profile page.
  • Added a request heartbeat, which is what makes any of it true. Presence used to come from the last sign-in, and a session lives a fortnight — so somebody reading a page right now was reported as last seen the day they typed their password. It is stamped on ordinary browsing, at most once a minute per person, and never on API-key traffic: a cron job says nothing about whether its owner is at a screen.

Click a price in the order book to fill your ticket

Every row of the order book has always looked like a control - the pointer changed over it and the row lit up - and on the standard terminal clicking one did nothing whatsoever. The affordance was a promise the panel could not keep.

Clicking a level now fills its price into the order form. Hold Shift (or Alt) while you click and it fills the size as well, using the running total down to the level you clicked, which is the size you would need to reach it. That is also what the third column of the book has always been telling you, so the number you were reading and the number you get are the same one.

  • Added click-to-fill to the standard terminal's order book, for the Limit and Stop tickets. On a stop-limit both legs start from the level you picked.
  • Added size-filling to the Pro terminal's order book, which already filled the price.

The order book groups prices to the market's own scale

The grouping menu offered eight fixed steps from 0.000001 to 10. On an asset priced near $100,000 the default and the first four options are all the same thing - no grouping - and on a token priced at three hundred-thousandths of a cent the coarsest option folds the entire book into a single row. The Pro terminal's order book had no grouping control at all.

The steps are now derived from the market's declared price precision and its current price, so a large-cap quoted to two decimals offers 0.01 / 0.1 / 1 / 10 / 50 / 100 and a sub-cent token offers steps inside its own scale. There is also an explicit None, which is every price exactly where it rests.

  • Added a grouping control to the Pro terminal's order book.
  • Changed the standard terminal's grouping menu to steps derived from the market, re-derived when you change market.

The spread, the mid price, and which side is carrying the size

The side-by-side order book showed neither the last traded price nor the spread

  • the last-price banner was drawn only in the narrow, stacked layout, and no spread was calculated anywhere on that screen. The two ladders sat next to each other with nothing between them saying how far apart they were.

A strip between them now carries the last trade with its direction, the spread in both the quote currency and percent, and the mid price. Under the book, a two-tone bar gives the share of the displayed depth resting on each side, with both totals - the fastest read there is of which way a market is leaning.

  • Added the spread strip and the bid/ask imbalance bar to the standard terminal's order book.
  • Added the mid price to the Pro terminal's spread strip, and made that strip visible in bids-only and asks-only view - it is also the only place that panel shows the last traded price, so choosing one side used to take the price away with it.

Hover a level to see what reaching it would cost

Pointing at a level highlights everything between it and the touch of the book, and a readout under the ladder gives the total size, the total value and the average price you would pay to sweep that far. It answers the question the running-total column raises without asking you to do the arithmetic.

  • Added the sweep highlight and readout to the standard terminal's order book, on hover and on keyboard focus alike.

The depth bars move with the book

Depth bars ease to their new width instead of jumping, so the shape of the book changes smoothly as liquidity moves. Each side is measured against its own largest row, so a lopsided book - one side carrying eight times the other is an ordinary morning - keeps both ladders readable rather than pinning one at full width and leaving the other blank.

Individual price levels deliberately do not flash when their size changes, which is worth saying because some venues do it. On these markets the resting sizes are adjusted continuously, so a per-level flash would fire on most of the visible rows most of the time — constant movement that carries no information. The motion here tracks a quantity you can read rather than announcing an event.

  • Added eased depth bars, measured per side.
  • Added full respect for the operating system's reduce-motion setting across the panel.

Bids only, asks only, or both

  • Added a display-mode control to the standard terminal's order book, matching the one the Pro terminal has.
  • Added keyboard access to every level in both books. A row is a focus stop, Enter or Space fills the ticket the way a click does, and each row reads out as one phrase - "bid 68,412.50, size 0.42" - rather than as a tour of three separate cells.

Changed

The blog publishes whether an author is around, never when they signed in

A trader advertising on a public board has chosen to say when they were last seen. An author did not put themselves on a blog so anonymous readers could learn what time they sat down at their desk, and a commenter certainly did not.

  • Changed the public-payload redaction pass to convert lastLogin into a three-state presence bucket and delete the timestamp. It runs on every public blog endpoint already, so a query that adds the column gets the dot and cannot leak the instant.

The avatar and menu editors write only what you changed

  • Changed the menu editor to store a renamed label only when the text actually differs from what ships. Setting an icon and saving used to store the shipped title as a rename, which badged the item Renamed and — more quietly — pinned that wording: an admin label opts out of translation by design, so the item would have kept the old text through a release that renamed it and in every language a visitor selected.

What survives sanitising on a page you wrote yourself

The five built-in pages are produced by a template wizard that styles everything with classes and never emits an inline style, so the sanitiser that cleans them strips one. A page typed into the rich-text editor, or pasted in from a document, carries inline styling on nearly every element — and removing it there deletes the owner's own formatting and tells them nothing.

  • Changed the sanitiser for owner-added pages to keep inline style. Scripts, iframes, embeds and objects are still refused, event handlers and javascript: URLs are still removed, and the five built-in pages keep the stricter policy they had.
  • Added an @import strip to page CSS and to <style> blocks. A stylesheet import is an outbound request made on behalf of every visitor, and it is the one way styling could arrive on a published page without passing through any of the above.

How the chart cache decides what to fetch, and how it stops

Three of the read path's decisions were the wrong way round, and each showed up as a broken chart rather than as an error.

Gaps were filled oldest first, so a market that had not been polled for a month spent its whole budget on history nobody was looking at and the visible right-hand edge was the last thing repaired - usually never. Filling now starts at the newest gap, because that is where the viewport is.

The whole operation was wrapped in a 20-second timeout that threw when it expired, so a large gap produced a failed request, then another, then another - each doing real work, none of it kept, because the answer was discarded every time. It now stops at a request count and a wall-clock budget and returns what it has; progress is saved, so consecutive requests converge.

Request coalescing was keyed on the raw millisecond window, which differs on every request the chart makes, so it never once produced a hit.

  • Changed gap filling to work newest-first, within a bounded budget, keeping partial progress instead of discarding it.
  • Changed the cache to merge onto the file on disk rather than onto whatever Redis happened to return. Redis returning nothing - an eviction, a restart, or a timeout - used to replace the entire on-disk history with the 500 bars being saved.
  • Changed cache writes to go through a temporary file and a rename, so an interrupted write cannot leave an unreadable archive where the history was.
  • Changed a cache file that cannot be read to report itself as unreadable rather than as empty. Those are not the same answer: a save merges onto what the read returns, so "empty" made a momentary read failure permanent.

The admin chart console reports on every interval the cache holds

The console's interval list was hand-written and omitted 3m and 8h - both of which the build job writes and both of which exist on disk. Those files counted towards nothing and showed nowhere, on the one page whose entire job is to describe the cache. A second hand-written table treated an unrecognised interval as one minute, so such a series reported a gap between literally every pair of bars: a cache in perfect condition reading as entirely broken.

The build job's own default list omitted 1w, so a weekly chart was never pre-built and had to construct its whole history through on-demand fetching, one request at a time.

  • Changed the console, the build job and the clean job to share the cache's own interval set. All fourteen intervals are now reported, built and cleaned.
  • Changed the build job to save each batch as it arrives. A build of a year of one-minute data is thousands of batches, and an interrupted job used to leave nothing behind.

Symbols are checked before they become a path on disk

The cache writes to data/chart/<base>/<quote>/, and both halves of a symbol became directory names. manage.exchange.chart is permission to maintain the cache, not to choose where on the filesystem a file lands.

  • Changed every route that names a series - the public chart endpoint and the three admin ones - to require the canonical BASE/QUOTE shape before building a path.

The Pro terminal expands only the panels that gain from the room

Markets, Order Book and Trade are fixed-width columns, and everything drawn in them is laid out for that column — a market row with a name at one end and a price at the other, a price ladder whose depth bars are drawn as a share of the row's width, a stacked order form. Filling the screen with one of those shows nothing more than the column did; it shows the same figures with a hand's width of empty space through the middle of every row. Orders and Positions are the full-width tables under the chart, and they are the ones with columns waiting for space, so they are the two that keep the control.

  • Removed the expand control from the Markets, Order Book and Trade panels. Collapsing them to the edge is unchanged.
  • Changed Orders and Positions so that expanding one restores the other. The workspace can only ever have a single panel filling it.
  • Added Escape as a way back out of an expanded panel, alongside the button in its header.

The order book redraws once per screen frame

Both order books called for a redraw once per message from the venue. A liquid market pushes a book far faster than a screen can show one, so the cost of the panel was set by how busy the market was rather than by anything anybody could see - and a 50-row grid was being rebuilt tens of times a second to produce frames nobody would ever look at.

The book is now assembled at most once per screen frame. The visible behaviour is identical and the work is bounded by the display. It also means a background tab costs nothing: the browser stops handing out frames to a tab you are not looking at, so six markets open behind your current one stop consuming anything until you switch to them.

  • Changed order-book ingestion to draw on the display's clock.
  • Removed roughly 150 lines of throttling machinery in the standard terminal's order book that had no callers at all - it had been left behind when the code it served was replaced, so the panel was running with no throttling of any kind while appearing to have some.

Depth bars measure what the row prints

A depth bar is a share of something, and it has to be a share of the number printed next to it. When the third column showed the running total the bars were still sized against the largest single level, so the widest bar could sit beside a number that was not the largest. The bars are now measured against whichever quantity the column is showing, over exactly the rows on screen.

  • Changed how depth bars are scaled in both order books.

Fixed

Switching to TradingView on a 2-hour chart showed the wrong timeframe

2h was the one interval missing from every resolution table in the TradingView adapter, while the chart toolbar and every backend already offered it. Selecting it fell through to the default resolution, so the chart quietly showed a different timeframe from the one labelled above it.

  • Fixed the 2-hour timeframe on TradingView charts.

The chart panel was blank while the chart engine loaded

The panel that dominates the trading screen rendered nothing for the whole download of a ~1.2 MB chunk - no spinner, no text. On a cold cache or a slow connection that is a long time to look at a blank rectangle, and blank is the one state a user cannot tell apart from broken. The same panel already showed a spinner while the TradingView script loaded, so the two waiting states also disagreed with each other.

  • Fixed the chart panel showing nothing while the engine loads. It now shows the same waiting state the TradingView path uses.

A superseded request kept running after the chart had moved on

The platform's request helper had no way to accept a cancellation signal, and several callers were written as though it did - building one, checking it afterwards and tearing it down on cleanup, with nothing to hand it to. Switching symbol mid-flight, panning, or leaving a panel discarded the result correctly; nothing stopped the work, so it held a connection from the browser's per-host pool and delayed the request the user was actually waiting for.

  • Fixed requests being cancellable. Callers that already build a signal now actually abort.

The order ticket's warning sat below the button and moved it

Submit errors and validation warnings in the Pro terminal rendered below the Buy/Sell button, in a slot held open whether or not there was anything to say - because a message appearing after the button pushes the button up as you reach for it. The reservation left the button floating 28px above the panel edge at all times.

  • Fixed the reserved gap under the order button. The message is now above the button, which grows the bar upward instead of moving it, and the reason to read a warning before pressing Buy rather than after is its own argument.

"Log In" and "Sign Up" could wrap onto two lines

Adding a control to the header squeezed the signed-out call-to-action buttons until their labels broke across two lines, which grew the whole bar. The two buttons a signed-out visitor is there for now hold their size and the flexible controls beside them give up the width instead.

  • Fixed the auth buttons reflowing when the header runs short of room.

A refused search left the previous results on screen

Every query parameter on every route is screened for injection patterns before a handler runs, and one of those patterns matches ordinary English — a search containing both a word like delete and a word like from is refused. The search panel now clears its results and says the search could not be run, rather than leaving the previous query's rows under the new text where they read as a confident wrong answer.

  • Fixed the search panel keeping stale results when a query is rejected.

Opening an added page in the Page Builder replaced it with a blank one

Both kinds of page live in the same table, so the builder's URL would open one it cannot read. It parsed the HTML body as a builder document, got nothing, and showed an empty canvas — and one Save, or one Ctrl-S, wrote that empty document over the owner's page. There is no undo for it.

  • Fixed the builder refusing to open a page that holds HTML. It says so and sends you to the editor that can edit it.
  • Fixed the save path as well as the load path, because the keyboard shortcut can fire before the redirect lands.

More than about six rules of page CSS was rejected

The Custom CSS and Custom JS fields are stored in columns that hold documents, and the request validator capped them at 255 characters — a default inherited from fields meant for names and titles, stated nowhere the owner could see it. Writing a real stylesheet produced a validation error naming a field they had every right to fill.

  • Fixed the caps on page CSS, page JS and page settings, now 64 KB each.
  • Fixed the same inherited cap on SEO keywords and the Open Graph description, now 1,000 and 500 characters.

CSS written at the top of a page vanished on save

A <style> block at the very start of a document is moved into the document's head by the HTML parser, which put it outside the part the sanitiser returns — so an owner who opened the HTML tab and put their CSS first, which is where anyone would put it, watched it disappear with no error anywhere.

  • Fixed the leading <style> block being dropped. Position no longer decides whether the styling exists.
  • Fixed a page whose whole body was removed by sanitising answering 200 with a blank column between the header and the footer. It is a 404, which is what it is.

The page list ignored the page-size picker

/admin/builder asked for its rows with the wrong parameter name, so the list served the default page size whatever the picker was set to.

  • Fixed the page-size picker on the page list.

Save stayed greyed out after retyping an added page's URL

The editor decides whether there is anything to save by comparing the draft against what was loaded, and the URL was not in that comparison. Changing it left Save disabled with nothing explaining why, and leaving the screen discarded the edit without even the usual prompt.

  • Fixed the URL counting as a change.
  • Fixed a duplicate URL surfacing as a generic server error with no mention of the URL, on a collision the screen was already showing in red.

Geo-restriction told correctly configured servers to change their proxy setting

Forwarding headers are honoured from a proxy on the same machine with no configuration at all, but the settings screen read the absent environment variable as "switched off" and advised every such install to set TRUST_PROXY=true — which is the one mode that believes a forwarding header from a public peer. A second banner warned that visitor countries were wrong on every healthy same-host nginx or Apache install.

  • Fixed the first warning to appear only where TRUST_PROXY=false has actually been set, and to say what to do instead — remove it, or list the proxy's address in TRUST_PROXY_CIDRS if it sits on another host.
  • Fixed the second warning firing on a loopback peer, which is the normal shape of a proxied request. It now appears only when forwarding headers arrive and the address the engine ends up with is still a private one, which is the condition that genuinely means your visitors are being geolocated as your proxy.

Expanding a panel in the Pro terminal produced a narrow strip

Every panel with a header offered the control and not one of them expanded. The panel was told to cover the screen, but the rule that gives it its place in the layout still applied and won — so each column kept its own narrow width and merely grew to the full height of the display, and Orders, which is a row rather than a column, kept its shallow height and grew to the full width. All of them were placed over the terminal's own header, so the symbol, the price and the way back out went underneath the panel you had just expanded.

  • Fixed an expanded panel filling the workspace exactly — the whole area below the header, which stays visible and usable above it, the way the pattern library and the analytics and news views already did.
  • Fixed the contents of an expanded panel keeping their old dimensions. The chart, the order book and the tables measure their box once and re-measure only when told to, so the frame grew and nothing inside it followed.
  • Fixed several panels being expanded at the same time. Each one covered the workspace and only the last of them was reachable, so the ones underneath stayed expanded and sprang back the next time they were clicked.

The Pro terminal's fullscreen button always showed the same icon

It drew the enter-fullscreen icon whether or not the terminal was in fullscreen, and named itself "Fullscreen" either way — so while you were in fullscreen the one control that leaves it was labelled as the one that enters it. The browser also leaves fullscreen on Escape and on F11, which means the button could not be trusted even as a record of what you had last clicked.

  • Fixed the button showing the leave-fullscreen icon and label while the terminal is in fullscreen, and following the state when you leave by Escape or F11 rather than by the button.

A grouped order book could show a best bid above its best ask

Grouping rounded prices on both sides downward. At a grouping of 1, an ask at 100.40 and a bid at 100.20 both land on 100 - so the panel drew a book whose best bid equalled its best ask, which is not a market that can exist. Bids now round down and asks round up, which is the only pairing that cannot cross.

  • Fixed the grouped book crossing itself.

The Pro terminal's order book hid the ten ask levels nearest the spread

The panel asked the server for 25 ask levels, reversed them so the best would be drawn nearest the spread, and then cut the list to 15 - from the wrong end. The fifteen rendered rows were the fifteen worst asks and the ten closest to the spread, including the best ask, were never drawn at all. The running total was accumulated over the same reversed list, so the depth column counted outward from the worst displayed level rather than from the touch of the book.

  • Fixed the ask ladder showing the wrong fifteen levels. The order the rows are drawn in is now a layout decision and cannot reach the arithmetic.

The futures order book never reached anyone

Both publishers of the futures book addressed their broadcasts with a message key that no client has ever registered, and labelled the frame with a name no client listens for. Two independent mismatches, either of which alone was enough: the futures order book has not reached a single browser for as long as it has shipped, and nothing logged an error, because a broadcast that matches nobody is not a failure anywhere in the chain.

Frames are now addressed with the subscriber's own key, so a broadcast cannot fail to match the socket it was built for.

  • Fixed the futures order book not arriving.
  • Fixed futures price levels being computed through a conversion that loses precision above 2^53, which produced level prices a fraction off the ones the rest of the system uses.
  • Fixed futures book levels being sent in the order they happened to be stored rather than in price order.

One trader leaving a market stopped the book for everyone else on it

The upstream feed for a market is shared by everybody watching it, and the first UNSUBSCRIBE tore it down regardless of how many others were still there. Two people on ETH/USDT, one navigates away, and the other's book quietly freezes with no error anywhere.

Whether a feed is still wanted is now answered by asking which sockets are actually connected, rather than by a tally kept alongside them. The same change closes a second hole on the public market feed, where a client could stop a market's book for everyone by sending unsubscribe messages for a market it had never subscribed to.

  • Fixed a market feed stopping while people were still watching it.
  • Fixed an unauthenticated client being able to stop a market feed it was not subscribed to.

On a quiet market the order book could be minutes out of date

The server fetched a market's book, its ticker and its trade tape together and waited for all three before sending any of them on. Each of those only answers when the venue has something new to say for that particular stream — so the book advanced at the rate of whichever stream was quietest. On a market that had not printed a trade for five minutes, the ladder on screen was five minutes old, and nothing about it looked stale.

Each stream is now fetched independently, so a quiet trade tape cannot hold up a busy book.

  • Fixed the order book advancing only as fast as the slowest unrelated stream on the same market.
  • Fixed provider failures never being acted on. The error handling around that combined fetch could not be reached — so when a venue started refusing us, nothing recorded it, nothing backed off, and the connection was never re-established.
  • Fixed the public order-book endpoint accepting any depth at all from an anonymous caller, and passing a non-numeric one straight through to the venue.

A market nobody was watching kept being polled

No market-data route noticed a socket closing without a polite goodbye - a crashed tab, a phone that lost signal, an app the operating system froze. The market stayed in the poller's list and kept being fetched from the venue for the life of the process, and on two of the three routes the timer that did it could never be cancelled by anything.

  • Fixed abandoned markets continuing to be polled. A market is dropped as soon as its last live listener is gone, however that listener left.

Switching market could draw the previous market's book

Order-book and ticker frames were deliberately not checked against the market you were looking at, on the reasoning that the subscription had already filtered them. It had not: the routing label carries the depth but not the market, so every order book on a page shared one delivery path. Worse, a frame that arrived without naming its market had the reader's own market stamped onto it, turning "we do not know whose this is" into "this is definitely yours".

  • Fixed one market's book being drawn under another market's name.

Two views of the same market at different depths left one of them blank

The standard terminal asks for 50 levels and the Pro terminal for 25. Those were treated as the same subscription by the browser and as the same subscription by the server, while being delivered on two different channels - so whichever was opened second was silently dropped, on both ends. The same flaw stopped a second market at the same depth from ever being subscribed.

  • Fixed two depths on one market, and two markets at one depth, cancelling each other out.
  • Fixed the browser keeping a message listener for every order book that had ever been opened. Nothing removed them, so a long session accumulated listeners that went on decoding every frame for panels that had closed.
  • Fixed a cached book with no age limit being handed to a panel as though it had just arrived. A remount still gets the last frame immediately; anything older is discarded rather than drawn as live.

The depth chart's mid line and hover readout were wrong

The depth chart derived the market's mid price from the width of its own axis rather than from the book, so the dashed "mid" line was painted at the exact centre of the chart whatever the spread was doing, and the hover readout used that same false mid to decide whether you were pointing at bids or asks. The bid-side lookup was also inverted, so it reported the total bid depth wherever the pointer was.

  • Fixed the mid line, the bid/ask split and the hover volume.
  • Fixed a one-sided book mirroring the whole chart.
  • Fixed the chart not re-fitting when its panel was resized - it only re-measured when new market data happened to arrive.
  • Fixed the chart reallocating its drawing surface on every incoming frame.

A crossed book was shown as the tightest possible spread

When the two sides of a book overlap - always a fault somewhere upstream - the spread is negative, and the Pro terminal's spread widget had no branch for a negative number. It fell through to its smallest-value case and printed <0.01bp: the one condition a trader most needs to be warned about, displayed as the best spread the widget could show. A crossed book is now named as one, in both books.

  • Fixed a crossed book being reported as a vanishingly tight spread.

Prices and sizes ignored the market's declared precision

Both books picked their decimal places from how large the number was rather than from what the market declares, capping anything above 1,000 at two decimals and forcing anything below 1 to eight. A market quoted to four decimals around $2,000 had two of them rounded away, and adjacent ladder rows one tick apart could print the identical price. The Pro terminal also declared a size precision in its own interface and never supplied it, so every size rendered at a fixed four decimals.

  • Fixed both books honouring the market's own price and size precision.
  • Fixed sizes and prices not lining up in their columns.

A running total that stopped adding up

The Pro terminal's rows skipped redrawing unless their own price, size or bar width changed - but the third column shows a running total, which changes whenever a level above it moves. A level sitting still therefore kept printing a sum that no longer matched the rows above it.

  • Fixed the running total going stale.
  • Fixed rows keeping an out-of-date decimal precision when the market's details arrived after the first draw.
  • Fixed the cumulative toggle doing nothing in the short, side-by-side layout, where the running total was calculated for every row and then not drawn.
  • Fixed the Depth tab leaving the panel empty when the panel was made too short to hold it - the tab disappeared while remaining selected.
  • Fixed the Pro order book having no empty state. Once a market answered with nothing, the whole panel body rendered nothing at all, with no way to tell a quiet market from a broken screen.
  • Fixed a malformed frame from the venue being broadcast as an empty book with a fresh timestamp. Those are routine while an upstream feed resynchronises, and each one wiped every watcher's ladder and told them the emptiness was current. The last good book now stays on screen.
  • Fixed the trade tape rendering in the order the venue happened to send it, and several rows of a single sweep colliding under one identifier.
  • Fixed the trade tape printing its timestamps in US format regardless of the language the rest of the panel was in.

Depth bars are measured against their own side

Both ladders shared one maximum, so on a lopsided book — one side carrying eight times the other is an ordinary morning — every row of the fatter side was pinned at full width while the thinner side was a column of empty rows. Neither ladder said anything about the shape of its own liquidity.

Each side is now measured against itself. How the two compare is what the imbalance bar under the book is for.

  • Fixed depth bars on the thinner side of a lopsided book rendering as nothing.

A reconnect could leave a panel connected and silent

Messages sent while a socket is mid-handshake are queued and flushed when it opens - but they were flushed AFTER the reconnect had re-sent its subscriptions, and the frames waiting in that queue are the previous session's cleanups. So the last thing to reach a freshly reconnected socket was an unsubscribe for something that had just been re-subscribed. The panel reconnected, reported itself connected, and received nothing further.

The queue now drains before the new session speaks, and a socket that dies takes its queue with it.

  • Fixed a reconnect silently cancelling the subscriptions it had just restored.
  • Fixed a malformed or mixed-content WebSocket address leaving every panel on "Connecting" forever. That failure set an error state without telling anyone and without retrying, so the only evidence was in the browser console.

The mobile order book ran from page load whether or not you opened it

Mobile tabs are hidden with a style rather than unmounted, which is right for the chart - remounting it is expensive - and wrong for the order book, which opens three live subscriptions and rebuilds a ladder on every frame. Every mobile visitor paid for that from the moment the page loaded, including the ones who never opened the tab.

  • Fixed the order book mounting before the Book tab is first opened. Once opened it stays mounted, so switching back is still instant.

The last price moved sideways every time it ticked

The Pro terminal draws a small arrow beside the last traded price showing which way it moved. The arrow was only rendered when there WAS a direction, so it took up no room the rest of the time — and it appears and disappears roughly once a second as prices tick, shoving the price beside it about twenty pixels sideways each time. The number you were trying to read never sat still.

  • Fixed the direction arrow shifting the layout as it comes and goes, in both the tall and the side-by-side order book.

Two parts of the server disagreed about which price levels existed

The ecosystem order book has two independent publishers - the matching engine, which drops dust below a threshold, and the two-second poller, which sent whatever storage returned and ignored the depth the client had asked for. The same stream therefore carried two different answers, and a level the engine had just declared gone reappeared on the next poll.

  • Fixed both publishers shaping the book through one function.
  • Fixed the engine's per-socket depth having no ceiling, so a client-chosen depth could defeat the cut it exists to apply.

The futures engine scanned every market's book on every cycle

Once per matching cycle it read the entire order-book table with no filter - every price level of every futures market, competing with order placement on the same connection - to build a map it then used for only the handful of markets with queued orders. It also read only the first page of that result, so past a few thousand rows the rest of the book was silently absent and the engine matched against a book it believed was complete.

The same defect on the ecosystem side took the boot phase to the memory ceiling and was fixed there some time ago; this is the copy that was left behind.

  • Fixed the cycle reading only the markets it is about to match, and reading them to the end.
  • Fixed a crash that could land AFTER fills had been written. One of the two places that reads a market's book had the guard its neighbour has, and the other did not.
  • Fixed the forced order-book broadcast re-reading the same book once per subscribed depth.

Bot connector stream fixes

  • Fixed a lower-case market in a subscription being accepted, acknowledged, and then served a permanently empty book. Storage keys are case-sensitive, so the read succeeded and returned nothing.
  • Fixed any string being accepted as a market name. Accepted topics join a shared per-tick budget, so one connector subscribing to invented markets pushed real ones out of the rotation.
  • Fixed scheduler ticks overlapping. Each tick was started on a timer without waiting for the previous one, so when storage slowed the ticks piled up and made it slower still.
  • Fixed trades sharing the newest millisecond being dropped forever. They routinely become readable a tick later, and the cut-off excluded them permanently.
  • Fixed the bot terminal's price ladder reporting an empty market when the book could not be READ. The verdict panel beside it already said so correctly; the ladder contradicted it.

Smaller order-book corrections

  • Fixed the public order-book endpoint returning 500 for a market that does not exist, so "you asked for something that isn't there" was indistinguishable from "the venue is down". It now returns the 404 it has always documented.
  • Fixed the admin setting for the Pro terminal's depth chart doing nothing. It has been in the settings panel and resolved by the workspace since both were written, and no component ever read it - so turning the depth chart off left it on screen.
  • Removed a panel registry in the standard terminal that nothing read. The real dispatch is written out again a few lines below it, which is how a dead mapping becomes the first place someone adds a panel that then never renders.