@danxbot/ui 4.1.11 → 5.0.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.
@@ -59,6 +59,32 @@ export interface ProvenanceDetailProps {
59
59
  * spellings of the whole component.
60
60
  */
61
61
  valueContent?: ReactNode;
62
+ /**
63
+ * Draw the top-right corner of the header yourself, in place of the built-in
64
+ * `ProvenanceBadge`.
65
+ *
66
+ * This replaces ONE thing — the badge beside the field name — and nothing
67
+ * else: the field name itself, `valueContent` on the line below it, and the
68
+ * confidence readout under that are still the component's. `ProvenanceBadge`
69
+ * exists because this library has an opinion about extraction verification;
70
+ * it has none about who ELSE has a stake in that same corner. A consumer that
71
+ * layers its own sign-off/attribution mark over a record — who answered,
72
+ * whether a human has signed it, an audit's own verdict — is naming a fact
73
+ * this library cannot: it owns the record's own fields, not a caller's
74
+ * broader authorship model. Forcing that mark one row lower (the only slot
75
+ * that reached the header before this one) put two badges answering related
76
+ * questions on two different lines instead of the one corner a reader
77
+ * actually scans.
78
+ *
79
+ * The SAME `??` idiom `valueContent` uses, and for the same reason: passing
80
+ * `null` falls back to the built-in `ProvenanceBadge` exactly as leaving the
81
+ * prop `undefined` does, so gating this on some later condition is
82
+ * `showOwnMark ? <OwnMark /> : null` rather than two spellings of the header.
83
+ *
84
+ * Does not touch the trigger — `ProvenanceDisclosure`'s closed-page mark and
85
+ * accessible name are unaffected either way; see `stateContent` there.
86
+ */
87
+ stateContent?: ReactNode;
62
88
  /**
63
89
  * Put a known id on the field-name heading, so something WRAPPING this can be
64
90
  * labelled by it.
@@ -73,4 +99,4 @@ export interface ProvenanceDetailProps {
73
99
  titleAs?: ProvenanceDetailTitleAs;
74
100
  className?: string;
75
101
  }
76
- export declare function ProvenanceDetail({ record, actions, emptyLabel, redactedLabel, ratingLabel, valueContent, titleId, titleAs: Title, className, }: ProvenanceDetailProps): import("react").JSX.Element;
102
+ export declare function ProvenanceDetail({ record, actions, emptyLabel, redactedLabel, ratingLabel, valueContent, stateContent, titleId, titleAs: Title, className, }: ProvenanceDetailProps): import("react").JSX.Element;
@@ -65,6 +65,16 @@ export interface ProvenanceDisclosureProps {
65
65
  * consumer supplied this.
66
66
  */
67
67
  valueContent?: ReactNode;
68
+ /**
69
+ * Draw the PANEL HEADER'S top-right corner yourself, in place of the
70
+ * built-in `ProvenanceBadge`. {@link ProvenanceDetailProps.stateContent}
71
+ *
72
+ * Like `valueContent`, this reaches only the panel — the trigger's own
73
+ * `triggerBadge` is a separate, independent choice about what the CLOSED
74
+ * page shows, and passing this does not change it or the trigger's
75
+ * accessible name (see "THE NAME IS COMPUTED FROM THE CONTENT" above).
76
+ */
77
+ stateContent?: ReactNode;
68
78
  /**
69
79
  * Called with `true` when the panel opens and `false` when it closes.
70
80
  *
@@ -94,4 +104,4 @@ export interface ProvenanceDisclosureProps {
94
104
  */
95
105
  width?: string;
96
106
  }
97
- export declare function ProvenanceDisclosure({ record, actions, emptyLabel, redactedLabel, ratingLabel, triggerBadge, valueWrap, triggerContent, valueContent, onOpenChange, className, width, }: ProvenanceDisclosureProps): import("react").JSX.Element;
107
+ export declare function ProvenanceDisclosure({ record, actions, emptyLabel, redactedLabel, ratingLabel, triggerBadge, valueWrap, triggerContent, valueContent, stateContent, onOpenChange, className, width, }: ProvenanceDisclosureProps): import("react").JSX.Element;
@@ -113,5 +113,5 @@ export { ProvenanceDisclosure, ProvenanceDetail, ProvenanceBadge, ConfidenceRead
113
113
  export { createSimStore, resetSimStores, useSimSettings, setSimSettings, getNetworkSpeed, getDataSource, simulateLatency, SimNetworkError, type SimStore, type SimStoreOptions, type SimSettings, type NetworkSpeed, type DataSource, type SimNamespace, } from "./lib/sim";
114
114
  export { NodeCanvas, LAYOUT_DEFAULTS, layoutTree, pathToNode, type NodeCanvasProps, type CanvasEdge, type CanvasLayout, type CanvasNode, type CanvasOrientation, type LayoutOptions, type NodeRenderContext, type NodeRenderer, type PlacedNode, } from "./components/node-canvas";
115
115
  export { defineRecordType, resetRecordTypes, useRecord, useRecords, useStaleness, write, writeAll, peekRecord, readState, readStaleness, subscribeToKey, subscribeToStaleness, retainKeys, sweepRecords, setRecordStoreTenant, getRecordStoreTenant, flushRecordNotifications, resetRecordStore, recordStoreStats, UNKNOWN_STATE, normalizeTypeName, normalizeTimestamp, recordKey, isRecordRef, type RecordRef, type ReadOptions, type RecordPayload, type RecordState, type RecordTypeOptions, type RecordTypeToken, type StalenessEntry, type WriteAllResult, type WriteOptions, type WriteResult, type WriteSource, type WriteVerdict, } from "./lib/records";
116
- export { RealtimeProvider, useRealtime, useRealtimeEpoch, useRealtimeHealth, useRealtimeRegistry, SubscriptionRegistry, normalizeScope, scopeKey, readRealtimeEpoch, subscribeToRealtimeEpoch, subscribeToRealtimeEvents, resetRealtimeSignals, type RealtimeChannel, type RealtimeConnectionState, type RealtimeDiagnostic, type RealtimeEvent, type RealtimeHealth, type RealtimeProviderProps, type RealtimeScope, type RealtimeScopeInput, type RealtimeStatus, type RealtimeTransport, type RegistryOptions, type SubscriptionHealth, type SubscriptionLease, type SubscriptionRequest, } from "./lib/realtime";
117
- export { useRecordRead, useRecordListRead, useRecordStoreSweep, applyMutationResult, acquireSlot, appendCreatedKey, emptySlotSnapshot, readSlot, resetSlots, slotKeys, subscribeToSlot, writeSlot, RefetchRunner, registerActiveRead, resetActiveReads, type ActiveRead, type ListFetch, type ListFetchResult, type ListReadOptions, type ListReadResult, type ReadError, type RecordFetch, type RecordReadOptions, type RecordReadResult, type RunnerOptions, type SlotSnapshot, } from "./lib/reads";
116
+ export { RealtimeProvider, useRealtime, useRealtimeEpoch, useRealtimeHealth, useRealtimeRegistry, SubscriptionRegistry, normalizeScope, scopeKey, subscriptionKey, readRealtimeEpoch, subscribeToRealtimeEpoch, subscribeToRealtimeEvents, resetRealtimeSignals, type RealtimeChannel, type RealtimeConnectionState, type RealtimeDiagnostic, type RealtimeEvent, type RealtimeHealth, type RealtimeProviderProps, type RealtimeScope, type RealtimeScopeInput, type RealtimeStatus, type RealtimeTransport, type RegistryOptions, type SubscriptionHealth, type SubscriptionLease, type SubscriptionRequest, } from "./lib/realtime";
117
+ export { useRecordRead, useRecordListRead, useRecordStoreSweep, applyMutationResult, acquireSlot, appendCreated, emptySlotSnapshot, readSlot, resetSlots, slotKeys, subscribeToSlot, writeSlot, RefetchRunner, registerActiveRead, resetActiveReads, type ActiveRead, type ListFetch, type ListFetchResult, type ListReadOptions, type ListReadResult, type ReadError, type RecordFetch, type RecordReadOptions, type RecordReadResult, type RunnerOptions, type SlotSnapshot, } from "./lib/reads";
@@ -1,4 +1,4 @@
1
1
  export { useRecordRead, useRecordListRead, applyMutationResult, useRecordStoreSweep } from "./hooks";
2
- export { acquireSlot, appendCreatedKey, emptySlotSnapshot, readSlot, resetSlots, slotKeys, subscribeToSlot, writeSlot, type SlotSnapshot, } from "./slots";
2
+ export { acquireSlot, appendCreated, emptySlotSnapshot, readSlot, resetSlots, slotKeys, subscribeToSlot, writeSlot, type SlotSnapshot, } from "./slots";
3
3
  export { RefetchRunner, registerActiveRead, resetActiveReads, type ActiveRead, type RunnerOptions } from "./refetch";
4
4
  export type { ListFetch, ListFetchResult, ListReadOptions, ListReadResult, ReadError, RecordFetch, RecordReadOptions, RecordReadResult, } from "./types";
@@ -1,3 +1,4 @@
1
+ import type { RealtimeEvent, RealtimeScope } from "../realtime";
1
2
  export interface SlotSnapshot<T = unknown, TMeta = unknown> {
2
3
  readonly items: readonly T[];
3
4
  readonly pending: readonly T[];
@@ -17,7 +18,7 @@ export declare function emptySlotSnapshot(): SlotSnapshot;
17
18
  export declare function acquireSlot(id: string, options: {
18
19
  type: string;
19
20
  tag: string;
20
- acceptsCreated: boolean;
21
+ createdScope?: RealtimeScope;
21
22
  }): () => void;
22
23
  /**
23
24
  * Replace a slot's membership with a fetch's answer.
@@ -25,15 +26,26 @@ export declare function acquireSlot(id: string, options: {
25
26
  * A key that arrived through a `created` event and is now in the fetched page
26
27
  * stops being pending — the fetch has placed it, which is the only thing that
27
28
  * ever could.
29
+ *
30
+ * @param settled the keys that were pending when this fetch was ISSUED
31
+ * (`slotPendingKeys`, read before the request). The answer covers them even
32
+ * when it does not hold them: a row that sorted onto another page is not
33
+ * "new" any more, it is placed — elsewhere. Without this a counted row that
34
+ * lands on page 2 stays "1 new" after Show, forever. A key appended after
35
+ * the request went out is not in this list, and stays pending.
28
36
  */
29
- export declare function writeSlot(id: string, keys: readonly string[], meta: unknown): void;
37
+ export declare function writeSlot(id: string, keys: readonly string[], meta: unknown, settled?: readonly string[]): void;
38
+ /** The keys a slot is holding as pending right now — what a fetch issued now will settle. */
39
+ export declare function slotPendingKeys(id: string): readonly string[];
30
40
  /**
31
- * Append a record that entered the set elsewhere.
41
+ * Append a record that entered the set elsewhere, to every slot it belongs to.
32
42
  *
33
43
  * WITH NO POSITION. The server owns sort order and page boundary; a client that
34
44
  * inserts at a guessed index renders a list that is subtly, confidently wrong.
45
+ *
46
+ * AND ONLY WHERE IT BELONGS — see `belongsTo`.
35
47
  */
36
- export declare function appendCreatedKey(type: string, key: string): void;
48
+ export declare function appendCreated(event: RealtimeEvent): void;
37
49
  export declare function subscribeToSlot(id: string, listener: () => void): () => void;
38
50
  /** The keys a slot holds right now — the roots a targeted refetch matches on. */
39
51
  export declare function slotKeys(id: string): readonly string[];
@@ -1,4 +1,5 @@
1
1
  import type { RecordPayload, RecordState } from "../records";
2
+ import type { RealtimeScopeInput } from "../realtime";
2
3
  /**
3
4
  * A refusal.
4
5
  *
@@ -76,18 +77,28 @@ export interface ListReadOptions<TMeta = unknown> extends CommonReadOptions {
76
77
  readonly key: string;
77
78
  readonly fetch: ListFetch<TMeta>;
78
79
  /**
79
- * Whether a `created` broadcast for this type may append to this slot.
80
- * Default `true`.
80
+ * The realtime scope whose `created` events enter this list — normally the
81
+ * very scope the screen passes to `useRealtime` for this type. Omit it and no
82
+ * `created` ever appends here.
81
83
  *
82
- * SET IT FALSE FOR A SLOT WHOSE MEMBERSHIP THIS CLIENT CANNOT VERIFY. The
83
- * backend evaluates a filter subscription server-side per save, so a
84
- * `created` event arriving on such a subscription IS authoritative that the
85
- * record matches — but the library cannot tell WHICH of several slots of the
86
- * same type it matches when a screen holds more than one differently-filtered
87
- * list. Appending to all of them would be a guess; this flag is where the app
88
- * says which one owns the event.
84
+ * WHY A LIST HAS TO SAY THIS. The channel carries every record of the type
85
+ * for the whole tenant, and the server sends an event there when ANY
86
+ * subscription on the tenant matched. So "a `created` arrived" never meant
87
+ * "it matches this list": a queue filtered to schema A counted a demand of
88
+ * no schema at all because a teammate had an unfiltered list open — observed,
89
+ * and the reason this replaced a boolean that appended every `created` of the
90
+ * type to every list that accepted one.
91
+ *
92
+ * How membership is decided per scope:
93
+ * - `"all"` — every `created` of the type belongs.
94
+ * - ids — a `created` whose id the list names belongs.
95
+ * - `{filter}` — only a `created` the server ATTRIBUTED to this scope
96
+ * (`RealtimeEvent.subscriptions`). The transport must implement
97
+ * `subscriptionsOf`, and this scope must also be subscribed with
98
+ * `useRealtime` — the attribution names this client's own subscriptions,
99
+ * so a filter nobody here subscribed to is never named.
89
100
  */
90
- readonly acceptsCreated?: boolean;
101
+ readonly createdScope?: RealtimeScopeInput;
91
102
  }
92
103
  /** The loading / refreshing / error triple, shared by both reads. */
93
104
  interface ReadStatus {
@@ -1,4 +1,4 @@
1
1
  export { RealtimeProvider, useRealtimeRegistry, type RealtimeProviderProps } from "./provider";
2
2
  export { useRealtime, useRealtimeEpoch, useRealtimeHealth } from "./hooks";
3
- export { SubscriptionRegistry, normalizeScope, scopeKey, readRealtimeEpoch, subscribeToRealtimeEpoch, subscribeToRealtimeEvents, resetRealtimeSignals, type RegistryOptions, } from "./registry";
3
+ export { SubscriptionRegistry, normalizeScope, scopeKey, subscriptionKey, readRealtimeEpoch, subscribeToRealtimeEpoch, subscribeToRealtimeEvents, resetRealtimeSignals, type RegistryOptions, } from "./registry";
4
4
  export type { RealtimeChannel, RealtimeConnectionState, RealtimeDiagnostic, RealtimeEvent, RealtimeHealth, RealtimeScope, RealtimeScopeInput, RealtimeStatus, RealtimeTransport, SubscriptionHealth, SubscriptionLease, SubscriptionRequest, } from "./types";
@@ -48,6 +48,16 @@ export declare function resetRealtimeSignals(): void;
48
48
  */
49
49
  export declare function normalizeScope(input: RealtimeScopeInput): RealtimeScope;
50
50
  export declare function scopeKey(scope: RealtimeScope): string;
51
+ /**
52
+ * The registry's key for a (type, scope) subscription — the identity
53
+ * `RealtimeEvent.subscriptions` names.
54
+ *
55
+ * ONE FUNCTION FOR BOTH SIDES. The registry keys its entries with it and a list
56
+ * slot keys its declared `createdScope` with it, so the two cannot disagree
57
+ * about what "the same subscription" means — a type spelled `WorkflowInput`
58
+ * here and `WorkflowInputResource` there would silently never match.
59
+ */
60
+ export declare function subscriptionKey(type: string, scope: RealtimeScopeInput | RealtimeScope): string;
51
61
  export declare class SubscriptionRegistry {
52
62
  private readonly transport;
53
63
  private readonly entries;
@@ -155,6 +165,17 @@ export declare class SubscriptionRegistry {
155
165
  * lands in the store as the team you just joined.
156
166
  */
157
167
  private deliver;
168
+ /**
169
+ * This client's subscriptions a broadcast satisfied, translated from the
170
+ * server's names through each entry's lease keys.
171
+ *
172
+ * THE WHOLE REASON THIS IS NEEDED: the channel is per type per tenant, so an
173
+ * event arrives here whenever ANY subscription on the tenant matched — a
174
+ * teammate's unfiltered list included. Arrival says "somebody on this team
175
+ * asked"; only this says "you asked". `undefined` when the transport cannot
176
+ * say, which is a different answer from `[]` ("it said, and none were yours").
177
+ */
178
+ private attribute;
158
179
  /** The event name that means "entered the set", for the read binding. */
159
180
  get createdEventName(): string;
160
181
  subscribeToHealth: (listener: () => void) => (() => void);
@@ -12,10 +12,13 @@ export type RealtimeConnectionState = "connecting" | "connected" | "disconnected
12
12
  *
13
13
  * - `ids` — the rows on screen. The backend keys its registry entry per model
14
14
  * id, so this is the scope that pins specific records.
15
- * - `filter` — a server-evaluated predicate. The backend re-runs it per save,
16
- * which is what makes a `created` event authoritative about set entry. It
17
- * answers "does this record match my filter" and NOTHING about sort position
18
- * or page boundary.
15
+ * - `filter` — a server-evaluated predicate. The backend re-runs it per save
16
+ * and answers "does this record match this filter" — and NOTHING about sort
17
+ * position or page boundary. ARRIVAL ALONE PROVES NOTHING about this filter:
18
+ * the channel is per type per tenant, so an event arrives whenever ANY
19
+ * subscription on the tenant matched, including a teammate's wider one. Only
20
+ * `RealtimeEvent.subscriptions` says which of this client's scopes it
21
+ * satisfied.
19
22
  * - `all` — every record of the type for this tenant.
20
23
  */
21
24
  export type RealtimeScope = {
@@ -57,6 +60,16 @@ export interface SubscriptionRequest {
57
60
  export interface SubscriptionLease {
58
61
  /** Milliseconds since epoch. Omit only if the server genuinely does not say. */
59
62
  readonly expiresAt?: number;
63
+ /**
64
+ * The SERVER'S names for this registration — one per server-side entry the
65
+ * request fanned out to — in the same vocabulary `subscriptionsOf` returns.
66
+ *
67
+ * THIS IS HALF OF ATTRIBUTION. A broadcast names the server entries it
68
+ * satisfied; the registry maps those names back to its own subscriptions
69
+ * through these. Omitted on a keepalive, the names from the last subscribe
70
+ * stand — they describe the registration, which a renewal does not change.
71
+ */
72
+ readonly keys?: readonly string[];
60
73
  }
61
74
  /** A joined socket channel. One per type per tenant, and no more. */
62
75
  export interface RealtimeChannel {
@@ -111,6 +124,22 @@ export interface RealtimeTransport {
111
124
  * warning, and the registry resubscribes rather than waiting.
112
125
  */
113
126
  keepalive(requests: readonly SubscriptionRequest[]): Promise<readonly SubscriptionLease[]>;
127
+ /**
128
+ * Which server-side registrations a broadcast satisfied, in the vocabulary of
129
+ * `SubscriptionLease.keys` — or `undefined` when this payload does not say.
130
+ *
131
+ * WHY IT EXISTS: the channel carries every record of a type for a whole
132
+ * tenant, and the server sends an event there when ANY subscription on the
133
+ * tenant matched. Without this a list filtered to schema A counts a record
134
+ * that arrived only because a teammate has an unfiltered list open —
135
+ * observed, not theorised. A method rather than a field name because the
136
+ * field belongs to the wire format, like `isSelfEvent`.
137
+ *
138
+ * Optional: a transport whose server cannot attribute leaves it off, and a
139
+ * list whose membership needs attribution (a `filter` scope) then counts
140
+ * nothing rather than guessing.
141
+ */
142
+ subscriptionsOf?(payload: RecordPayload): readonly string[] | undefined;
114
143
  /** Join (or return) the channel carrying this type for this tenant. */
115
144
  channel(type: string, tenant: string | number | undefined): RealtimeChannel;
116
145
  connectionState(): RealtimeConnectionState;
@@ -197,6 +226,17 @@ export interface RealtimeEvent {
197
226
  * consumer comparing the string would be the second place that fact lives.
198
227
  */
199
228
  readonly isCreate: boolean;
229
+ /**
230
+ * This client's subscriptions the broadcast satisfied, by registry key
231
+ * (`subscriptionKey(type, scope)`), or `undefined` when the transport could
232
+ * not attribute it.
233
+ *
234
+ * AN EMPTY ARRAY IS AN ANSWER, not a missing one: the server attributed the
235
+ * event and none of the names were this client's — it is on the channel for
236
+ * somebody else's subscription. That is the case a filtered list must not
237
+ * count.
238
+ */
239
+ readonly subscriptions: readonly string[] | undefined;
200
240
  readonly payload: RecordPayload;
201
241
  readonly tenant: string | number | undefined;
202
242
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danxbot/ui",
3
- "version": "4.1.11",
3
+ "version": "5.0.0",
4
4
  "type": "module",
5
5
  "description": "Danxbot — a domain-agnostic React design system with motion as a first-class primitive.",
6
6
  "license": "MIT",