@nice-code/state 0.4.7 → 0.4.9

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.
@@ -161,7 +161,7 @@ class StateDevtoolsCore {
161
161
  }
162
162
  }
163
163
  // src/devtools/browser/NiceStateDevtools.tsx
164
- import { useEffect as useEffect2, useId, useMemo as useMemo2, useReducer, useState as useState3 } from "react";
164
+ import { useEffect as useEffect2, useId, useMemo, useReducer, useState as useState3 } from "react";
165
165
 
166
166
  // src/devtools/core/devtools_colors.ts
167
167
  var DEVTOOL_COLOR_SEMANTIC_ERROR = "#FF5C5C";
@@ -253,6 +253,25 @@ function summarizeChange(change) {
253
253
  function topLevel(path) {
254
254
  return path.length > 0 ? String(path[0]) : "(root)";
255
255
  }
256
+ function changeGroupKey(change) {
257
+ return `${change.storeId}|${safeStringify(change.snapshot, 0)}`;
258
+ }
259
+ function groupChanges(changes) {
260
+ const groups = [];
261
+ let lastKey = null;
262
+ for (const change of changes) {
263
+ const key = changeGroupKey(change);
264
+ const last = groups[groups.length - 1];
265
+ if (last != null && key === lastKey) {
266
+ last.count++;
267
+ last.oldest = change;
268
+ } else {
269
+ groups.push({ representative: change, oldest: change, count: 1 });
270
+ }
271
+ lastKey = key;
272
+ }
273
+ return groups;
274
+ }
256
275
  function computeDiff(before, after) {
257
276
  const out = [];
258
277
  walkDiff([], before, after, out);
@@ -1466,37 +1485,15 @@ function ToggleCheckbox({
1466
1485
  }
1467
1486
 
1468
1487
  // src/devtools/browser/components/ChangeList.tsx
1469
- import { useMemo } from "react";
1470
1488
  import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
1471
- function changeGroupKey(change) {
1472
- const shape = change.patches.length > 0 ? safeStringify(change.patches, 0) : safeStringify(change.snapshot, 0);
1473
- return `${change.storeId}|${change.source}|${shape}`;
1474
- }
1475
- function groupChanges(changes) {
1476
- const groups = [];
1477
- let lastKey = null;
1478
- for (const change of changes) {
1479
- const key = changeGroupKey(change);
1480
- const last = groups[groups.length - 1];
1481
- if (last != null && key === lastKey) {
1482
- last.count++;
1483
- last.oldestCuid = change.cuid;
1484
- } else {
1485
- groups.push({ representative: change, oldestCuid: change.cuid, count: 1 });
1486
- }
1487
- lastKey = key;
1488
- }
1489
- return groups;
1490
- }
1491
1489
  function ChangeList({
1492
- changes,
1490
+ groups,
1493
1491
  selectedCuid,
1494
1492
  onSelect,
1495
1493
  showStore,
1496
1494
  style
1497
1495
  }) {
1498
- const groups = useMemo(() => groupChanges(changes), [changes]);
1499
- if (changes.length === 0) {
1496
+ if (groups.length === 0) {
1500
1497
  return /* @__PURE__ */ jsxDEV6("div", {
1501
1498
  style: {
1502
1499
  padding: "24px",
@@ -1513,10 +1510,10 @@ function ChangeList({
1513
1510
  children: groups.map((group) => /* @__PURE__ */ jsxDEV6(ChangeRow, {
1514
1511
  change: group.representative,
1515
1512
  count: group.count,
1516
- selected: group.representative.cuid === selectedCuid,
1517
- onClick: () => onSelect(group.representative.cuid),
1513
+ selected: group.oldest.cuid === selectedCuid,
1514
+ onClick: () => onSelect(group.oldest.cuid),
1518
1515
  showStore
1519
- }, group.oldestCuid, false, undefined, this))
1516
+ }, group.oldest.cuid, false, undefined, this))
1520
1517
  }, undefined, false, undefined, this);
1521
1518
  }
1522
1519
  function ChangeRow({
@@ -2098,15 +2095,30 @@ function NiceStateDevtools_Panel({
2098
2095
  const dockSide = getDockSide(position);
2099
2096
  const isHorizDock = dockSide === "top" || dockSide === "bottom";
2100
2097
  const dockedSize = isHorizDock ? dockedHeight : dockedWidth;
2101
- const filteredChanges = useMemo2(() => storeFilter == null ? changes : changes.filter((c) => c.storeId === storeFilter), [changes, storeFilter]);
2102
- const selectedChange = selectedChangeCuid != null ? changes.find((c) => c.cuid === selectedChangeCuid) ?? null : null;
2103
- const latestCuid = filteredChanges.length > 0 ? filteredChanges[0].cuid : null;
2098
+ const filteredChanges = useMemo(() => storeFilter == null ? changes : changes.filter((c) => c.storeId === storeFilter), [changes, storeFilter]);
2099
+ const groups = useMemo(() => groupChanges(filteredChanges), [filteredChanges]);
2100
+ const selectedChange = useMemo(() => {
2101
+ if (selectedChangeCuid == null)
2102
+ return null;
2103
+ const group = groups.find((g) => g.oldest.cuid === selectedChangeCuid);
2104
+ if (group == null)
2105
+ return changes.find((c) => c.cuid === selectedChangeCuid) ?? null;
2106
+ if (group.count > 1) {
2107
+ return {
2108
+ ...group.representative,
2109
+ prevSnapshot: group.oldest.prevSnapshot,
2110
+ inversePatches: group.oldest.inversePatches
2111
+ };
2112
+ }
2113
+ return group.representative;
2114
+ }, [selectedChangeCuid, groups, changes]);
2115
+ const latestCuid = groups.length > 0 ? groups[0].oldest.cuid : null;
2104
2116
  useEffect2(() => {
2105
2117
  if (stayOnLatest && latestCuid != null)
2106
2118
  setSelectedChangeCuid(latestCuid);
2107
2119
  }, [stayOnLatest, latestCuid]);
2108
2120
  const activeStore = stores.find((s) => s.id === storeFilter) ?? (stores.length > 0 ? stores[0] : null);
2109
- const dock = useMemo2(() => getDevtoolsDockCoordinator(), []);
2121
+ const dock = useMemo(() => getDevtoolsDockCoordinator(), []);
2110
2122
  const panelId = useId();
2111
2123
  const [, bumpView] = useReducer((n) => n + 1, 0);
2112
2124
  const badge = changes.length > 0 ? String(changes.length) : undefined;
@@ -2248,7 +2260,7 @@ function NiceStateDevtools_Panel({
2248
2260
  /* @__PURE__ */ jsxDEV10("div", {
2249
2261
  style: { flex: 1, minHeight: 0, overflowY: "auto" },
2250
2262
  children: /* @__PURE__ */ jsxDEV10(ChangeList, {
2251
- changes: filteredChanges,
2263
+ groups,
2252
2264
  selectedCuid: selectedChangeCuid,
2253
2265
  onSelect: (cuid) => {
2254
2266
  if (stayOnLatest)
@@ -1,7 +1,7 @@
1
- import { type CSSProperties } from "react";
2
- import type { IDevtoolsStateChange } from "../../core/StateDevtools.types";
3
- export declare function ChangeList({ changes, selectedCuid, onSelect, showStore, style, }: {
4
- changes: IDevtoolsStateChange[];
1
+ import type { CSSProperties } from "react";
2
+ import { type IChangeGroup } from "./utils";
3
+ export declare function ChangeList({ groups, selectedCuid, onSelect, showStore, style, }: {
4
+ groups: IChangeGroup[];
5
5
  selectedCuid: string | null;
6
6
  onSelect: (cuid: string) => void;
7
7
  showStore: boolean;
@@ -13,6 +13,33 @@ export declare function patchPathToString(path: Patch["path"]): string;
13
13
  * the distinct top-level paths affected.
14
14
  */
15
15
  export declare function summarizeChange(change: IDevtoolsStateChange): string;
16
+ /** A run of consecutive, structurally-equal changes collapsed into one row. */
17
+ export interface IChangeGroup {
18
+ /** Newest change in the run — the one the row renders and selects. */
19
+ representative: IDevtoolsStateChange;
20
+ /**
21
+ * Oldest change in the run — the one that actually moved the state (every later
22
+ * member re-applied the same mutation as a no-op). Its `prevSnapshot` is the
23
+ * real "before" of the whole run, so the detail panel uses it to show what the
24
+ * initial update changed instead of the representative's empty self-diff.
25
+ */
26
+ oldest: IDevtoolsStateChange;
27
+ /** How many changes the run collapses (1 = ungrouped). */
28
+ count: number;
29
+ }
30
+ /**
31
+ * Structural identity of a change by its *resulting* state: same store, same
32
+ * snapshot. The feature collapses updates that "didn't change the state from the
33
+ * previous update", so we compare the committed state itself rather than the
34
+ * mutation that produced it. Keying on patches would miss the no-op cases this is
35
+ * meant to catch — a fresh object reference with identical content, or a
36
+ * same-value write from a different source (e.g. a devtools edit followed by an
37
+ * app update). Those carry patches yet leave the state untouched, so they belong
38
+ * in the run that already reached this state regardless of source.
39
+ */
40
+ export declare function changeGroupKey(change: IDevtoolsStateChange): string;
41
+ /** Collapse consecutive structurally-equal changes (list is newest-first). */
42
+ export declare function groupChanges(changes: IDevtoolsStateChange[]): IChangeGroup[];
16
43
  export type TDiffKind = "added" | "removed" | "changed";
17
44
  export interface IDiffEntry {
18
45
  path: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nice-code/state",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {