Changelog
Every release of @hakam-aldeen-kh/blix, newest first. Blix follows semantic versioning; before 1.0, minor releases may change behavior.
The panel on the new Blix identity: graphite grounds, grey that carries, and green and red only where something is happening.
Changed
- New default themes,
blixandblix-light. A panel on the "auto" appearance switches to them on upgrade; one where a theme was chosen explicitly — Midnight included — keeps it. The other twelve themes are unchanged and still in the Appearance menu. - The launcher and header carry the Blix mark: the docked edge's blade is green, and a red dot shows while capture is running. A paused panel shows no dot. Failures never appear on the mark.
- The capture chip reads RECORDING or PAUSED, with the same red dot and an amber square.
- Request rows lead with a status dot: round when the request worked, square when it failed, hollow while in flight. Failing rows are tinted; a selected failure stays red. 2xx status codes are grey.
- Waterfall bars are one neutral colour — green on the selected row, red on failures — so they compare as lengths.
- The state filters are pills, and only the error count is coloured.
- The sidebar has new line icons for the four sources, in each source's colour, and shows how many entries failed beside each source's count.
- The detail pane colours the status code only; Replay, Pin and cURL are plain outline buttons; the active tab is underlined in the text colour.
- Menus and the command palette mark the selected item with a background step instead of the accent colour, and group labels are mono capitals.
- Empty sources show the idle mark and "Nothing captured on Network yet." The setup snippets stay.
- While dragging the launcher, each corner shows the slot it would land in, and the one under the pointer is outlined in green.
- Transitions use one easing curve: 120ms for hovers, 200ms for panels and menus. With reduced motion requested, nothing in the panel animates.
Removed
- The Redux and TanStack Query logos in the sidebar and launcher, replaced by the new source icons. This also makes the bundle slightly smaller.
Fixed
- Panels docked to the top or bottom no longer extend under the page's scrollbar, which hid the close button and the right edge of the panel.
Failures recorded whole, the browser's own timing on every request, docks on every edge — and three credential leaks closed.
Breaking
entry.errorfor a failed HTTP request is now aCapturedErrorobject (kind,name,code,message,status,statusText,timeoutMs,response,cause,stack) instead of the response body or a string. The body is onerror.response. HAR export still writes the body.- The stored error for query and mutation rows has the same shape, rather than a copy of the thrown object.
- Persisted logs from earlier versions are discarded once on upgrade. Panel preferences are kept.
- Docked panels reserve their side of the screen with inline padding on
<html>— the page is pushed over instead of covered — and no longer close on an outside click. A developer whose panel was already docked sees their page re-laid-out the first time they open it after upgrading. - Single-key shortcuts apply only while focus is in the panel.
MonitorPrefsgains requiredtopHandleftW;DockModegains"top"and"left".
Security
- Query and mutation errors, and Redux actions whose payload is an error, no
longer store the axios request config, which exposed unmasked
AuthorizationandCookieheaders in the panel, in exports and in persisted logs. Upgrade, and treat any log exported from an earlier version as holding live credentials. - String values under
authorization,cookie,set-cookieandx-api-keykeys are masked at any depth in data Blix copies — Redux payloads and diffs, query data, and everything written to IndexedDB. - "Copy as cURL", and the exported cURL script, now quote the method, header
names and every other value as single shell arguments. A request recorded
with a crafted method or header name could previously run commands when the
snippet was pasted into a shell. "Copy as fetch" serializes every value with
JSON.stringify.
Added
entry.wire: the browser'sPerformanceResourceTimingfor each request — queue, DNS, connect, TLS, wait (TTFB) and download, bytes transferred, encoded and decoded size, protocol, and whether it came from the cache. A cross-origin response withoutTiming-Allow-Originkeeps its total only and is markedrestricted. No setup: everyattach*Monitorstarts it.- React 18 is supported: the
reactandreact-dompeer range is now^18.0.0 || ^19.0.0. - The Timing tab gains "On the wire" and "Server-Timing" sections.
CapturedError,FailureKind,WireTimingandServerTimingMetrictypes, exported from both entry points.topandleftdock modes, besidebottomandright— each with its own saved size, and in the header's dock switcher and "Cycle dock position".--blix-dock-top,--blix-dock-right,--blix-dock-bottomand--blix-dock-leftCSS variables on<html>: the docked panel's size on that side,0pxon the others. Forposition: fixed/stickyelements, which the reserved space does not move by itself. Set only while a panel is docked, so read them with a fallback:var(--blix-dock-right, 0px).- Each source has its own icon and colour, in the sidebar, the launcher and the detail pane's active tab.
Changed
- The status column reads
timeoutornetworkinstead ofERRwhen there was no response, and the detail head shows the failure's own message. - The size column uses the browser's decoded size when it reports one.
- A stacked panel too short for two panes shows one at a time, with a Back button; the header, sidebar and payload bar degrade in steps at small sizes.
- Top and bottom docks respect the safe-area insets.
- Shortcuts ignore Ctrl/⌘/Alt combinations,
contenteditable,<select>and IME input. Ctrl/⌘ + backtick still toggles the panel from anywhere. - All shortcuts, including the toggle and Ctrl/⌘+K, work on non-Latin keyboard layouts: they follow the physical key, so nothing needs a layout switch.
- Escape only closes Blix when focus is inside it, and an Escape Blix handles no longer reaches the host app.
- A Redux action whose payload is an error, or holds a masked key, cannot be re-dispatched: what is stored is no longer what was dispatched.
- While another tab on the same origin is still running an earlier version of Blix, the saved log cannot be upgraded. The status bar says that nothing is being saved, and saving starts by itself — with what was captured meanwhile — once that tab is closed or reloaded.
- The theme contrast report is no longer printed to the console on every
mount. Set
globalThis.__BLIX_DEBUG__ = truebefore the panel mounts to see it.
Fixed
- axios requests no longer stay
pendingwhen the app's own interceptors reject with their own error, unwrapr => r.data, or retry under the same config: the outcome is recorded before any app interceptor runs. - A request with a timeout that is still pending 3 s past it is settled as a timeout, and corrected if an answer arrives later.
- An error thrown by a request interceptor is attributed to its request.
- A crash in the payload viewer that tore down the host app's tree when a payload arrived after the pane mounted.
- The Headers, Initiator and Query State tabs now fill the pane and scroll.
- Menus no longer show a scrollbar; Copy is no longer clipped in a narrow pane.
- Restoring a preserved log no longer drops pinned rows, open realtime connections, or entries beyond 200 when other pools are busy: hydration now applies the same per-source limits as live capture.
- Pinned rows already saved in a preserved log are no longer dropped when the log is trimmed to 200 records on load. Saving is still bounded: a pin older than the newest ~200 saved requests is evicted from storage like any other record, and does not come back after a reload.
- The panel is wrapped in an error boundary, so a failure inside Blix can no
longer take down the host app: the panel is removed until reload and the
error is logged once as
[blix] the panel crashed. A panel chunk that fails to load is caught the same way. - Blocked web storage — a sandboxed iframe, strict privacy settings — no longer throws; Blix runs with default preferences instead.
- Invalid or outdated stored preferences fall back to their defaults field by field instead of crashing the panel.
- The
rshortcut asks for the same confirmation as the Replay button before re-sending write requests. The palette's "Replay request" does too. - The Close button is shown at every panel width. Below 620px — every side dock at its default width — the header had none.
- The status bar no longer reports entries as saved when IndexedDB is unavailable and nothing was written; it says storage is unavailable.
- Reading
NEXT_PUBLIC_BLIXno longer throws "process is not defined" in production builds on bundlers without a process polyfill (Vite, esbuild, Rollup).
A launcher that says what it holds, and exporting one entry on its own.
Added
- Export the selected entry. The export menu's scope has a third option, Selected, beside Shown and All: the one entry open in the detail pane — a request, a Redux action or a query — in any of the six formats. It is disabled while nothing is selected, and a remembered Selected scope falls back to Shown rather than opening with every format disabled.
- Per-source counts on the launcher badge. The single total is replaced by one segment per source that has captured anything — Network, Realtime, Redux, Query — each with the rail's icon and colour, and its failing count in red beside it. Empty sources are left out.
- Icons in the overflow menu, one per option. Purge has its own glyph, so it no longer reads as the same action as Clear.
Fixed
- The launcher's failing count and its in-flight pulse only counted the source last open in the panel, so an error in Network did not show while Redux was the saved source. Both now span every source.
Blix on a staging server, a clear button scoped to one source, and no more hydration warning in Next.js.
Added
NEXT_PUBLIC_BLIX=truekeeps Blix in a production build, for staging and test servers. Since Next.js 16,next buildruns on Turbopack and inlinesNODE_ENVas"production"whatever the environment says, so a staging build made withNODE_ENV=development— which kept the panel under webpack — silently lost it. The panel and the capture layer both check the new variable next toNODE_ENV. Set it tofalsein the production env file: left unset, the check stays in the bundle at runtime and the panel's chunk is emitted into the build (it never loads). Your own mount component and call-site guards need the variable too.- A red clear button beside Export empties the source you are reading and
leaves the other three alone. Pinned entries survive it, and it is disabled
when the source has nothing to clear. Clear log in the overflow menu and
⇧Cstill empty every source.
Changed
- The panel opens floating by default, instead of docked to the bottom. Only the default changes: a dock position you already chose is kept.
Fixed
- Hydration mismatch in SSR hosts.
<Blix />branched ontypeof window, so the server rendered nothing and the client's first pass rendered a<Suspense>, and Next.js reported a mismatch on every load. It now renders nothing until hydration has finished, and the panel mounts on the render after.
The panel, redesigned. One token layer, a row you can tell apart from its neighbours, and columns you choose. Nothing about capture, storage or masking changes.
Added
- A single token layer. Sizes, type, spacing and radii live in
ui/styles/tokens.ts; colour is 33 slots per theme inui/themes/bx.ts, authored rather than derived. A dev-time check reports any theme missing a slot, and any theme whose--bx-fg-2or--bx-fg-3falls under 4.5:1 against a ground it actually lands on. It reports; it never corrects — half of these palettes are published and their values are deliberate. - Column control. Right-click the column head, or Columns in the overflow menu. Seven of the eight columns can be switched off and the choice persists; ROUTE cannot, because a row you cannot identify is not a row.
- The Redux Diff tab is a unified diff. Each changed path is a header;
both sides are pretty-printed and diffed by line, with unchanged stretches
collapsed and a per-path
+n −n. It replaces one line per path, which was right for a scalar and useless for an object —{"items":[…]}on both sides told you nothing. Copy now yields a real unified diff. - Launcher corners — Sharp, Soft or Pill, in the overflow menu. The launcher is the only surface that sits on the host app's page rather than inside the panel's own chrome, which makes it the one radius worth handing over.
- Clicking the status bar's pinned count goes to that entry, switching source if it is in another one, and cycles on repeat clicks.
Changed
- The row is nine tracks: rail, pin, method, route, size, status, time,
links, timing. ROUTE renders the whole route with the module dimmed and the
identifier bright, so four rows against
getAllare four different rows rather than one repeated four times. PIN and LINKS hold their width whether or not they have anything to show. - The list is an ARIA grid with a roving tabindex, not a stack of divs carrying click handlers. Enter activates a row; Space stays with capture.
- Status codes and methods are monospace text. The coloured pills and badges are gone — here, and in the export menu, the appearance menu and the databases sheet.
- Default density is the 26px step. All three steps remain in the menu.
- The status bar degrades a segment at a time, each carrying its own separator, so a narrow panel no longer leaves a row of orphan slashes.
- Timing phases take the data colours rather than the four source-identity ones, and a phase that cost nothing recedes instead of competing.
- The collapsed sidebar shows sources and counts only. The session stats are hidden rather than shrunk: four numbers with their labels stripped off are four unattributed numbers.
- Dragging the launcher shows all four corner targets, each one named.
- The filter field shows
8 of 56and a clear button in place of removable token chips.
Fixed
- The shortcuts sheet and the command palette advertised
P,R,X,CandUwhere the handlers are lower case, so those keys did nothing. Case is load-bearing —ccopies the response,⇧Cclears the log — so the documentation now prints what actually fires. The palette also offered⇧Dfor density, which has never had a binding at all. cwas documented as copying the active format. It has always copied the response as JSON, and now says so.- The list/detail split is clamped to what the panel can give it. A stored 580px list inside a 580px panel left the detail pane 40px wide.
- The detail pane's format controls drop to a row of their own below 560px instead of sharing one 27px line with the tabs and squeezing the format labels to nothing.
- Selecting text in the filter field no longer reads as a second focus ring.
- The Headers, Connection and Query State tabs render as a key/value grid again rather than one item per line.
The Authorization header, inspectable: what a JWT in it claims, and an opt-in
switch to show the value itself on screen. Plus a fix for an axios setup that
made every request on the instance fail.
Added
- JWT claims. When an
Authorizationvalue is a compact JWS, Blix decodes it and keepsalg,sub,iss,iatandexpon the entry — never the token and never its signature, so nothing stored can be replayed. The JWT chip on theAuthorizationrow opens them, counting down toexpand turning red once it has passed. It works with masking on, which is the default. Decoding is all-or-nothing about the token's shape: an opaque bearer token, a JWE, or a claim of the wrong type stores no claims at all rather than presenting a guess as a fact. The signature is never verified — Blix has no key, and a devtool that said "valid" would be claiming something it cannot know. algis display-only and never withholds anything: a missing, empty or non-stringalgstill yields every other claim. The XML-DSig URIs .NET'sJsonWebTokenHandlerwrites there —http://www.w3.org/2001/04/xmldsig-more#hmac-sha256and its HMAC, RSA, RSA-PSS and ECDSA siblings — are shown by name, asHMAC-SHA256 (HS256), with the raw value in the tooltip and on a Copy chip; the entry, the database and the exports keep the raw value. Analgofnone, in any letter case, is flagged as an unsigned token, reported as what was sent and what came back rather than as a verdict.- The claims say where they were read: when a
fetchcall was made, from the headers an axios request settled with, or — for an axios request that never settled or used theauthoption — when it started. - Showing
Authorizationin full, off by default: command palette → Show Authorization values in full. It applies to requests captured after it is switched on; earlier entries were masked at capture and cannot be revealed, and their row says so. The raw value is held in memory beside the entries, never on them, so it cannot reach IndexedDB, a downloaded file or a copied snippet: every export and copy path — HAR, JSON, NDJSON, CSV, Markdown, the cURL script, Copy as cURL and Copy as fetch — still carries the masked value. The row's own Copy button is the one way to take it. Masking again discards every value held.cookie,set-cookie,x-api-keyand realtime connect tokens stay masked regardless. - While it is on, the status bar reads UNMASKED Authorization, second in
the bar and never dropped at a breakpoint — of everything about a session, it
is the one fact someone about to screenshot the panel needs to see. Every
such row in the Headers tab is tagged
UNMASKED. JwtClaimsandJwtClaimsSourceare exported from@hakam-aldeen-kh/blix/capture.- A CI workflow: typecheck, build, and two guards on the published bundle —
that
process.env.NODE_ENVsurvived it (defined away, the panel would ship to every consumer's users) and that"use client"is still its first bytes (without it, a React Server Component rendering<Blix />fails in Next.js).
Changed
- In development, Blix warns once when an axios instance sets
transitional.legacyInterceptorReqResOrdering: false, which puts Blix's request interceptor last and means the Payload tab shows the body after the host's interceptors transformed it. Previously Blix could not detect this at all. - The README documents which request headers the panel can and cannot show,
and SECURITY.md notes that the raw
Authorizationnever leaves memory.
Fixed
attachHttpMonitormade every request on the instance fail when a response interceptor registered before it returned something other than a response —(response) => response.data, the usual unwrapping interceptor, or a rejection handler returning a normalised domain error. Blix read.configstraight off that value, which threw inside its own interceptor and rejected the request. Present since 0.2.0. Such a response now passes through untouched; the entry stayspending, because Blix does not settle a request it cannot see settle. Register the unwrapping interceptor afterattachHttpMonitorto get both.- Request headers are now the ones the request went out with. Blix's
request interceptor runs before the host's, so the headers they add — auth,
tracing, signing, locale — and the
Content-Typeaxios sets itself did not exist yet, and the panel showed a request without them. Headers are now re-read from the config axios settles with, and the request-time snapshot is kept only until then — and for good, for a request that never settles or whose rejection carries noconfig. The body is still the plaintext captured at request time, which is the point of running first.Authorization: Basic …from axios'sauthoption is the exception: the adapter builds it on its own copy of the config, so the settled config shows something that was never sent. That header keeps its request-time value; every other header comes from settle.
is:encryptedmatched every request in an app that encrypts nothing: it testedskipEncryption !== true, which is unset on an ordinary request. It now requires ciphertext actually handed tocaptureEncrypted. The detail pane's Encrypted row followed the same broken rule and read "yes" for those requests; it now reports the evidence, or shows no row at all.- The empty-state setup snippets named exports that do not exist —
attachHttp(apiClient)forattachHttpMonitor, and a baretapRealtimeAdapter(...)call that discards the wrapped adapter. Each snippet now compiles as written, with axios andfetchshown separately andtapQueryClientshown in the effect Strict Mode requires.
Fixed
withInitiatorCapture: a wrapped client threwTypeError: Cannot create property '__monitorInitiator' on stringon every call that passed a URL string first —client(url),client(url, config),client.request(url)andclient.request(url, config). The wrapper threw before axios ran, so no promise was returned and a.catch()never saw it; only a surroundingtrydid. Present since 0.2.0. Those calls now reach axios as written, with no initiator stack. The wrapper also forwards every argument now — previously it passed on only the first, which is what keeps theconfiginclient(url, config)now that the call no longer throws.withInitiatorCapture: a frozen, sealed or otherwise non-extensible config threwTypeError: Cannot add property __monitorInitiator, object is not extensible, through every wrapped method. The request now goes out with no initiator stack.withInitiatorCapture: a config object reused across calls, such as a module-levelconst options, reported the stack of the first call that used it for every later request. A config object you wrote now records the call site of each call that passes it.withInitiatorCapture: a config axios rebuilt for a retry keeps the original call site. When an auth-refresh or retry interceptor callsclient(error.config), the retried request's initiator is the call that made the first attempt, not the interceptor — as in 0.6.0. The previous fix tells the two cases apart by__monitorId, whichattachHttpMonitorstamps only on configs axios built, so this holds whenattachHttpMonitoris installed on the instance.withInitiatorCapture: wrapping a client that is already wrapped returns it unchanged, with a warning in development. Wrap the axios instance once and export the wrapped one.attachFetchMonitor: a request body read from aRequestobject, as infetch(new Request(url, { body })), never set the entry's size. The row showed 0 B while pending, and kept it when the response recorded no size of its own — event streams, opaque responses, network errors and aborts. The size now comes from the bytes read. For a body overmaxBodyBytesit is the bytes counted before capture stopped, so a lower bound. A size the response has already recorded is never overwritten.
Fetch capture, and storage scoped per project. No public function signature narrows, but the saved log and panel preferences do not carry over: see Upgrading.
Security
- Header masking now handles a
Headersinstance and[name, value]pairs. The first serialized as{}, and the second was walked by index, soauthorizationin either form would have gone through unmasked. Neither form reached the masker in earlier releases, because axios was the only capture path and it normalises headers before its interceptors run.fetchaccepts both, so the fix ships withattachFetchMonitor. attachFetchMonitorcaptures everyfetchon the page, including those made by third-party scripts. See the README's Security section.
Upgrading
- Databases are now named
blix:<dbName>, andblix:defaultwhen nodbNameis given. The panel opens a new, empty database whether or not you already passeddbName. The oldnm-devtoolsdatabase is neither migrated nor deleted. It is listed under Databases on this origin, where you can peek at it and delete it. - Panel preferences start once from the defaults. The unscoped
nm:prefslocalStoragekey (and the oldernm:size,nm:pos,nm:corner,nm:listw) are removed on first read rather than adopted, since on a shared origin they held whichever project wrote last.
Added
attachFetchMonitor(options?)in/capture, with theFetchMonitorOptionsandFetchIgnoreRuletypes. It wrapsglobalThis.fetchon the client and records requests in the Network section in the same shape as axios requests, with the same header masking. It reads bodies from clones, so your code gets an untouchedRequestandResponse. Bodies are capped at 5 MB (maxBodyBytes), counted as bytes arrive rather than trusted fromContent-Length. Event streams, opaque responses and streaming uploads are recorded without a body, and the row says why. Next.js and webpack dev traffic is ignored by default;ignorereplaces or extends that list. It returns a disposer, and installing it twice does nothing. With axios on its fetch adapter, each request is logged once, as the axios entry. Fetch rows cannot be replayed and do not supportcaptureEncrypted.clientonMonitorEntry("axios"|"fetch"), shown as a Client row in the Headers tab and filterable withclient:fetch/client:axios. Entries captured before this release have noclientand match neither.- Databases on this origin, from the command palette, ⋯ More actions or
the status bar. It lists every Blix database on the origin with its
approximate size, Peek shows a read-only snapshot of a database's 50
newest entries, and it deletes any database except the active one. In Firefox,
which lacks
indexedDB.databases(), the list comes from the names Blix has recorded and is labelled as possibly incomplete. - The status bar names the active database, turning amber with a
sharedtag onblix:default. attachHttpMonitorreturns a disposer that ejects both interceptors.- When Replay is unavailable, the detail pane states the reason as text, and the palette's Replay row carries it as a note. A disabled button does not reliably show its tooltip.
Changed
- The panel's
localStoragepreferences mirror is keyed per project (blix:<dbName>:prefs), so apps on one origin no longer share dock position, theme or the preserve-log toggle. - Omitting
dbNamelogs a console warning in development, once per mount. - A
dbNameset after the database is open is ignored with a console warning. Previously it was accepted, so the live connection stayed on the old database and a later Purge deleted the new, unused name. attachHttpMonitoris idempotent per instance, and treats an instance and itswithInitiatorCapturewrapper as one.- An axios request cancelled through
AbortControllerorCancelTokensettles as aborted rather than as an error. Timeouts are still errors. - The published type declarations no longer import axios.
attachHttpMonitorandwithInitiatorCaptureaccept structurally typed clients, andwithInitiatorCapturenow returns the type it was given, so anAxiosInstancestays anAxiosInstance. Fetch-only apps type-check without axios installed. axios is still an optional peer dependency.
Fixed
- In the published package, the Initiator column pointed at Blix's own frames.
They were filtered by source module name, which the bundled
dist/chunk-*.jsfiles do not carry. They are now dropped by stack depth. - Calling
attachHttpMonitortwice on one instance registered two interceptor pairs, so every request left a duplicate entry pending for the rest of the session. - A purge blocked by another tab holding the database open was reported as done. The panel now shows the error.
- Request sizes for
Blob,File,ArrayBuffer, typed arrays,DataViewandURLSearchParamsbodies. The first three andURLSearchParamswere measured as 2 bytes, and typed arrays were walked element by element: about 340 ms for a 1 MBUint8Array.
The panel is redesigned. The runtime API is unchanged — <Blix /> and
every capture function keep their signatures and behaviour, so upgrading is a
version bump. The one breaking change is to an exported type: see Removed.
Added
- Command palette (
Ctrl/⌘ K), reachable from the header, the rail and the status bar. It is the only affordance for sorting, density, dock position, the per-entry copy formats and the filter syntax. - Two more ways to purge the saved log — ⋯ More actions and the palette — alongside the status bar's arm-then-confirm label. Both delete on a single press, and both stay available with preserve-log off, so a log written earlier in the session can still be deleted after the toggle is switched back. They are disabled when nothing is on disk.
- Linked events. The correlations Blix already observed — the query behind a request, the requests a query caused, the original of a replay — are now symmetric, shown as a tick on the row and a strip of chips at the foot of the detail pane. They replace the one-directional "Caused by" notice and the "Requests caused" list inside the Query State tab.
railCollapsedpanel preference.
Changed
- Redesigned the panel. The four sources moved from tabs in the toolbar to a rail down the left, which also carries the session totals; the header now holds only session-level controls, with a labelled capture pill and a filter field whose parsed tokens are removable chips inside the box. The detail pane's tabs and the payload format switch share one row, and the entry list is a fixed five-track row rather than a resizable column set.
- Row metadata that used to have its own column — size, initiator, transport, touched slices — folds into the identity column and gives up width before the name does. The waterfall column is replaced by a duration bar scaled against the slowest entry in view.
- Empty sections now carry their setup call and a Copy button, so the fact that capture must be installed where the adapter or store is constructed is stated where a developer discovers the problem.
- The Timing tab compares a request against the session's median and its slowest entry.
- Masked headers are labelled
MASKEDin the Headers tab rather than leaving the reader to infer it from an ellipsis. The stored value keeps its(masked)suffix, so every export path still carries the fact. - Row density defaults are a few pixels taller (26 / 30 / 36) — the duration column now holds a number and its bar.
- The docked badge separates its identity from its counts and carries a grip, so what is draggable is visible before you drag it. The failing count is titled rather than only coloured.
Removed
- Column resizing, and
columnWidthsfrom the exportedMonitorPrefstype. The list has one elastic column now; the rest are fixed. Breaking for anyone who constructs aMonitorPrefsobject or reads that field —railCollapsedis required in its place. Stored preferences carrying the old key still load; it is ignored. - The filter-syntax hint row under the toolbar, and the state-filter bar. The filters moved into the list header; the syntax moved into the palette.
Fixed
- The list rendered only the overscan window — eight rows and a blank column below them — when the panel was opened onto a buffer that had filled up before it was opened. The virtualizer's ResizeObserver never attached, because the scroller does not exist until the panel is open and nothing in its effect changed at that moment.
Pause captureand the rail toggle performed a store write inside asetStateupdater, which React runs during the render phase — a setState from inside a render, warned about in the console.
Documentation and packaging only — no code change.
Changed
- Corrected the Security section on clearing: switching preserve-log off
already clears the stored entries, and panel preferences survive both that
and a Purge, since they are mirrored to
localStorage. - Scoped the 24 MB retention cap to HTTP entries. Realtime frames are stored untruncated and under-counted against the byte budget, so the cap does not bound them.
- Clarified header masking: values of 12 characters or fewer are replaced entirely rather than partially masked.
- Noted that panel preferences are mirrored to
localStoragein addition to IndexedDB. - The preserve-log toggles all require the panel to be mounted.
- CHANGELOG and LICENSE links in the README are now absolute, so they resolve from the npm package page.
CHANGELOG.mdis no longer included in the published package. The README links to it on GitHub instead.
Changed
- Documentation only — no code change.
- Added a Security section documenting what Blix captures, what reaches IndexedDB and when, the four redacted header names, retention limits and how to purge.
- Documented
preserve-log: its default, the three toggles, and that switching it on writes the session already in the buffer. - Corrected the
dbNamesection, which claimed the log persists across a reload by default. It does not — persistence is opt-in. - Noted that the package is ESM-only.
- Added
SECURITY.md.
Identical in code to 0.3.3. It exists only to correct the version number: 0.3.3 was a feature release with a type-level break and should never have been a patch bump. 0.3.3 is deprecated on npm in favour of this release. Everything below is new relative to 0.3.2, not to 0.3.3.
Added
- Twelve panel themes, behind a new toolbar button. Dark: Midnight (default),
Carbon, Nord, Tokyo Night, One Dark, Mocha, Dracula, Gruvbox. Light: Daylight,
GitHub, Latte, Solar. Hovering or arrow-keying an entry applies it to the panel
behind the menu; moving away restores the previous one. The default preference
is
auto, which follows the host app's light/dark class on<html>. The choice is persisted with the panel's other preferences. - A format switch on every payload pane — Tree, Table, JSON, YAML, Text. It applies to request and response bodies, realtime frames, the encrypted envelope, Redux actions and both Redux state views. Copy returns whichever format is on screen (Table copies CSV, YAML copies YAML). The choice is persisted.
- Folding in the JSON view, with a Collapse all action; Alt-click in the Tree expands or collapses an entire subtree.
- Keyboard navigation in the payload tree (arrow keys to move, expand, collapse
and step in/out; Enter to toggle) and in menus (arrows, Home/End, Enter, and
Esc to close and return focus to the button that opened it). The
?cheatsheet documents both, plus the theme picker and the formats. - An export menu with six formats — HAR, JSON, NDJSON, Markdown table (to the clipboard), CSV and a cURL script — and a Shown / All scope toggle that shows both counts. Export previously always took the unfiltered buffer, whatever the list was showing; Shown is now the default. CSV is written with a UTF-8 BOM so Excel reads non-ASCII correctly.
- A store slice picker on the Redux State tab. It opens on the slice the
selected action wrote to when the action touched exactly one, marks every slice
the action changed, and keeps
rootselectable. - Copy as fetch in the row context menu, alongside Copy as cURL.
- Horizontal scrolling for the tab row and the slice picker when they overflow, with edge fades, wheel support and arrow buttons.
- Accessible names and pressed/expanded state on the toolbar controls, and tab/tablist roles on the tab and slice strips.
Changed
- The Preview and Response tabs are merged into a single Response tab. Both previously rendered the same field — one as a tree, one as raw JSON — so the rendering is now chosen with the format switch instead of by picking a tab.
MonitorPrefs, exported from@hakam-aldeen-kh/blix/capture, gained two required fields:theme: stringanddataFormat: string. This is a compile-time break for anyone constructing aMonitorPrefsobject literal.BlixPropsand every function signature in/captureare unchanged.- HAR download filename:
network-<timestamp>.har→blix-<timestamp>.har. Every other export uses the sameblix-<timestamp>.<ext>form. - A press anywhere outside an open menu now dismisses it, including inside the panel; previously only a press outside the panel root did, so a menu left open over the panel could only be closed with Esc or its own button. The press is not swallowed — it still selects the row or activates the button underneath.
- The status bar and the detail pane's empty states name what a row is in the active section — requests, connections, actions or queries — instead of calling every row a "request".
- Every colour the panel paints now comes from the active theme's tokens: status colours, method and section accents, waterfall bars, timing segments and JSON syntax highlighting. The previous split, with a light palette in CSS and a duplicate dark one in JS, is gone.
- WebSocket, Redux and Query rows take their section's accent colour rather than the shared fallback that HTTP verbs fell through to.
- The docked badge reads "Blix" rather than "Dev", and the toolbar shows a BLIX wordmark beside the status dot.
Fixed
- Generated downloads revoke their object URL on the next task instead of immediately after the click, which could race the browser's read of the blob and save an empty file in Safari.
Released in error as a patch bump: it was a feature release that also added two
required fields to the publicly exported MonitorPrefs type. Superseded by
0.4.0, which is identical in code. Deprecated on npm — use 0.4.0. Its contents
are listed under 0.4.0.
Added
./package.jsonas an export subpath, a top-leveltypesfield,enginesdeclaring Node >= 18, and package metadata (repository, homepage, bugs, keywords).
Changed
peerDependenciesnarrowed from*toaxios ^1.0.0,@reduxjs/toolkit ^2.0.0and@tanstack/react-query ^5.0.0. Narrowing a peer range is breaking by convention and this shipped as a patch — though 0.3.2 was the first version on the public npm registry, so no npm consumer could have been affected.- License:
UNLICENSED→ MIT. - Published to the public npm registry instead of GitHub Packages.
Fixed
tapQueryClient's disposer now clears the client's internal tapped mark. A dispose-then-reinstall cycle — exactly what React Strict Mode does to an effect returning this disposer — hit the idempotency guard and became a permanent no-op, leaving the Query tab silently empty for the rest of the session.
Versions before 0.3.2 were published to GitHub Packages and are not available on the public npm registry.