mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-06 04:36:21 +02:00
feat(docs): add window.__docDebug() console diagnostic
Exposes a lightweight function on window that returns CRDT heads, HTML, doc hash, DOM HTML, timeline delta count/senders, and snapshot info. Call it on both clients and compare to pinpoint divergence. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
1e225789a0
commit
8c45fe3736
@ -443,6 +443,70 @@ export const DocumentView = memo(function DocumentView({ room }: DocumentViewPro
|
||||
ref.current?.focus();
|
||||
}, [ref]);
|
||||
|
||||
// Expose a lightweight diagnostic on `window.__docDebug()` so we can
|
||||
// compare CRDT state across clients from the browser console without
|
||||
// flooding the log. Returns a plain object — safe to JSON.stringify.
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).__docDebug = () => {
|
||||
const model = composerModel;
|
||||
const collab = isCollaborative(model);
|
||||
const timeline = room.getLiveTimeline().getEvents();
|
||||
const deltas = timeline.filter((e) => e.getType() === DOC_DELTA_EVENT_TYPE);
|
||||
const stateEvt = room.currentState.getStateEvents(DOC_STATE_EVENT_TYPE, "");
|
||||
|
||||
// Simple hash of a base64 string for quick comparison.
|
||||
const simpleHash = (s: string): string => {
|
||||
let h = 0;
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
h = ((h << 5) - h + s.charCodeAt(i)) | 0;
|
||||
}
|
||||
return (h >>> 0).toString(16).padStart(8, "0");
|
||||
};
|
||||
|
||||
const docBytes = collab ? base64Encode(model.save_document()) : null;
|
||||
const info = {
|
||||
userId: client.getUserId(),
|
||||
deviceId: client.getDeviceId(),
|
||||
roomId: room.roomId,
|
||||
modelReady: collab,
|
||||
heads: collab ? model.get_heads() : null,
|
||||
html: collab ? model.get_content_as_html() : null,
|
||||
docHash: docBytes ? simpleHash(docBytes) : null,
|
||||
docBytesLen: docBytes ? docBytes.length : null,
|
||||
domHTML: ref.current?.innerHTML ?? null,
|
||||
timelineDeltaCount: deltas.length,
|
||||
timelineDeltaSenders: deltas.map((e) => `${e.getSender()} @${e.getTs()}`),
|
||||
snapshotTs: stateEvt?.getTs() ?? null,
|
||||
snapshotHash: stateEvt?.getContent<{ data?: string }>().data
|
||||
? simpleHash(stateEvt!.getContent<{ data: string }>().data)
|
||||
: null,
|
||||
};
|
||||
const json = JSON.stringify(info, null, 2);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[DocDebug]", json);
|
||||
// Fallback copy: execCommand works from console unlike clipboard API.
|
||||
try {
|
||||
const ta = document.createElement("textarea");
|
||||
ta.value = json;
|
||||
ta.style.position = "fixed";
|
||||
ta.style.opacity = "0";
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(ta);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[DocDebug] Copied to clipboard ✓");
|
||||
} catch { /* ignore */ }
|
||||
return info;
|
||||
};
|
||||
|
||||
return () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
delete (window as any).__docDebug;
|
||||
};
|
||||
}, [composerModel, room, client, ref]);
|
||||
|
||||
// Always render the Editor so that `ref.current` is attached before
|
||||
// useComposerModel's effect runs and calls initModel(). The loading
|
||||
// overlay only hides the toolbar while the Automerge document is loading.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user