atom.io 0.33.13 → 0.33.14

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.
Files changed (37) hide show
  1. package/dist/data/index.js.map +1 -1
  2. package/dist/eslint-plugin/index.js.map +1 -1
  3. package/dist/internal/index.js +25 -23
  4. package/dist/internal/index.js.map +1 -1
  5. package/dist/introspection/index.d.ts +7 -7
  6. package/dist/introspection/index.d.ts.map +1 -1
  7. package/dist/introspection/index.js +26 -38
  8. package/dist/introspection/index.js.map +1 -1
  9. package/dist/json/index.js.map +1 -1
  10. package/dist/main/index.js +11 -11
  11. package/dist/main/index.js.map +1 -1
  12. package/dist/react/index.js.map +1 -1
  13. package/dist/react-devtools/index.css +110 -56
  14. package/dist/react-devtools/index.css.map +1 -1
  15. package/dist/react-devtools/index.js +38 -14
  16. package/dist/react-devtools/index.js.map +1 -1
  17. package/dist/realtime/index.js.map +1 -1
  18. package/dist/realtime-client/index.js.map +1 -1
  19. package/dist/realtime-react/index.js.map +1 -1
  20. package/dist/realtime-server/index.js.map +1 -1
  21. package/dist/realtime-testing/index.js.map +1 -1
  22. package/dist/transceivers/set-rtx/index.js.map +1 -1
  23. package/dist/use-o-BrXc7Qro.js.map +1 -1
  24. package/dist/web/index.js.map +1 -1
  25. package/package.json +6 -6
  26. package/src/internal/transaction/create-transaction.ts +9 -5
  27. package/src/introspection/attach-atom-index.ts +6 -15
  28. package/src/introspection/attach-introspection-states.ts +5 -5
  29. package/src/introspection/attach-selector-index.ts +78 -85
  30. package/src/introspection/attach-timeline-index.ts +5 -12
  31. package/src/introspection/attach-transaction-index.ts +18 -16
  32. package/src/introspection/auditor.ts +2 -3
  33. package/src/react-devtools/StateIndex.tsx +44 -20
  34. package/src/react-devtools/TimelineIndex.tsx +16 -12
  35. package/src/react-devtools/TransactionIndex.tsx +22 -18
  36. package/src/react-devtools/devtools.css +110 -56
  37. package/src/react-devtools/store.ts +1 -1
@@ -1,8 +1,7 @@
1
- import type { AtomToken, ReadonlyPureSelectorToken } from "atom.io"
1
+ import type { AtomToken } from "atom.io"
2
2
  import type { Store } from "atom.io/internal"
3
3
  import {
4
4
  createRegularAtom,
5
- createStandaloneSelector,
6
5
  deposit,
7
6
  isReservedIntrospectionKey,
8
7
  } from "atom.io/internal"
@@ -11,13 +10,11 @@ import type { WritableTokenIndex } from "."
11
10
 
12
11
  export type AtomTokenIndex = WritableTokenIndex<AtomToken<unknown>>
13
12
 
14
- export const attachAtomIndex = (
15
- store: Store,
16
- ): ReadonlyPureSelectorToken<AtomTokenIndex> => {
17
- const atomTokenIndexState__INTERNAL = createRegularAtom<AtomTokenIndex>(
13
+ export const attachAtomIndex = (store: Store): AtomToken<AtomTokenIndex> => {
14
+ return createRegularAtom<AtomTokenIndex>(
18
15
  store,
19
16
  {
20
- key: `🔍 Atom Token Index (Internal)`,
17
+ key: `🔍 Atom Token Index`,
21
18
  default: () => {
22
19
  const base: AtomTokenIndex = new Map()
23
20
  for (const [key, val] of store.atoms) {
@@ -66,7 +63,7 @@ export const attachAtomIndex = (
66
63
  } else {
67
64
  self.set(atomToken.key, atomToken)
68
65
  }
69
- return self
66
+ return new Map(self)
70
67
  })
71
68
  })
72
69
  store.on.atomDisposal.subscribe(`introspection`, (atomToken) => {
@@ -80,10 +77,8 @@ export const attachAtomIndex = (
80
77
  self.delete(familyKey)
81
78
  }
82
79
  }
83
- } else {
84
- self.delete(atomToken.key)
85
80
  }
86
- return self
81
+ return new Map(self)
87
82
  })
88
83
  })
89
84
  },
@@ -91,8 +86,4 @@ export const attachAtomIndex = (
91
86
  },
92
87
  undefined,
93
88
  )
94
- return createStandaloneSelector(store, {
95
- key: `🔍 Atom Token Index`,
96
- get: ({ get }) => get(atomTokenIndexState__INTERNAL),
97
- })
98
89
  }
@@ -1,7 +1,7 @@
1
1
  import type {
2
+ AtomToken,
2
3
  Loadable,
3
4
  ReadonlyPureSelectorFamilyToken,
4
- ReadonlyPureSelectorToken,
5
5
  TimelineToken,
6
6
  TransactionToken,
7
7
  TransactionUpdate,
@@ -18,14 +18,14 @@ import { attachTransactionLogs } from "./attach-transaction-logs"
18
18
  import { attachTypeSelectors } from "./attach-type-selectors"
19
19
 
20
20
  export type IntrospectionStates = {
21
- atomIndex: ReadonlyPureSelectorToken<AtomTokenIndex>
22
- selectorIndex: ReadonlyPureSelectorToken<SelectorTokenIndex>
23
- transactionIndex: ReadonlyPureSelectorToken<TransactionToken<Func>[]>
21
+ atomIndex: AtomToken<AtomTokenIndex>
22
+ selectorIndex: AtomToken<SelectorTokenIndex>
23
+ transactionIndex: AtomToken<TransactionToken<Func>[]>
24
24
  transactionLogSelectors: ReadonlyPureSelectorFamilyToken<
25
25
  TransactionUpdate<Func>[],
26
26
  string
27
27
  >
28
- timelineIndex: ReadonlyPureSelectorToken<TimelineToken<any>[]>
28
+ timelineIndex: AtomToken<TimelineToken<any>[]>
29
29
  timelineSelectors: ReadonlyPureSelectorFamilyToken<Timeline<any>, string>
30
30
  typeSelectors: ReadonlyPureSelectorFamilyToken<Loadable<string>, string>
31
31
  }
@@ -1,8 +1,7 @@
1
- import type { ReadonlyPureSelectorToken, SelectorToken } from "atom.io"
1
+ import type { AtomToken, SelectorToken } from "atom.io"
2
2
  import type { Store } from "atom.io/internal"
3
3
  import {
4
4
  createRegularAtom,
5
- createStandaloneSelector,
6
5
  deposit,
7
6
  isReservedIntrospectionKey,
8
7
  } from "atom.io/internal"
@@ -13,97 +12,91 @@ export type SelectorTokenIndex = WritableTokenIndex<SelectorToken<unknown>>
13
12
 
14
13
  export const attachSelectorIndex = (
15
14
  store: Store,
16
- ): ReadonlyPureSelectorToken<SelectorTokenIndex> => {
17
- const readonlySelectorTokenIndexState__INTERNAL =
18
- createRegularAtom<SelectorTokenIndex>(
19
- store,
20
-
21
- {
22
- key: `🔍 Selector Token Index (Internal)`,
23
- default: () => {
24
- const base: SelectorTokenIndex = new Map()
25
- for (const map of [store.readonlySelectors, store.writableSelectors]) {
26
- for (const [key, val] of map) {
27
- if (isReservedIntrospectionKey(key)) {
28
- continue
29
- }
30
- const token = deposit(val)
31
- if (val.family) {
32
- let familyNode = base.get(val.family.key)
33
- if (!familyNode || !(`familyMembers` in familyNode)) {
34
- familyNode = {
35
- key: val.family.key,
36
- familyMembers: new Map(),
37
- }
38
- base.set(val.family.key, familyNode)
15
+ ): AtomToken<SelectorTokenIndex> => {
16
+ return createRegularAtom<SelectorTokenIndex>(
17
+ store,
18
+ {
19
+ key: `🔍 Selector Token Index`,
20
+ default: () => {
21
+ const base: SelectorTokenIndex = new Map()
22
+ for (const map of [store.readonlySelectors, store.writableSelectors]) {
23
+ for (const [key, val] of map) {
24
+ if (isReservedIntrospectionKey(key)) {
25
+ continue
26
+ }
27
+ const token = deposit(val)
28
+ if (val.family) {
29
+ let familyNode = base.get(val.family.key)
30
+ if (!familyNode || !(`familyMembers` in familyNode)) {
31
+ familyNode = {
32
+ key: val.family.key,
33
+ familyMembers: new Map(),
39
34
  }
40
- familyNode.familyMembers.set(val.family.subKey, token)
41
- } else {
42
- base.set(key, token)
35
+ base.set(val.family.key, familyNode)
43
36
  }
37
+ familyNode.familyMembers.set(val.family.subKey, token)
38
+ } else {
39
+ base.set(key, token)
44
40
  }
45
41
  }
46
- return base
47
- },
48
- effects: [
49
- ({ setSelf }) => {
50
- store.on.selectorCreation.subscribe(
51
- `introspection`,
52
- (selectorToken) => {
53
- if (isReservedIntrospectionKey(selectorToken.key)) {
54
- return
55
- }
42
+ }
43
+ return base
44
+ },
45
+ effects: [
46
+ ({ setSelf }) => {
47
+ store.on.selectorCreation.subscribe(
48
+ `introspection`,
49
+ (selectorToken) => {
50
+ if (isReservedIntrospectionKey(selectorToken.key)) {
51
+ return
52
+ }
56
53
 
57
- setSelf((self) => {
58
- if (selectorToken.family) {
59
- const { key: familyKey, subKey } = selectorToken.family
60
- let familyNode = self.get(familyKey)
61
- if (
62
- familyNode === undefined ||
63
- !(`familyMembers` in familyNode)
64
- ) {
65
- familyNode = {
66
- key: familyKey,
67
- familyMembers: new Map(),
68
- }
69
- self.set(familyKey, familyNode)
54
+ setSelf((self) => {
55
+ if (selectorToken.family) {
56
+ const { key: familyKey, subKey } = selectorToken.family
57
+ let familyNode = self.get(familyKey)
58
+ if (
59
+ familyNode === undefined ||
60
+ !(`familyMembers` in familyNode)
61
+ ) {
62
+ familyNode = {
63
+ key: familyKey,
64
+ familyMembers: new Map(),
70
65
  }
71
- familyNode.familyMembers.set(subKey, selectorToken)
72
- } else {
73
- self.set(selectorToken.key, selectorToken)
66
+ self.set(familyKey, familyNode)
74
67
  }
75
- return self
76
- })
77
- },
78
- )
68
+ familyNode.familyMembers.set(subKey, selectorToken)
69
+ } else {
70
+ self.set(selectorToken.key, selectorToken)
71
+ }
72
+ return new Map(self)
73
+ })
74
+ },
75
+ )
79
76
 
80
- store.on.selectorDisposal.subscribe(
81
- `introspection`,
82
- (selectorToken) => {
83
- setSelf((self) => {
84
- if (selectorToken.family) {
85
- const { key: familyKey, subKey } = selectorToken.family
86
- const familyNode = self.get(familyKey)
87
- if (familyNode && `familyMembers` in familyNode) {
88
- familyNode.familyMembers.delete(subKey)
89
- if (familyNode.familyMembers.size === 0) {
90
- self.delete(familyKey)
91
- }
77
+ store.on.selectorDisposal.subscribe(
78
+ `introspection`,
79
+ (selectorToken) => {
80
+ setSelf((self) => {
81
+ if (selectorToken.family) {
82
+ const { key: familyKey, subKey } = selectorToken.family
83
+ const familyNode = self.get(familyKey)
84
+ if (familyNode && `familyMembers` in familyNode) {
85
+ familyNode.familyMembers.delete(subKey)
86
+ if (familyNode.familyMembers.size === 0) {
87
+ self.delete(familyKey)
92
88
  }
93
- } else {
94
- self.delete(selectorToken.key)
95
89
  }
96
- return self
97
- })
98
- },
99
- )
100
- },
101
- ],
102
- },
103
- undefined,
104
- )
105
- return createStandaloneSelector(store, {
106
- key: `🔍 Selector Token Index`,
107
- get: ({ get }) => get(readonlySelectorTokenIndexState__INTERNAL),
108
- })
90
+ } else {
91
+ self.delete(selectorToken.key)
92
+ }
93
+ return new Map(self)
94
+ })
95
+ },
96
+ )
97
+ },
98
+ ],
99
+ },
100
+ undefined,
101
+ )
109
102
  }
@@ -1,16 +1,14 @@
1
- import type { ReadonlyPureSelectorToken, TimelineToken } from "atom.io"
1
+ import type { AtomToken, TimelineToken } from "atom.io"
2
2
  import type { Store } from "atom.io/internal"
3
- import { createRegularAtom, createStandaloneSelector } from "atom.io/internal"
3
+ import { createRegularAtom } from "atom.io/internal"
4
4
 
5
5
  export const attachTimelineIndex = (
6
6
  store: Store,
7
- ): ReadonlyPureSelectorToken<TimelineToken<any>[]> => {
8
- const timelineTokenIndexState__INTERNAL = createRegularAtom<
9
- TimelineToken<any>[]
10
- >(
7
+ ): AtomToken<TimelineToken<any>[]> => {
8
+ return createRegularAtom<TimelineToken<any>[]>(
11
9
  store,
12
10
  {
13
- key: `🔍 Timeline Token Index (Internal)`,
11
+ key: `🔍 Timeline Token Index`,
14
12
  default: () =>
15
13
  [...store.timelines].map(([key]): TimelineToken<any> => {
16
14
  return { key, type: `timeline` }
@@ -28,9 +26,4 @@ export const attachTimelineIndex = (
28
26
  },
29
27
  undefined,
30
28
  )
31
- const timelineTokenIndex = createStandaloneSelector(store, {
32
- key: `🔍 Timeline Token Index`,
33
- get: ({ get }) => get(timelineTokenIndexState__INTERNAL),
34
- })
35
- return timelineTokenIndex
36
29
  }
@@ -1,25 +1,32 @@
1
- import type { ReadonlyPureSelectorToken, TransactionToken } from "atom.io"
1
+ import type { AtomToken, TransactionToken } from "atom.io"
2
2
  import type { Func, Store } from "atom.io/internal"
3
- import { createRegularAtom, createStandaloneSelector } from "atom.io/internal"
3
+ import { createRegularAtom, isReservedIntrospectionKey } from "atom.io/internal"
4
4
 
5
5
  export const attachTransactionIndex = (
6
6
  store: Store,
7
- ): ReadonlyPureSelectorToken<TransactionToken<Func>[]> => {
8
- const transactionTokenIndexState__INTERNAL = createRegularAtom<
9
- TransactionToken<Func>[]
10
- >(
7
+ ): AtomToken<TransactionToken<Func>[]> => {
8
+ return createRegularAtom<TransactionToken<Func>[]>(
11
9
  store,
12
10
  {
13
- key: `🔍 Transaction Token Index (Internal)`,
14
- default: () =>
15
- [...store.transactions].map(([key]): TransactionToken<Func> => {
16
- return { key, type: `transaction` }
17
- }),
11
+ key: `🔍 Transaction Token Index`,
12
+ default: () => {
13
+ const tokens: TransactionToken<Func>[] = []
14
+ for (const [key] of store.transactions) {
15
+ if (isReservedIntrospectionKey(key)) {
16
+ continue
17
+ }
18
+ tokens.push({ key, type: `transaction` })
19
+ }
20
+ return tokens
21
+ },
18
22
  effects: [
19
23
  ({ setSelf }) => {
20
24
  store.on.transactionCreation.subscribe(
21
25
  `introspection`,
22
26
  (transactionToken) => {
27
+ if (isReservedIntrospectionKey(transactionToken.key)) {
28
+ return
29
+ }
23
30
  setSelf((state) => [...state, transactionToken])
24
31
  },
25
32
  )
@@ -28,9 +35,4 @@ export const attachTransactionIndex = (
28
35
  },
29
36
  undefined,
30
37
  )
31
- const transactionTokenIndex = createStandaloneSelector(store, {
32
- key: `🔍 Transaction Token Index`,
33
- get: ({ get }) => get(transactionTokenIndexState__INTERNAL),
34
- })
35
- return transactionTokenIndex
36
38
  }
@@ -2,7 +2,6 @@ import {
2
2
  type AtomToken,
3
3
  getState,
4
4
  type ReadableToken,
5
- type ReadonlyPureSelectorToken,
6
5
  type SelectorToken,
7
6
  } from "atom.io"
8
7
  import * as Internal from "atom.io/internal"
@@ -26,8 +25,8 @@ export class Auditor {
26
25
  public readonly store: Internal.Store
27
26
  public auditorCreatedAt: number = performance.now()
28
27
  public statesCreatedAt: Map<string, number> = new Map()
29
- public readonly atomIndex: ReadonlyPureSelectorToken<AtomTokenIndex>
30
- public readonly selectorIndex: ReadonlyPureSelectorToken<SelectorTokenIndex>
28
+ public readonly atomIndex: AtomToken<AtomTokenIndex>
29
+ public readonly selectorIndex: AtomToken<SelectorTokenIndex>
31
30
  public disposed = false
32
31
 
33
32
  private readonly unsubscribeFromAtomCreation: () => void
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ AtomToken,
2
3
  Loadable,
3
4
  ReadableToken,
4
5
  ReadonlyPureSelectorToken,
@@ -7,6 +8,7 @@ import type {
7
8
  import {
8
9
  actUponStore,
9
10
  arbitrary,
11
+ disposeFromStore,
10
12
  findInStore,
11
13
  getFromStore,
12
14
  } from "atom.io/internal"
@@ -16,6 +18,7 @@ import { useI, useO } from "atom.io/react"
16
18
  import { type FC, useContext } from "react"
17
19
 
18
20
  import { button } from "./Button"
21
+ import { DEFAULT_JSON_EDITOR_COMPONENTS } from "./json-editor"
19
22
  import { StoreEditor } from "./StateEditor"
20
23
  import { DevtoolsContext } from "./store"
21
24
 
@@ -25,7 +28,8 @@ export const StateIndexLeafNode: FC<{
25
28
  node: ReadableToken<unknown>
26
29
  isOpenState: RegularAtomToken<boolean>
27
30
  typeState: ReadonlyPureSelectorToken<Loadable<string>>
28
- }> = ({ node, isOpenState, typeState }) => {
31
+ dispose?: (() => void) | undefined
32
+ }> = ({ node, isOpenState, typeState, dispose }) => {
29
33
  const { openCloseAllTX, store } = useContext(DevtoolsContext)
30
34
 
31
35
  const setIsOpen = useI(isOpenState)
@@ -64,11 +68,23 @@ export const StateIndexLeafNode: FC<{
64
68
  <h2>{node.family?.subKey ?? node.key}</h2>
65
69
  <span className="type detail">({stateType})</span>
66
70
  </main>
67
- {isPrimitive ? (
68
- <StoreEditor token={node} />
69
- ) : (
70
- <div className="json_viewer">{JSON.stringify(state)}</div>
71
- )}
71
+ <footer>
72
+ {isPrimitive ? (
73
+ <StoreEditor token={node} />
74
+ ) : (
75
+ <div className="json_viewer">{JSON.stringify(state)}</div>
76
+ )}
77
+ {dispose ? (
78
+ <DEFAULT_JSON_EDITOR_COMPONENTS.Button
79
+ onClick={() => {
80
+ dispose?.()
81
+ }}
82
+ testid={`${node.key}-dispose`}
83
+ >
84
+ <DEFAULT_JSON_EDITOR_COMPONENTS.DeleteIcon />
85
+ </DEFAULT_JSON_EDITOR_COMPONENTS.Button>
86
+ ) : null}
87
+ </footer>
72
88
  </header>
73
89
  {isOpen && !isPrimitive ? (
74
90
  <main>
@@ -120,6 +136,9 @@ export const StateIndexTreeNode: FC<{
120
136
  node={childNode}
121
137
  isOpenState={findInStore(store, viewIsOpenAtoms, [node.key, key])}
122
138
  typeState={findInStore(store, typeSelectors, childNode.key)}
139
+ dispose={() => {
140
+ disposeFromStore(store, childNode)
141
+ }}
123
142
  />
124
143
  ))
125
144
  : null}
@@ -131,7 +150,8 @@ export const StateIndexNode: FC<{
131
150
  node: FamilyNode<ReadableToken<unknown>> | ReadableToken<unknown>
132
151
  isOpenState: RegularAtomToken<boolean>
133
152
  typeState: ReadonlyPureSelectorToken<Loadable<string>>
134
- }> = ({ node, isOpenState, typeState }) => {
153
+ dispose?: () => void
154
+ }> = ({ node, isOpenState, typeState, dispose }) => {
135
155
  return (
136
156
  <section className="node state" data-testid={`state-${node.key}`}>
137
157
  {`type` in node ? (
@@ -139,6 +159,7 @@ export const StateIndexNode: FC<{
139
159
  node={node}
140
160
  isOpenState={isOpenState}
141
161
  typeState={typeState}
162
+ dispose={dispose}
142
163
  />
143
164
  ) : (
144
165
  <StateIndexTreeNode node={node} isOpenState={isOpenState} />
@@ -148,26 +169,29 @@ export const StateIndexNode: FC<{
148
169
  }
149
170
 
150
171
  export const StateIndex: FC<{
151
- tokenIndex: ReadonlyPureSelectorToken<
152
- WritableTokenIndex<ReadableToken<unknown>>
153
- >
172
+ tokenIndex: AtomToken<WritableTokenIndex<ReadableToken<unknown>>>
154
173
  }> = ({ tokenIndex }) => {
155
174
  const tokenIds = useO(tokenIndex)
156
175
 
157
176
  const { typeSelectors, viewIsOpenAtoms, store } = useContext(DevtoolsContext)
177
+ const statesName = tokenIndex.key.includes(`Atom`) ? `atoms` : `selectors`
158
178
 
159
179
  return (
160
180
  <article className="index state_index" data-testid="state-index">
161
- {[...tokenIds.entries()].map(([key, node]) => {
162
- return (
163
- <StateIndexNode
164
- key={key}
165
- node={node}
166
- isOpenState={findInStore(store, viewIsOpenAtoms, [node.key])}
167
- typeState={findInStore(store, typeSelectors, node.key)}
168
- />
169
- )
170
- })}
181
+ {tokenIds.size === 0 ? (
182
+ <p className="index-empty-state">(no {statesName})</p>
183
+ ) : (
184
+ [...tokenIds.entries()].map(([key, node]) => {
185
+ return (
186
+ <StateIndexNode
187
+ key={key}
188
+ node={node}
189
+ isOpenState={findInStore(store, viewIsOpenAtoms, [node.key])}
190
+ typeState={findInStore(store, typeSelectors, node.key)}
191
+ />
192
+ )
193
+ })
194
+ )}
171
195
  </article>
172
196
  )
173
197
  }
@@ -95,18 +95,22 @@ export const TimelineIndex: FC = () => {
95
95
 
96
96
  return (
97
97
  <article className="index timeline_index" data-testid="timeline-index">
98
- {tokenIds
99
- .filter((token) => !token.key.startsWith(`👁‍🗨`))
100
- .map((token) => {
101
- return (
102
- <TimelineLog
103
- key={token.key}
104
- token={token}
105
- isOpenState={findInStore(store, viewIsOpenAtoms, [token.key])}
106
- timelineState={findInStore(store, timelineSelectors, token.key)}
107
- />
108
- )
109
- })}
98
+ {tokenIds.length === 0 ? (
99
+ <p className="index-empty-state">(no timelines)</p>
100
+ ) : (
101
+ tokenIds
102
+ .filter((token) => !token.key.startsWith(`👁‍🗨`))
103
+ .map((token) => {
104
+ return (
105
+ <TimelineLog
106
+ key={token.key}
107
+ token={token}
108
+ isOpenState={findInStore(store, viewIsOpenAtoms, [token.key])}
109
+ timelineState={findInStore(store, timelineSelectors, token.key)}
110
+ />
111
+ )
112
+ })
113
+ )}
110
114
  </article>
111
115
  )
112
116
  }
@@ -27,15 +27,15 @@ export const TransactionLog: FC<{
27
27
  data-testid={`transaction-${token.key}`}
28
28
  >
29
29
  <header>
30
- <button.OpenClose
31
- isOpen={isOpen}
32
- testid={`open-close-transaction-${token.key}`}
33
- setIsOpen={setIsOpen}
34
- />
35
30
  <main>
31
+ <button.OpenClose
32
+ isOpen={isOpen}
33
+ testid={`open-close-transaction-${token.key}`}
34
+ setIsOpen={setIsOpen}
35
+ />
36
36
  <h2>{token.key}</h2>
37
- <span className="detail length">({log.length})</span>
38
37
  </main>
38
+ <span className="detail length">({log.length})</span>
39
39
  </header>
40
40
  {isOpen ? (
41
41
  <main>
@@ -59,18 +59,22 @@ export const TransactionIndex: FC = () => {
59
59
  const tokenIds = useO(transactionIndex)
60
60
  return (
61
61
  <article className="index transaction_index" data-testid="transaction-index">
62
- {tokenIds
63
- .filter((token) => !token.key.startsWith(`🔍`))
64
- .map((token) => {
65
- return (
66
- <TransactionLog
67
- key={token.key}
68
- token={token}
69
- isOpenState={findInStore(store, viewIsOpenAtoms, [token.key])}
70
- logState={findInStore(store, transactionLogSelectors, token.key)}
71
- />
72
- )
73
- })}
62
+ {tokenIds.length === 0 ? (
63
+ <p className="index-empty-state">(no transactions)</p>
64
+ ) : (
65
+ tokenIds
66
+ .filter((token) => !token.key.startsWith(`🔍`))
67
+ .map((token) => {
68
+ return (
69
+ <TransactionLog
70
+ key={token.key}
71
+ token={token}
72
+ isOpenState={findInStore(store, viewIsOpenAtoms, [token.key])}
73
+ logState={findInStore(store, transactionLogSelectors, token.key)}
74
+ />
75
+ )
76
+ })
77
+ )}
74
78
  </article>
75
79
  )
76
80
  }