@hakam-aldeen-kh/blix 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,8 @@ a dockable panel with cross-source links, diffing, replay and HAR/cURL
7
7
  export.
8
8
 
9
9
  The entire panel is eliminated from production builds — see
10
- [Production elimination](#production-elimination).
10
+ [Production elimination](#production-elimination). To keep it on a staging
11
+ server, see [Enabling Blix in a production build](#enabling-blix-in-a-production-build-staging).
11
12
 
12
13
  Release notes are in [CHANGELOG.md](https://github.com/Hakam-aldeen-Kh/blix/blob/main/CHANGELOG.md).
13
14
 
@@ -397,7 +398,9 @@ never creating one of its own — and the panel updates.
397
398
  It is a **silent no-op** — never a throw, never a console warning — in every
398
399
  one of these:
399
400
 
400
- - outside development (`process.env.NODE_ENV !== "development"`);
401
+ - outside development (`process.env.NODE_ENV !== "development"`), unless the
402
+ build opted in with `NEXT_PUBLIC_BLIX=true` (see
403
+ [staging](#enabling-blix-in-a-production-build-staging));
401
404
  - **outside the browser** — the gate is also `typeof window !== "undefined"`,
402
405
  so every call made while rendering on the server does nothing, by design (see
403
406
  [Production elimination](#production-elimination));
@@ -721,7 +724,11 @@ import { store } from "@/src/store";
721
724
  // module graph, which fails the production build. Importing them here keeps
722
725
  // that evaluation on the client side of the boundary.
723
726
  export default function BlixMount() {
724
- if (process.env.NODE_ENV !== "development") return null;
727
+ if (
728
+ process.env.NODE_ENV !== "development" &&
729
+ process.env.NEXT_PUBLIC_BLIX !== "true"
730
+ )
731
+ return null;
725
732
  return <Blix store={store} apiClient={apiClient} dbName="my-app" />;
726
733
  }
727
734
  ```
@@ -798,6 +805,14 @@ field carries its own count — `8 of 56` — and a clear button, so you never
798
805
  have to read the status bar to know one is applied. `.*` widens the search to
799
806
  request and response bodies.
800
807
 
808
+ **The red bin beside Export clears the source you are reading**, and only
809
+ that one: emptying Redux leaves Network alone. Pinned entries survive it, and
810
+ it is disabled when the source has nothing left to clear. To empty every
811
+ source at once, use **Clear log** in the overflow menu, or `⇧C`.
812
+
813
+ The panel opens **floating** the first time. Dock it to the bottom or the
814
+ right from the header; the choice is saved per project.
815
+
801
816
  **`Ctrl/⌘ K` opens the command palette**, and for several things it is the only
802
817
  way in — sort order, row density, dock position, the copy formats, the filter
803
818
  syntax. The header spends its width on what you read constantly; everything you
@@ -1014,8 +1029,55 @@ payloads for the lifetime of the server. Keeping the server-side singleton
1014
1029
  permanently empty is the point.
1015
1030
 
1016
1031
  Practically: a `captureEncrypted` call that runs during SSR does nothing, and
1017
- `<Blix />` returns `null` there — it checks `typeof window` alongside
1018
- `NODE_ENV` before touching the panel import.
1032
+ `<Blix />` renders nothing there. It also renders nothing during hydration, so
1033
+ its first client render matches the server's HTML; the panel mounts on the
1034
+ render right after. (Before 0.9.0 it branched on `typeof window` instead,
1035
+ which Next.js reported as a hydration mismatch on every load.)
1036
+
1037
+ ### Enabling Blix in a production build (staging)
1038
+
1039
+ A staging or test server usually runs a production build, where the panel is
1040
+ gone by design. Setting `NODE_ENV=development` for that build does not bring
1041
+ it back on Next.js 16: `next build` runs on Turbopack and inlines `NODE_ENV`
1042
+ as `"production"` whatever the environment says.
1043
+
1044
+ Opt in explicitly instead, in that server's env file:
1045
+
1046
+ ```bash
1047
+ NEXT_PUBLIC_BLIX=true
1048
+ ```
1049
+
1050
+ The panel and the capture layer both check it next to `NODE_ENV`, as the same
1051
+ kind of literal comparison, so Next.js inlines it at build time.
1052
+
1053
+ > **Set `NEXT_PUBLIC_BLIX=false` in your production env file.** Next.js only
1054
+ > inlines variables that are defined. Left unset, the comparison stays in the
1055
+ > bundle as a runtime check: the panel still never loads — the variable is
1056
+ > undefined in the browser — but its chunk is emitted into your production
1057
+ > build. `false` lets the bundler fold the check and drop the chunk.
1058
+
1059
+ **Never set `NEXT_PUBLIC_BLIX=true` for a build that real users get.** It
1060
+ ships the monitor to them, and with it everything it captures — see
1061
+ [Security](#security).
1062
+
1063
+ **Your own guards need the variable too.** A mount component that returns
1064
+ `null` outside development (as in [Mounting the panel](#mounting-the-panel)),
1065
+ or a call-site guard around `attachHttpMonitor` (below), keeps Blix off on
1066
+ staging regardless of the variable. Add it to each:
1067
+
1068
+ ```ts
1069
+ if (
1070
+ (process.env.NODE_ENV === "development" ||
1071
+ process.env.NEXT_PUBLIC_BLIX === "true") &&
1072
+ typeof window !== "undefined"
1073
+ ) {
1074
+ attachHttpMonitor(apiClient);
1075
+ }
1076
+ ```
1077
+
1078
+ The variable is read under its `NEXT_PUBLIC_` name, which is a Next.js
1079
+ convention. The opt-in has been verified with Next.js 16 (Turbopack) only;
1080
+ other bundlers do not inline that name by default.
1019
1081
 
1020
1082
  ### Guard your call sites
1021
1083
 
@@ -1085,10 +1147,12 @@ option to keep a third party's traffic out of the log.
1085
1147
  By default all of that is **in memory only**. Nothing is written to disk, and
1086
1148
  a reload starts clean.
1087
1149
 
1088
- All of it is dev-only regardless. Capture is gated on
1150
+ All of it is dev-only unless you opt a build in. Capture is gated on
1089
1151
  `process.env.NODE_ENV === "development" && typeof window !== "undefined"`, and
1090
1152
  the database is opened only when the panel mounts — see
1091
- [Production elimination](#production-elimination).
1153
+ [Production elimination](#production-elimination). A build made with
1154
+ `NEXT_PUBLIC_BLIX=true` captures exactly as development does, so everything in
1155
+ this section applies to whoever uses that build.
1092
1156
 
1093
1157
  ### What reaches disk, and when
1094
1158
 
@@ -30,7 +30,7 @@ import {
30
30
  setReduxIgnoreList,
31
31
  summarizeInitiator,
32
32
  truncateForPersist
33
- } from "./chunk-2V2YJ5ML.js";
33
+ } from "./chunk-IWI5YFYP.js";
34
34
 
35
35
  // src/capture/monitorDatabases.ts
36
36
  var REGISTRY_KEY = `${DB_PREFIX}databases`;
@@ -469,7 +469,7 @@ var LEGACY_KEYS = [
469
469
  ];
470
470
  var DEFAULT_PREFS = {
471
471
  rev: 0,
472
- mode: "bottom",
472
+ mode: "float",
473
473
  bottomH: 420,
474
474
  rightW: 520,
475
475
  float: { pos: null, size: { w: 920, h: 600 }, maximized: false },
@@ -4699,6 +4699,9 @@ function MonitorToolbar(props) {
4699
4699
  onExportMenu,
4700
4700
  exportOpen,
4701
4701
  exportDisabled,
4702
+ onClearSection,
4703
+ clearDisabled,
4704
+ sectionLabel,
4702
4705
  onThemeMenu,
4703
4706
  themeOpen,
4704
4707
  themeLabel,
@@ -4825,6 +4828,18 @@ function MonitorToolbar(props) {
4825
4828
  children: /* @__PURE__ */ jsx16(Icon, { name: "download", size: 13 })
4826
4829
  }
4827
4830
  ),
4831
+ /* @__PURE__ */ jsx16(
4832
+ "button",
4833
+ {
4834
+ type: "button",
4835
+ className: "nm-ibtn nm-ibtn-danger",
4836
+ onClick: onClearSection,
4837
+ disabled: clearDisabled,
4838
+ "aria-label": `Clear the ${sectionLabel} log`,
4839
+ title: `Clear the ${sectionLabel} log \u2014 other sources and pinned entries are kept`,
4840
+ children: /* @__PURE__ */ jsx16(Icon, { name: "clear", size: 13 })
4841
+ }
4842
+ ),
4828
4843
  /* @__PURE__ */ jsx16(
4829
4844
  "button",
4830
4845
  {
@@ -8583,6 +8598,9 @@ var HEADER = `
8583
8598
  .nm-ibtn:hover { background: var(--bx-hover); }
8584
8599
  .nm-ibtn.active, .nm-ibtn[aria-expanded="true"] { background: var(--bx-sel); color: var(--bx-fg); }
8585
8600
  .nm-ibtn:disabled { color: var(--bx-mark); cursor: default; background: transparent; }
8601
+ /* Destructive: the header's per-source clear. Red while it can do something;
8602
+ the disabled rule above still greys it out when there is nothing to clear. */
8603
+ .nm-ibtn-danger:not(:disabled) { color: var(--bx-err); }
8586
8604
  .nm-hsep { width: 1px; height: 16px; flex: none; background: var(--bx-line); }
8587
8605
  `;
8588
8606
  var RAIL = `
@@ -10524,7 +10542,7 @@ var EMPTY_COPY = {
10524
10542
  }
10525
10543
  };
10526
10544
  function DevTools() {
10527
- var _a, _b, _c, _d, _e;
10545
+ var _a, _b, _c, _d, _e, _f, _g;
10528
10546
  const { apiClient } = useContext4(BlixContext);
10529
10547
  const entries = useSyncExternalStore2(
10530
10548
  networkMonitor.subscribe,
@@ -11214,6 +11232,12 @@ function DevTools() {
11214
11232
  onExportMenu: setExportAnchor,
11215
11233
  exportOpen: exportAnchor !== null,
11216
11234
  exportDisabled: entries.length === 0,
11235
+ onClearSection: () => {
11236
+ networkMonitor.clear((e) => sectionOf(e) === section);
11237
+ selection.clear();
11238
+ },
11239
+ clearDisabled: !sectionEntries.some((e) => !e.pinned),
11240
+ sectionLabel: (_f = (_e = SECTION_DEFS.find((s) => s.id === section)) == null ? void 0 : _e.label) != null ? _f : section,
11217
11241
  onThemeMenu: setThemeAnchor,
11218
11242
  themeOpen: themeAnchor !== null,
11219
11243
  themeLabel: theme.label,
@@ -11369,7 +11393,7 @@ function DevTools() {
11369
11393
  noun: nouns.many,
11370
11394
  totalBytes: list.totalBytes,
11371
11395
  slowestMs,
11372
- failingHint: (_e = firstFailing == null ? void 0 : firstFailing.url) != null ? _e : null,
11396
+ failingHint: (_g = firstFailing == null ? void 0 : firstFailing.url) != null ? _g : null,
11373
11397
  pinnedCount,
11374
11398
  persistedCount: persisted.count,
11375
11399
  persistedLabel: prefs2.preserveLog && hydration2 === "ready" ? `${persisted.count} saved \xB7 ${formatBytes(persisted.bytes)}` : null,
@@ -3,12 +3,12 @@ import {
3
3
  attachHttpMonitor,
4
4
  captureEncrypted,
5
5
  tapRealtimeAdapter
6
- } from "../chunk-GGVNJ6QZ.js";
6
+ } from "../chunk-TV2KC74D.js";
7
7
  import {
8
8
  createReduxMonitorMiddleware,
9
9
  tapQueryClient,
10
10
  withInitiatorCapture
11
- } from "../chunk-2V2YJ5ML.js";
11
+ } from "../chunk-IWI5YFYP.js";
12
12
  export {
13
13
  attachFetchMonitor,
14
14
  attachHttpMonitor,
@@ -19,7 +19,7 @@ var __spreadValues = (a, b) => {
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
20
 
21
21
  // src/capture/monitorConfig.ts
22
- var MONITOR_ENABLED = process.env.NODE_ENV === "development" && typeof window !== "undefined";
22
+ var MONITOR_ENABLED = (process.env.NODE_ENV === "development" || process.env.NEXT_PUBLIC_BLIX === "true") && typeof window !== "undefined";
23
23
  var NM_SCHEMA_VERSION = 1;
24
24
  var DB_PREFIX = "blix:";
25
25
  var LEGACY_DB_NAME = "nm-devtools";
@@ -386,11 +386,20 @@ var NetworkMonitor = class {
386
386
  this.entries = next;
387
387
  (_b = this.sink) == null ? void 0 : _b.onDirty(parentId);
388
388
  }
389
- /** Clears the log, keeping pinned entries — that is what pinning is for. */
390
- clear() {
389
+ /**
390
+ * Clears the log, keeping pinned entries — that is what pinning is for.
391
+ *
392
+ * With `match`, only the unpinned entries it accepts go — the header's
393
+ * per-source clear passes one, so emptying Redux leaves Network alone. The
394
+ * predicate comes from the caller because "which source is this entry" is
395
+ * a panel concept (`sectionOf`), not a capture one.
396
+ */
397
+ clear(match) {
391
398
  var _a, _b;
392
- const survivors = this.entries.filter((e) => e.pinned);
393
- const removed = this.entries.filter((e) => !e.pinned).map((e) => e.id);
399
+ const goes = (e) => !e.pinned && (!match || match(e));
400
+ const survivors = this.entries.filter((e) => !goes(e));
401
+ const removed = this.entries.filter(goes).map((e) => e.id);
402
+ if (removed.length === 0) return;
394
403
  this.entries = survivors;
395
404
  this.reindex();
396
405
  if (survivors.length === 0) {
@@ -15,7 +15,7 @@ import {
15
15
  serializeBody,
16
16
  serializeHeaders,
17
17
  truncateForPersist
18
- } from "./chunk-2V2YJ5ML.js";
18
+ } from "./chunk-IWI5YFYP.js";
19
19
 
20
20
  // src/capture/monitorStamp.ts
21
21
  var MONITOR_ID = /* @__PURE__ */ Symbol.for("blix.monitorId");
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  attachHttpMonitor,
5
5
  captureEncrypted,
6
6
  tapRealtimeAdapter
7
- } from "./chunk-GGVNJ6QZ.js";
7
+ } from "./chunk-TV2KC74D.js";
8
8
  import {
9
9
  BlixContext
10
10
  } from "./chunk-PQIOIMAQ.js";
@@ -13,23 +13,31 @@ import {
13
13
  createReduxMonitorMiddleware,
14
14
  tapQueryClient,
15
15
  withInitiatorCapture
16
- } from "./chunk-2V2YJ5ML.js";
16
+ } from "./chunk-IWI5YFYP.js";
17
17
 
18
18
  // src/ui/Blix.tsx
19
- import { lazy, Suspense, useEffect, useRef } from "react";
19
+ import { lazy, Suspense, useEffect, useRef, useSyncExternalStore } from "react";
20
20
  import { jsx } from "react/jsx-runtime";
21
+ var noopSubscribe = () => () => {
22
+ };
21
23
  function Blix({ store, apiClient, dbName }) {
22
24
  const panelRef = useRef(null);
25
+ const mounted = useSyncExternalStore(
26
+ noopSubscribe,
27
+ () => true,
28
+ () => false
29
+ );
23
30
  useEffect(() => {
24
- if (process.env.NODE_ENV !== "development") return;
31
+ if (process.env.NODE_ENV !== "development" && process.env.NEXT_PUBLIC_BLIX !== "true")
32
+ return;
25
33
  if (dbName) return;
26
34
  console.warn(
27
35
  '[blix] No dbName prop \u2014 using the shared database "blix:default".\nIndexedDB is scoped to the origin, not to your app, so every app served from this origin writes to that one database and entries from other projects will appear in this panel.\nFix: <Blix dbName="my-app" />\nExisting databases on this origin are listed under \u201CDatabases on this origin\u201D in the command palette (Ctrl/\u2318 K).'
28
36
  );
29
37
  }, []);
30
- if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
38
+ if (mounted && (process.env.NODE_ENV === "development" || process.env.NEXT_PUBLIC_BLIX === "true")) {
31
39
  if (!panelRef.current) {
32
- panelRef.current = lazy(() => import("./DevToolsPanel-WD327OP5.js"));
40
+ panelRef.current = lazy(() => import("./DevToolsPanel-5XOY5WN5.js"));
33
41
  }
34
42
  const Panel = panelRef.current;
35
43
  if (dbName) configureDbName(dbName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hakam-aldeen-kh/blix",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Dev-tools panel for React apps — HTTP, Redux, Query and Realtime monitoring.",
5
5
  "keywords": [
6
6
  "devtools",