wenay-react2 1.0.49 → 1.0.51
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 +14 -14
- package/doc/EXAMPLE_USAGE.md +48 -0
- package/doc/PROJECT_FUNCTIONALITY.md +13 -2
- package/doc/PROJECT_RULES.md +27 -27
- package/doc/WENAY_REACT2_RENAMES.md +170 -170
- package/doc/changes/v1.0.41.md +35 -35
- package/doc/changes/v1.0.42.md +26 -26
- package/doc/changes/v1.0.43.md +10 -10
- package/doc/changes/v1.0.44.md +14 -14
- package/doc/changes/v1.0.45.md +8 -8
- package/doc/changes/v1.0.46.md +11 -11
- package/doc/changes/v1.0.47.md +9 -9
- package/doc/changes/v1.0.48.md +8 -8
- package/doc/changes/v1.0.49.md +7 -7
- package/doc/changes/v1.0.50.md +7 -0
- package/doc/changes/v1.0.51.md +10 -0
- package/doc/examples/README.md +1 -1
- package/doc/examples/conference-client.html +34 -34
- package/doc/examples/conference-client.ts +212 -212
- package/doc/examples/conference-server.mjs +150 -150
- package/doc/examples/peer-call-media.tsx +7 -7
- package/doc/examples/stand.tsx +5 -5
- package/doc/native.md +37 -37
- package/doc/progress/README.md +13 -13
- package/doc/progress/architecture-fix-queue.md +74 -74
- package/doc/progress/column-state-present-gate.md +28 -28
- package/doc/progress/common2-adoption-1.0.73.md +28 -28
- package/doc/progress/common2-adoption-1.0.74.md +24 -24
- package/doc/progress/common2-adoption-1.0.75.md +120 -0
- package/doc/progress/hook-controller-opportunities.md +363 -363
- package/doc/progress/hook-extraction-audit.md +195 -195
- package/doc/progress/public-surface-normalization.md +351 -351
- package/doc/progress/qa-stand-walkthrough-2026-07-12.md +62 -62
- package/doc/progress/stand-as-examples-audit.md +20 -20
- package/doc/progress/style-system-normalization.md +121 -121
- package/doc/target/README.md +32 -32
- package/doc/target/my.md +11 -3
- package/doc/wenay-react2-rare.md +11 -7
- package/doc/wenay-react2.md +2 -2
- package/lib/common/src/grid/agGrid4/core.js +7 -2
- package/lib/common/src/hooks/useReplay.js +13 -2
- package/lib/common/src/logs/logs.d.ts +4 -1
- package/lib/common/src/logs/logs.js +19 -11
- package/lib/common/testUseReact/replayVideo.d.ts +5 -0
- package/lib/common/testUseReact/replayVideo.js +98 -1
- package/lib/style/menuRight.css +19 -19
- package/lib/style/style.css +23 -23
- package/lib/style/tokens.css +184 -184
- package/package.json +54 -54
- package/doc/changes/v1.0.35.md +0 -26
- package/doc/changes/v1.0.37.md +0 -13
- package/doc/changes/v1.0.38.md +0 -48
- package/doc/changes/v1.0.39.md +0 -35
- package/doc/changes/v1.0.40.md +0 -15
|
@@ -60,7 +60,12 @@ export function createGridBuffer(deps) {
|
|
|
60
60
|
if (seen.has(id))
|
|
61
61
|
continue;
|
|
62
62
|
seen.add(id);
|
|
63
|
-
|
|
63
|
+
// AG Grid replaces a transaction row. Overlay streams may send a
|
|
64
|
+
// Partial<T>, so preserve static fields from the rowData-owned row.
|
|
65
|
+
const node = mode == 'overlay' ? api.getRowNode?.(id) : undefined;
|
|
66
|
+
const merged = node?.data
|
|
67
|
+
? Object.assign({}, node.data, buf[id])
|
|
68
|
+
: buf[id];
|
|
64
69
|
if (gridHas(id))
|
|
65
70
|
toUpdate.push(merged);
|
|
66
71
|
else
|
|
@@ -104,7 +109,7 @@ export function createGridBuffer(deps) {
|
|
|
104
109
|
return;
|
|
105
110
|
const id = getId(node.data);
|
|
106
111
|
if (buf[id])
|
|
107
|
-
update.push(buf[id]);
|
|
112
|
+
update.push(Object.assign({}, node.data, buf[id]));
|
|
108
113
|
});
|
|
109
114
|
if (update.length)
|
|
110
115
|
api.applyTransaction({ update });
|
|
@@ -49,10 +49,18 @@ export function useReplaySubscribe(remote, cb, options = {}) {
|
|
|
49
49
|
useEffect(() => {
|
|
50
50
|
if (!remote || !enabled)
|
|
51
51
|
return;
|
|
52
|
+
if (!remote.line || typeof remote.line.on != 'function') {
|
|
53
|
+
const error = new Error('Replay remote is missing its line surface');
|
|
54
|
+
setReady(false);
|
|
55
|
+
setError(error);
|
|
56
|
+
hooksRef.current.onError?.(error);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
52
59
|
if (lastRemoteRef.current !== undefined && lastRemoteRef.current !== remote)
|
|
53
60
|
seqRef.current = undefined; // a different line — old seq is meaningless
|
|
54
61
|
lastRemoteRef.current = remote;
|
|
55
62
|
let alive = true;
|
|
63
|
+
let failed = false;
|
|
56
64
|
setReady(false);
|
|
57
65
|
setError(null);
|
|
58
66
|
// stale is NOT reset here: it re-syncs from common2 after the first delivery (a stale
|
|
@@ -68,8 +76,11 @@ export function useReplaySubscribe(remote, cb, options = {}) {
|
|
|
68
76
|
hooksRef.current.onSeq?.(seq);
|
|
69
77
|
},
|
|
70
78
|
onError: e => {
|
|
71
|
-
|
|
79
|
+
failed = true;
|
|
80
|
+
if (alive) {
|
|
72
81
|
setError(e);
|
|
82
|
+
setReady(false);
|
|
83
|
+
}
|
|
73
84
|
hooksRef.current.onError?.(e);
|
|
74
85
|
},
|
|
75
86
|
...(staleMs !== undefined ? {
|
|
@@ -83,7 +94,7 @@ export function useReplaySubscribe(remote, cb, options = {}) {
|
|
|
83
94
|
});
|
|
84
95
|
subRef.current = off;
|
|
85
96
|
off.ready.then(() => {
|
|
86
|
-
if (!alive)
|
|
97
|
+
if (!alive || failed)
|
|
87
98
|
return;
|
|
88
99
|
setReady(true);
|
|
89
100
|
if (staleMs !== undefined)
|
|
@@ -55,7 +55,7 @@ export declare function useLogsPageTable(state?: LogsViewState): {
|
|
|
55
55
|
getApi: () => GridReadyEvent<any, any> | null;
|
|
56
56
|
fit: () => void;
|
|
57
57
|
applyImportanceFilter: (min?: number) => void;
|
|
58
|
-
|
|
58
|
+
syncRows: () => void;
|
|
59
59
|
onGridReady: (a: GridReadyEvent<LogRow>) => void;
|
|
60
60
|
columnDefs: ({
|
|
61
61
|
field: string;
|
|
@@ -96,6 +96,9 @@ export declare function useLogsPageTable(state?: LogsViewState): {
|
|
|
96
96
|
rowHeight: number;
|
|
97
97
|
autoSizePadding: number;
|
|
98
98
|
rowData: any[];
|
|
99
|
+
getRowId: (p: {
|
|
100
|
+
data: LogRow;
|
|
101
|
+
}) => string;
|
|
99
102
|
columnDefs: ({
|
|
100
103
|
field: string;
|
|
101
104
|
sort: "desc";
|
|
@@ -80,10 +80,10 @@ function InputSettingLogs({ settings }) {
|
|
|
80
80
|
export function useLogsPageTable(state) {
|
|
81
81
|
const setting = state?.settings ?? memoryGetOrCreate("settingLogs", settingLogs);
|
|
82
82
|
const full = state?.full ?? datumConst;
|
|
83
|
-
const mini = state?.mini ?? datumMiniConst;
|
|
84
83
|
const apiGrid = useRef(null);
|
|
85
84
|
// mount-time snapshot: the live grid is fed by transactions, not re-renders
|
|
86
85
|
const [rowData] = useState(() => [...full.map.values()].flat());
|
|
86
|
+
const shownRows = useRef(new Map(rowData.map(row => [row.num, row])));
|
|
87
87
|
const getApi = useCallback(() => apiGrid.current, []);
|
|
88
88
|
const fit = useCallback(() => { apiGrid.current?.api.sizeColumnsToFit(); }, []);
|
|
89
89
|
const applyImportanceFilter = useCallback((min) => {
|
|
@@ -103,26 +103,33 @@ export function useLogsPageTable(state) {
|
|
|
103
103
|
api.destroyFilter("var");
|
|
104
104
|
}
|
|
105
105
|
}, []);
|
|
106
|
-
const
|
|
107
|
-
apiGrid.current?.api
|
|
108
|
-
|
|
106
|
+
const syncRows = useCallback(() => {
|
|
107
|
+
const api = apiGrid.current?.api;
|
|
108
|
+
if (!api)
|
|
109
|
+
return;
|
|
110
|
+
const next = new Map([...full.map.values()].flat().map(row => [row.num, row]));
|
|
111
|
+
const add = [...next].filter(([num]) => !shownRows.current.has(num)).map(([, row]) => row);
|
|
112
|
+
const remove = [...shownRows.current].filter(([num]) => !next.has(num)).map(([, row]) => row);
|
|
113
|
+
shownRows.current = next;
|
|
114
|
+
if (add.length || remove.length)
|
|
115
|
+
api.applyTransactionAsync({ add, remove });
|
|
116
|
+
}, [full]);
|
|
109
117
|
// settings change -> single filter method (no re-render: updateBy with a callback)
|
|
110
118
|
updateBy(setting, () => {
|
|
111
119
|
applyImportanceFilter(setting.params.minVarLogs);
|
|
112
120
|
});
|
|
113
|
-
//
|
|
114
|
-
updateBy(
|
|
115
|
-
|
|
116
|
-
if (data)
|
|
117
|
-
appendRow({ ...data });
|
|
121
|
+
// The full state owns retention, so reconcile additions and evictions together.
|
|
122
|
+
updateBy(full, () => {
|
|
123
|
+
syncRows();
|
|
118
124
|
});
|
|
119
125
|
const onGridReady = useCallback((a) => {
|
|
120
126
|
apiGrid.current = a;
|
|
127
|
+
syncRows();
|
|
121
128
|
fit();
|
|
122
129
|
// fresh grid has no filter - only apply when the setting asks for one
|
|
123
130
|
if (setting.params.minVarLogs)
|
|
124
131
|
applyImportanceFilter(setting.params.minVarLogs);
|
|
125
|
-
}, [applyImportanceFilter, fit, setting]);
|
|
132
|
+
}, [applyImportanceFilter, fit, setting, syncRows]);
|
|
126
133
|
const columnDefs = useMemo(() => [
|
|
127
134
|
{
|
|
128
135
|
field: "time",
|
|
@@ -169,6 +176,7 @@ export function useLogsPageTable(state) {
|
|
|
169
176
|
rowHeight: 26,
|
|
170
177
|
autoSizePadding: 1,
|
|
171
178
|
rowData,
|
|
179
|
+
getRowId: (p) => String(p.data.num),
|
|
172
180
|
columnDefs,
|
|
173
181
|
onCellMouseDown: (e) => {
|
|
174
182
|
if (e.event instanceof MouseEvent && e.event.button == 2) {
|
|
@@ -180,7 +188,7 @@ export function useLogsPageTable(state) {
|
|
|
180
188
|
}
|
|
181
189
|
},
|
|
182
190
|
}), [columnDefs, onGridReady, rowData]);
|
|
183
|
-
return { getApi, fit, applyImportanceFilter,
|
|
191
|
+
return { getApi, fit, applyImportanceFilter, syncRows, onGridReady, columnDefs, gridProps };
|
|
184
192
|
}
|
|
185
193
|
export function PageLogs({ update, state }) {
|
|
186
194
|
const table = useLogsPageTable(state);
|
|
@@ -10,5 +10,10 @@ export declare const videoResolutions: readonly [{
|
|
|
10
10
|
}];
|
|
11
11
|
export declare const ReplayRouteDemo: () => import("react/jsx-runtime").JSX.Element;
|
|
12
12
|
export declare const ReplayVideoDemo: () => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
/**
|
|
14
|
+
* Real browser transport regression card. The server endpoint is the Vite-only
|
|
15
|
+
* `qaReplay` RPC facade; this component receives only its stable common2 remote.
|
|
16
|
+
*/
|
|
17
|
+
export declare const ReplayRpcReconnectDemo: () => import("react/jsx-runtime").JSX.Element;
|
|
13
18
|
export declare const ReplayStoreDemo: () => import("react/jsx-runtime").JSX.Element;
|
|
14
19
|
export declare const ReplayStoreEachDemo: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -8,7 +8,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
* these cards exercise the React lifecycle side.
|
|
9
9
|
*/
|
|
10
10
|
import React, { StrictMode, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
-
import { Observe, Replay } from "wenay-common2";
|
|
11
|
+
import { createRpcClientHub, Observe, Replay } from "wenay-common2";
|
|
12
|
+
import { io } from "socket.io-client";
|
|
12
13
|
import { useReplaySubscribe, useReplayRouteSubscribe, useReplayFrame, useReplayHistory, useStoreReplayMirror, useStoreReplayEach, useStoreNode, useStoreKeys } from "../src/hooks";
|
|
13
14
|
const FPS = 10;
|
|
14
15
|
export const videoResolutions = [{ w: 160, h: 90 }, { w: 320, h: 180 }, { w: 640, h: 360 }];
|
|
@@ -233,6 +234,102 @@ export const ReplayVideoDemo = () => {
|
|
|
233
234
|
? _jsx(LiveClient, { remote: demo.remoteDirect, since: lastSeqA.current, onSeq: seq => { lastSeqA.current = seq; } })
|
|
234
235
|
: _jsxs("div", { style: { ...canvasStyle, height: 180, display: "flex", alignItems: "center", justifyContent: "center", color: "#8b949e" }, children: ["unmounted \u00B7 kept seq ", lastSeqA.current ?? "-"] })] }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 700, marginBottom: 4 }, children: "B: slow wire + conflateReplay gate" }), _jsx(LiveClient, { remote: demo.wire.remote }), _jsxs("div", { style: statLine, children: ["wire buffer ", demo.wire.buffered(), " \u00B7 conflating ", String(wireStats.conflating), _jsx("br", {}), "dropped ", wireStats.dropped, " \u00B7 coalesced ", wireStats.coalesced, " \u00B7 recoveries ", wireStats.flushes, " (keyframes ", wireStats.keyframes, ")"] })] }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 700, marginBottom: 4 }, children: "C: time travel (archive + openHistory)" }), _jsx("canvas", { ref: ttCanvasRef, style: canvasStyle }), _jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 4 }, children: [_jsx("button", { onClick: () => tt.live ? tt.pause() : tt.play(), children: tt.live ? "⏸ pause" : "▶ live" }), _jsx("input", { type: "range", style: { width: 200 }, min: Math.max(0, tt.head - 550), max: Math.max(0, tt.head), value: tt.seq < 0 ? 0 : tt.seq, onChange: e => tt.seek({ seq: Number(e.target.value) }) }), _jsxs("span", { style: { fontSize: 12, fontFamily: "monospace" }, children: [tt.seq, "/", tt.head, " ", tt.live ? "live" : "paused"] })] })] }), _jsxs("div", { children: [_jsxs("div", { style: { fontSize: 13, fontWeight: 700, marginBottom: 4 }, children: ["D: freshness (staleMs=", STALE_MS, ", StrictMode)", " ", _jsx("button", { onClick: () => setGenD(v => v + 1), children: "new client (keyframe)" })] }), _jsx(StrictMode, { children: _jsx(StaleClient, { remote: demo.remoteDirect, staleMs: STALE_MS }, genD) }), _jsx("div", { style: { fontSize: 12, color: "#57606a", maxWidth: 320, marginTop: 4 }, children: "counters refresh only on fresh\u2194stale flips: a flat \"renders\" while frames grow is the no-per-event-render proof" })] }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 700, marginBottom: 4 }, children: "E: pull at own pace (useReplayFrame)" }), _jsx(PullClient, { remote: demo.remoteDirect })] })] })] });
|
|
235
236
|
};
|
|
237
|
+
const RpcReplayConsumer = React.memo(function RpcReplayConsumer(p) {
|
|
238
|
+
p.metrics.current.renders++;
|
|
239
|
+
const sub = useReplaySubscribe(p.remote, value => {
|
|
240
|
+
// This is intentionally an append-only QA probe. It reports duplicates; it never
|
|
241
|
+
// suppresses them, so a green card is evidence from the real transport instead of
|
|
242
|
+
// a React-side dedupe mask.
|
|
243
|
+
p.metrics.current.values.push(value);
|
|
244
|
+
p.metrics.current.latestCallbackRevision = p.callbackRevision;
|
|
245
|
+
}, {
|
|
246
|
+
since: 0,
|
|
247
|
+
policy: "queue",
|
|
248
|
+
onSeq: () => { },
|
|
249
|
+
onError: error => { p.metrics.current.errors.push(String(error)); },
|
|
250
|
+
onStale: () => { },
|
|
251
|
+
hint: { callbackRevision: p.callbackRevision },
|
|
252
|
+
});
|
|
253
|
+
p.metrics.current.seq = sub.seq;
|
|
254
|
+
p.metrics.current.ready = sub.ready;
|
|
255
|
+
return null;
|
|
256
|
+
});
|
|
257
|
+
/**
|
|
258
|
+
* Real browser transport regression card. The server endpoint is the Vite-only
|
|
259
|
+
* `qaReplay` RPC facade; this component receives only its stable common2 remote.
|
|
260
|
+
*/
|
|
261
|
+
export const ReplayRpcReconnectDemo = () => {
|
|
262
|
+
const hub = useMemo(() => createRpcClientHub(() => io({ path: "/__qa/replay-rpc", transports: ["websocket"], reconnection: false }), rpc => ({ qaReplay: rpc("qaReplay") })), []);
|
|
263
|
+
const metrics = useRef({ values: [], errors: [], renders: 0, latestCallbackRevision: -1, seq: () => -1, ready: false });
|
|
264
|
+
const firstRemote = useRef(null);
|
|
265
|
+
const [remote, setRemote] = useState(null);
|
|
266
|
+
const [mounted, setMounted] = useState(true);
|
|
267
|
+
const [callbackRevision, setCallbackRevision] = useState(0);
|
|
268
|
+
const [connected, setConnected] = useState(false);
|
|
269
|
+
const [snapshot, setSnapshot] = useState({ produced: 0, head: -1, listeners: 0, producing: false, received: 0, missing: 0, duplicates: 0, ordered: true, renders: 0, seq: -1, ready: false, errors: 0 });
|
|
270
|
+
const refresh = async () => {
|
|
271
|
+
const api = hub.facade.qaReplay?.func;
|
|
272
|
+
let server = { produced: 0, head: -1, listeners: 0, producing: false };
|
|
273
|
+
try {
|
|
274
|
+
if (api)
|
|
275
|
+
server = await api.stats();
|
|
276
|
+
}
|
|
277
|
+
catch { /* offline: retain local transport evidence */ }
|
|
278
|
+
const values = metrics.current.values;
|
|
279
|
+
const seen = new Set(values);
|
|
280
|
+
const missing = Array.from({ length: server.produced }, (_, i) => i + 1).filter(value => !seen.has(value)).length;
|
|
281
|
+
const duplicates = values.length - seen.size;
|
|
282
|
+
setSnapshot({
|
|
283
|
+
...server,
|
|
284
|
+
received: values.length,
|
|
285
|
+
missing,
|
|
286
|
+
duplicates,
|
|
287
|
+
ordered: values.every((value, index) => index == 0 || values[index - 1] < value),
|
|
288
|
+
renders: metrics.current.renders,
|
|
289
|
+
seq: metrics.current.seq(),
|
|
290
|
+
ready: metrics.current.ready,
|
|
291
|
+
errors: metrics.current.errors.length,
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
let alive = true;
|
|
296
|
+
const offConnect = hub.connectListen(() => { if (alive) {
|
|
297
|
+
setConnected(true);
|
|
298
|
+
void refresh();
|
|
299
|
+
} });
|
|
300
|
+
const offDisconnect = hub.disconnectListen(() => { if (alive) {
|
|
301
|
+
setConnected(false);
|
|
302
|
+
void refresh();
|
|
303
|
+
} });
|
|
304
|
+
void hub.setToken(null).then(clients => {
|
|
305
|
+
if (!alive)
|
|
306
|
+
return;
|
|
307
|
+
const next = clients.qaReplay.func.events;
|
|
308
|
+
firstRemote.current ??= next;
|
|
309
|
+
setRemote(next);
|
|
310
|
+
setConnected(true);
|
|
311
|
+
void refresh();
|
|
312
|
+
}).catch(error => { metrics.current.errors.push(String(error)); void refresh(); });
|
|
313
|
+
return () => {
|
|
314
|
+
alive = false;
|
|
315
|
+
offConnect();
|
|
316
|
+
offDisconnect();
|
|
317
|
+
hub.socket?.disconnect?.();
|
|
318
|
+
};
|
|
319
|
+
// The hub is intentionally one logical transport for the life of this card.
|
|
320
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
321
|
+
}, [hub]);
|
|
322
|
+
const api = hub.facade.qaReplay?.func;
|
|
323
|
+
const run = (work) => {
|
|
324
|
+
if (!work)
|
|
325
|
+
return;
|
|
326
|
+
void work().then(() => refresh()).catch(error => { metrics.current.errors.push(String(error)); void refresh(); });
|
|
327
|
+
};
|
|
328
|
+
const green = snapshot.produced > 0 && snapshot.missing == 0 && snapshot.duplicates == 0 && snapshot.ordered && snapshot.seq >= snapshot.head && snapshot.listeners == 1 && snapshot.errors == 0;
|
|
329
|
+
const statusStyle = { padding: "3px 8px", borderRadius: 5, color: "#fff", background: green ? "#2da44e" : "#cf222e", fontWeight: 700, fontSize: 12 };
|
|
330
|
+
return _jsxs("div", { style: { display: "grid", gap: 8 }, children: [_jsx("div", { style: { fontSize: 13, color: "#57606a", maxWidth: 760 }, children: "Keep this tab visible: background-tab throttling delays browser work and is not transport lag. Start the producer, wait for delivery, disconnect while it keeps running, rerender, then reconnect the same socket. No restart, key change, remount, or remote replacement is used for recovery." }), _jsxs("div", { style: { display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }, children: [_jsx("button", { onClick: () => run(api ? () => api.start() : undefined), children: "start producer (20/s)" }), _jsx("button", { onClick: () => run(api ? () => api.stop() : undefined), children: "stop producer" }), _jsx("button", { onClick: () => { hub.socket?.disconnect?.(); void refresh(); }, children: "disconnect transport" }), _jsx("button", { onClick: () => { hub.socket?.connect?.(); }, children: "reconnect transport" }), _jsx("button", { onClick: () => { for (let i = 1; i <= 8; i++)
|
|
331
|
+
window.setTimeout(() => setCallbackRevision(value => value + 1), i * 20); }, children: "parent rerender burst" }), _jsx("button", { onClick: () => run(api ? () => api.burst(5_000) : undefined), children: "burst 5,000" }), _jsx("button", { onClick: () => void refresh(), children: "refresh metrics" }), _jsx("button", { onClick: () => { setMounted(value => !value); window.setTimeout(() => void refresh(), 0); }, children: mounted ? "unmount consumer" : "mount consumer" }), _jsx("span", { style: statusStyle, children: green ? "PASS: exact replay" : "waiting / mismatch" })] }), remote && mounted && _jsx(StrictMode, { children: _jsx(RpcReplayConsumer, { remote: remote, metrics: metrics, callbackRevision: callbackRevision }) }), _jsxs("div", { style: { ...statLine, whiteSpace: "normal" }, children: ["transport ", connected ? "connected" : "disconnected", " \u00B7 stable remote ", String(remote != null && remote === firstRemote.current), " \u00B7 producer ", snapshot.producing ? "running" : "stopped", _jsx("br", {}), "produced ", snapshot.produced, " \u00B7 received ", snapshot.received, " \u00B7 seq ", snapshot.seq, "/", snapshot.head, " \u00B7 missing ", snapshot.missing, " \u00B7 duplicates ", snapshot.duplicates, " \u00B7 ordered ", String(snapshot.ordered), _jsx("br", {}), "consumer renders ", snapshot.renders, " \u00B7 latest callback revision ", metrics.current.latestCallbackRevision, "/", callbackRevision, " \u00B7 server listeners ", snapshot.listeners, " \u00B7 ready ", String(snapshot.ready), " \u00B7 errors ", snapshot.errors] }), _jsx("div", { style: { fontSize: 12, color: "#57606a" }, children: "The 5,000-event burst is lossless (`policy: queue`): use refresh after it drains. The consumer records deliveries in a ref, so delivery itself does not trigger React renders; only lifecycle/UI controls do. Unmount then refresh to confirm server listeners reaches 0." })] });
|
|
332
|
+
};
|
|
236
333
|
function createStoreReplayDemo() {
|
|
237
334
|
let stalled = false;
|
|
238
335
|
const store = Observe.createStore({ ticks: 0, price: 100, note: "start", bag: { a: 1 } });
|
package/lib/style/menuRight.css
CHANGED
|
@@ -43,25 +43,25 @@
|
|
|
43
43
|
width: max-content;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
.menu-item {
|
|
47
|
-
min-height: 30px;
|
|
48
|
-
padding: 10px;
|
|
49
|
-
position: relative;
|
|
50
|
-
cursor: pointer;
|
|
51
|
-
color: var(--menu-item-color, #fff);
|
|
52
|
-
border-bottom: var(--menu-border, 1px solid rgba(255, 255, 255, 0.41));
|
|
53
|
-
transition: background 0.3s, color 0.3s;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.menu-item:hover,
|
|
57
|
-
.menu-item.force-hover {
|
|
58
|
-
background: var(--menu-item-hover-bg-color, #fff);
|
|
59
|
-
color: var(--menu-item-hover-color, #101010);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.menu-item:active {
|
|
63
|
-
background: var(--menu-item-pressed-bg-color, #f2c94c);
|
|
64
|
-
color: var(--menu-item-pressed-color, #101010);
|
|
46
|
+
.menu-item {
|
|
47
|
+
min-height: 30px;
|
|
48
|
+
padding: 10px;
|
|
49
|
+
position: relative;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
color: var(--menu-item-color, #fff);
|
|
52
|
+
border-bottom: var(--menu-border, 1px solid rgba(255, 255, 255, 0.41));
|
|
53
|
+
transition: background 0.3s, color 0.3s;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.menu-item:hover,
|
|
57
|
+
.menu-item.force-hover {
|
|
58
|
+
background: var(--menu-item-hover-bg-color, #fff);
|
|
59
|
+
color: var(--menu-item-hover-color, #101010);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.menu-item:active {
|
|
63
|
+
background: var(--menu-item-pressed-bg-color, #f2c94c);
|
|
64
|
+
color: var(--menu-item-pressed-color, #101010);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
.submenu {
|
package/lib/style/style.css
CHANGED
|
@@ -1058,29 +1058,29 @@ body {
|
|
|
1058
1058
|
color: var(--cols-card-accent-color, #0969da);
|
|
1059
1059
|
white-space: nowrap;
|
|
1060
1060
|
}
|
|
1061
|
-
.wenayCardListFields {
|
|
1062
|
-
display: block;
|
|
1063
|
-
}
|
|
1064
|
-
.wenayCardListItem_compact .wenayCardListFields {
|
|
1065
|
-
display: grid;
|
|
1066
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1067
|
-
gap: 4px 8px;
|
|
1068
|
-
}
|
|
1069
|
-
.wenayCardListItem_compact .wenayCardListField {
|
|
1070
|
-
min-width: 0;
|
|
1071
|
-
padding: 2px 6px;
|
|
1072
|
-
border-radius: 5px;
|
|
1073
|
-
background: var(--cols-card-field-bg, #f6f8fa);
|
|
1074
|
-
line-height: 1.45;
|
|
1075
|
-
}
|
|
1076
|
-
.wenayCardListItem_compact .wenayCardListLabel {
|
|
1077
|
-
min-width: 0;
|
|
1078
|
-
font-size: 10px;
|
|
1079
|
-
text-transform: uppercase;
|
|
1080
|
-
letter-spacing: .03em;
|
|
1081
|
-
}
|
|
1082
|
-
@media (max-width: 320px) {
|
|
1083
|
-
.wenayCardListItem_compact .wenayCardListFields { grid-template-columns: 1fr; }
|
|
1061
|
+
.wenayCardListFields {
|
|
1062
|
+
display: block;
|
|
1063
|
+
}
|
|
1064
|
+
.wenayCardListItem_compact .wenayCardListFields {
|
|
1065
|
+
display: grid;
|
|
1066
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1067
|
+
gap: 4px 8px;
|
|
1068
|
+
}
|
|
1069
|
+
.wenayCardListItem_compact .wenayCardListField {
|
|
1070
|
+
min-width: 0;
|
|
1071
|
+
padding: 2px 6px;
|
|
1072
|
+
border-radius: 5px;
|
|
1073
|
+
background: var(--cols-card-field-bg, #f6f8fa);
|
|
1074
|
+
line-height: 1.45;
|
|
1075
|
+
}
|
|
1076
|
+
.wenayCardListItem_compact .wenayCardListLabel {
|
|
1077
|
+
min-width: 0;
|
|
1078
|
+
font-size: 10px;
|
|
1079
|
+
text-transform: uppercase;
|
|
1080
|
+
letter-spacing: .03em;
|
|
1081
|
+
}
|
|
1082
|
+
@media (max-width: 320px) {
|
|
1083
|
+
.wenayCardListItem_compact .wenayCardListFields { grid-template-columns: 1fr; }
|
|
1084
1084
|
}
|
|
1085
1085
|
.wenayCardListField {
|
|
1086
1086
|
display: flex;
|