@ibgib/space-gib 0.0.3 → 0.0.5

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 (63) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/IMPLEMENTATION.md +9 -13
  3. package/README.md +7 -0
  4. package/dist/client/bootstrap.mjs +1 -1
  5. package/dist/client/bootstrap.mjs.map +1 -1
  6. package/dist/client/chunk-ANGVYAEK.mjs +42 -0
  7. package/dist/client/chunk-ANGVYAEK.mjs.map +7 -0
  8. package/dist/client/chunk-IRGFDQRD.mjs +1920 -0
  9. package/dist/client/chunk-IRGFDQRD.mjs.map +7 -0
  10. package/dist/client/index.html +103 -5
  11. package/dist/client/index.mjs +1 -1
  12. package/dist/client/script.mjs +1 -1
  13. package/dist/client/style.css +466 -61
  14. package/dist/respec-gib.node.mjs +5 -0
  15. package/dist/server/server.mjs +533 -233
  16. package/dist/server/server.mjs.map +2 -2
  17. package/package.json +6 -6
  18. package/src/client/AUTO-GENERATED-version.mts +1 -1
  19. package/src/client/components/identity-header/IMPLEMENTATION.md +45 -0
  20. package/src/client/components/identity-header/identity-header.css +74 -0
  21. package/src/client/components/identity-header/identity-header.html +10 -0
  22. package/src/client/components/identity-header/identity-header.mts +361 -0
  23. package/src/client/components/identity-manager/IMPLEMENTATION.md +100 -0
  24. package/src/client/components/identity-manager/identity-manager.css +467 -0
  25. package/src/client/components/identity-manager/identity-manager.html +113 -0
  26. package/src/client/components/identity-manager/identity-manager.mts +767 -0
  27. package/src/client/components/keystone-creator/keystone-creator.css +2 -76
  28. package/src/client/components/keystone-creator/keystone-creator.html +41 -26
  29. package/src/client/components/keystone-creator/keystone-creator.mts +178 -41
  30. package/src/client/dev-tools/base-tools.mts +252 -0
  31. package/src/client/dev-tools/common.mts +217 -0
  32. package/src/client/dev-tools/phase-1.mts +156 -0
  33. package/src/client/dev-tools/phase-2.mts +143 -0
  34. package/src/client/dev-tools/phase-3.mts +189 -0
  35. package/src/client/dev-tools/phase-4-1.mts +197 -0
  36. package/src/client/dev-tools/phase-4-10.mts +884 -0
  37. package/src/client/dev-tools/phase-4-2.mts +388 -0
  38. package/src/client/dev-tools/phase-4-3.mts +391 -0
  39. package/src/client/dev-tools/phase-4-4.mts +374 -0
  40. package/src/client/dev-tools/phase-4-5.mts +376 -0
  41. package/src/client/dev-tools/phase-4-6.mts +273 -0
  42. package/src/client/dev-tools/phase-4-7.mts +399 -0
  43. package/src/client/dev-tools/phase-4-8.mts +430 -0
  44. package/src/client/dev-tools/phase-4-9.mts +398 -0
  45. package/src/client/dev-tools/phase-4.mts +1302 -0
  46. package/src/client/dev-tools.mts +52 -1194
  47. package/src/client/index.html +103 -5
  48. package/src/client/style.css +466 -61
  49. package/src/client/ui/shell/space-gib-shell-constants.mts +0 -2
  50. package/src/client/ui/shell/space-gib-shell-service.mts +82 -10
  51. package/src/common/common-constants.mts +0 -0
  52. package/src/common/keystone-policies.json +40 -43
  53. package/src/common/keystone-policies.mts +3 -5
  54. package/src/server/path-helper.respec.mts +99 -94
  55. package/src/server/serve-gib/README.md +9 -0
  56. package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +1 -1
  57. package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +1 -1
  58. package/src/server/serve-gib/handlers/ws/sync-upgrade-handler-base.mts +31 -3
  59. package/src/server/serve-gib/handlers/ws/ws-helper.mts +73 -45
  60. package/dist/client/chunk-2KJC5XKE.mjs +0 -31
  61. package/dist/client/chunk-2KJC5XKE.mjs.map +0 -7
  62. package/dist/client/chunk-QNIXTRFO.mjs +0 -235
  63. package/dist/client/chunk-QNIXTRFO.mjs.map +0 -7
@@ -0,0 +1,398 @@
1
+ import { extractErrorMsg, getUUID, getTimestamp } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
2
+ import { Factory_V1 as factory } from '@ibgib/ts-gib/dist/V1/factory.mjs';
3
+ import { ROOT } from '@ibgib/ts-gib/dist/V1/constants.mjs';
4
+ import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
5
+ import { KeystoneIbGib_V1 } from '@ibgib/core-gib/dist/keystone/keystone-types.mjs';
6
+ import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
7
+ import { getGlobalMetaspace_waitIfNeeded } from "@ibgib/web-gib/dist/helpers.mjs";
8
+ import { mut8Timeline } from '@ibgib/core-gib/dist/timeline/timeline-api.mjs';
9
+ import { getTjpAddr } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
10
+ import { SyncPeerWebSocketSender_V1 } from '@ibgib/core-gib/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs';
11
+ import { SyncSagaCoordinator } from '@ibgib/core-gib/dist/sync/sync-saga-coordinator.mjs';
12
+ import { SyncConflictStrategy } from '@ibgib/core-gib/dist/sync/sync-constants.mjs';
13
+ import { GRAFT_BASE_REL8N_NAME, GRAFT_ORPHAN_REL8N_NAME, GRAFT_INFO_REL8N_NAME } from '@ibgib/core-gib/dist/sync/graft-info/graft-info-constants.mjs';
14
+ import { SESSION_KEYSTONE_POLICY, getSpaceGibPoolConfig } from "../../common/keystone-policies.mjs";
15
+ import { SpaceGibApiBridge } from '../api/space-gib-api-bridge.mjs';
16
+
17
+ import { debugState, devLog } from './common.mjs';
18
+
19
+ interface Phase49State {
20
+ domainI_latest?: KeystoneIbGib_V1;
21
+ testRoot?: any;
22
+ targetAlphaV1Source?: any;
23
+ }
24
+
25
+ const state: Phase49State = {};
26
+
27
+ const INITIAL_TEXT = "This is the initial quiz question.\nIt has multiple lines.\nStudents will answer this.";
28
+ const SOURCE_APPEND = "\nInstructor added hint.";
29
+ const DEST_PREPEND = "Student note: Confusing!\n";
30
+
31
+ export function init4_9bSetupButton(): void {
32
+ const btn = document.getElementById('btn-4-9b-setup') as HTMLButtonElement | null;
33
+ if (!btn) { return; }
34
+ btn.addEventListener('click', async () => {
35
+ try {
36
+ btn.disabled = true;
37
+ devLog('4.9B Setup: Setting up identities, seeding text history, and creating divergent branches...');
38
+
39
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
40
+ const space = await metaspace.getLocalUserSpace({}) as any;
41
+ if (!space) { throw new Error("No default space."); }
42
+
43
+ const keystoneService = new KeystoneService_V1();
44
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
45
+
46
+ // 1. Generate Domain User Identity
47
+ const domainI = await keystoneService.genesis({
48
+ masterSecret: 'test-sender-secret-phase4-9b',
49
+ configs: [
50
+ getSpaceGibPoolConfig('sync', 'senderidentitysyncsaltphase4-9b'),
51
+ getSpaceGibPoolConfig('manage', 'senderidentitymanagesaltphase4-9b'),
52
+ ],
53
+ metaspace,
54
+ space,
55
+ frameDetails: { client: 'space-gib-web-dev', timestamp: getTimestamp() }
56
+ });
57
+ state.domainI_latest = domainI;
58
+ let domainI_latest: any = domainI;
59
+ const domainAddr = getIbGibAddr({ ibGib: domainI });
60
+ devLog(`4.9B Setup: ✓ Created identity: ${domainAddr}`);
61
+
62
+ // Register on server
63
+ const apiBridge = new SpaceGibApiBridge();
64
+ const resGenesis = await apiBridge.postGenesisKeystone(domainI);
65
+ if (!resGenesis.success) {
66
+ throw new Error(`Server rejected genesis domain keystone: ${resGenesis.message}`);
67
+ }
68
+ devLog('4.9B Setup: ✓ Identity registered on server.');
69
+
70
+ // 2. Create the divergent "remote" space
71
+ devLog('4.9B Setup: Creating temporary remote space...');
72
+ const remoteSpace = await metaspace.createNewLocalSpace({
73
+ opts: {
74
+ allowCancel: false,
75
+ spaceName: 'remote_space_4_9b',
76
+ getFnPrompt: metaspace.getFnPrompt!
77
+ }
78
+ }) as any;
79
+ await remoteSpace.initialized;
80
+
81
+ // Copy user identity to remote space
82
+ await metaspace.put({ ibGib: domainI, space: remoteSpace });
83
+ await metaspace.registerNewIbGib({ ibGib: domainI, space: remoteSpace });
84
+
85
+ // 3. Seed common history root -> v1 (with initial text) locally
86
+ const resRoot = await factory.firstGen({
87
+ parentIbGib: ROOT,
88
+ ib: 'timeline_root_text_conflict_4_9b',
89
+ data: { text: INITIAL_TEXT, label: 'TextRoot', random: Math.random() },
90
+ dna: true,
91
+ nCounter: true,
92
+ tjp: { uuid: true, timestamp: true }
93
+ });
94
+ const testRoot = resRoot.newIbGib;
95
+ await metaspace.persistTransformResult({ resTransform: resRoot, space });
96
+ await metaspace.registerNewIbGib({ ibGib: testRoot, space });
97
+ state.testRoot = testRoot;
98
+
99
+ // 4. Copy common history to remote space
100
+ const graphRoot = await metaspace.getDependencyGraph({ ibGibAddr: getIbGibAddr({ ibGib: testRoot }), space });
101
+ await metaspace.put({ ibGibs: Object.values(graphRoot), space: remoteSpace });
102
+ await metaspace.registerNewIbGib({ ibGib: testRoot, space: remoteSpace });
103
+
104
+ // 5. Initial sync of testRoot to the server so server has it
105
+ devLog('4.9B Setup: Syncing testRoot to server...');
106
+ const senderPeerInit = new SyncPeerWebSocketSender_V1({
107
+ classname: 'SyncPeerWebSocketSender_V1',
108
+ httpEvolveUrl: `${location.protocol}//${location.host}/api/keystone/evolve/${encodeURIComponent(domainAddr)}`,
109
+ wsUrl: `${protocol}//${location.host}/api/sync/ws/${encodeURIComponent(domainAddr)}`
110
+ });
111
+ const coordinatorInit = new SyncSagaCoordinator();
112
+ await senderPeerInit.initializeOpts({
113
+ localMetaspace: metaspace,
114
+ localSpace: space,
115
+ senderIdentity: domainI,
116
+ fnSenderSecret: async () => 'test-sender-secret-phase4-9b',
117
+ sagaId: await getUUID(),
118
+ sessionConnectPoolConfig: SESSION_KEYSTONE_POLICY.CONNECT_POOL as any,
119
+ sessionSyncPoolConfig: SESSION_KEYSTONE_POLICY.DEFAULT_POOL as any,
120
+ targetAddrs: [domainAddr]
121
+ });
122
+ const syncSagaInit = await coordinatorInit.sync({
123
+ domainIbGibs: [testRoot],
124
+ senderIdentity: domainI,
125
+ fnSenderSecret: async () => 'test-sender-secret-phase4-9b',
126
+ peer: senderPeerInit,
127
+ localSpace: space,
128
+ metaspace,
129
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
130
+ });
131
+ await syncSagaInit.done;
132
+ devLog('4.9B Setup: ✓ Seeding complete on server.');
133
+
134
+ // Fetch evolved user identity from space (now at n=2)
135
+ const domainLatestAddr = await metaspace.getLatestAddr({ ibGib: domainI, space });
136
+ if (!domainLatestAddr) { throw new Error("Could not find latest evolved identity address in local space."); }
137
+ const resGetLatest = await metaspace.get({ addr: domainLatestAddr, space });
138
+ if (!resGetLatest.success || !resGetLatest.ibGibs || resGetLatest.ibGibs.length === 0) {
139
+ throw new Error("Could not find latest evolved identity in local space.");
140
+ }
141
+ domainI_latest = resGetLatest.ibGibs[0] as KeystoneIbGib_V1;
142
+ state.domainI_latest = domainI_latest;
143
+ devLog(`4.9B Setup: ✓ Fetched evolved identity: ${domainLatestAddr}`);
144
+
145
+ // Copy evolved user identity to remote space
146
+ await metaspace.put({ ibGib: domainI_latest, space: remoteSpace });
147
+ await metaspace.registerNewIbGib({ ibGib: domainI_latest, space: remoteSpace });
148
+
149
+ // 6. Create Divergence:
150
+ // Client (default space) appends to end
151
+ const alpha_v1_source = await mut8Timeline({
152
+ timeline: testRoot,
153
+ mut8Opts: {
154
+ mut8Ib: testRoot.ib + '_appended',
155
+ dataToAddOrPatch: { text: INITIAL_TEXT + SOURCE_APPEND }
156
+ },
157
+ metaspace,
158
+ space,
159
+ });
160
+ state.targetAlphaV1Source = alpha_v1_source;
161
+ devLog(`4.9B Setup: ✓ Created client appended edit: ${getIbGibAddr({ ibGib: alpha_v1_source })}`);
162
+
163
+ // Remote Space prepends to beginning
164
+ const alpha_v1_remote = await mut8Timeline({
165
+ timeline: testRoot,
166
+ mut8Opts: {
167
+ mut8Ib: testRoot.ib + '_prepended',
168
+ dataToAddOrPatch: { text: DEST_PREPEND + INITIAL_TEXT }
169
+ },
170
+ metaspace,
171
+ space: remoteSpace,
172
+ });
173
+ devLog(`4.9B Setup: ✓ Created remote prepended edit: ${getIbGibAddr({ ibGib: alpha_v1_remote })}`);
174
+
175
+ // 7. Sync remote prepended edit to server, so server tip becomes the prepended edit
176
+ devLog('4.9B Setup: Pushing remote prepended edit to server...');
177
+ const senderPeerRemote = new SyncPeerWebSocketSender_V1({
178
+ classname: 'SyncPeerWebSocketSender_V1',
179
+ httpEvolveUrl: `${location.protocol}//${location.host}/api/keystone/evolve/${encodeURIComponent(domainAddr)}`,
180
+ wsUrl: `${protocol}//${location.host}/api/sync/ws/${encodeURIComponent(domainAddr)}`
181
+ });
182
+ const coordinatorRemote = new SyncSagaCoordinator();
183
+ await senderPeerRemote.initializeOpts({
184
+ localMetaspace: metaspace,
185
+ localSpace: remoteSpace,
186
+ senderIdentity: domainI_latest,
187
+ fnSenderSecret: async () => 'test-sender-secret-phase4-9b',
188
+ sagaId: await getUUID(),
189
+ sessionConnectPoolConfig: SESSION_KEYSTONE_POLICY.CONNECT_POOL as any,
190
+ sessionSyncPoolConfig: SESSION_KEYSTONE_POLICY.DEFAULT_POOL as any,
191
+ targetAddrs: [domainAddr]
192
+ });
193
+ const syncSagaRemote = await coordinatorRemote.sync({
194
+ domainIbGibs: [alpha_v1_remote],
195
+ senderIdentity: domainI_latest,
196
+ fnSenderSecret: async () => 'test-sender-secret-phase4-9b',
197
+ peer: senderPeerRemote,
198
+ localSpace: remoteSpace,
199
+ metaspace,
200
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
201
+ });
202
+ await syncSagaRemote.done;
203
+ devLog('4.9B Setup: ✓ Server tip is now the prepended edit.');
204
+
205
+ // Fetch evolved user identity from remote space (now at n=3)
206
+ const domainLatestAddrRemote = await metaspace.getLatestAddr({ ibGib: domainI, space: remoteSpace });
207
+ if (!domainLatestAddrRemote) { throw new Error("Could not find latest evolved identity address in remote space."); }
208
+ const resGetLatestRemote = await metaspace.get({ addr: domainLatestAddrRemote, space: remoteSpace });
209
+ if (!resGetLatestRemote.success || !resGetLatestRemote.ibGibs || resGetLatestRemote.ibGibs.length === 0) {
210
+ throw new Error("Could not find latest evolved identity in remote space.");
211
+ }
212
+ const domainI_latestRemote = resGetLatestRemote.ibGibs[0] as KeystoneIbGib_V1;
213
+ await metaspace.put({ ibGib: domainI_latestRemote, space });
214
+ await metaspace.registerNewIbGib({ ibGib: domainI_latestRemote, space });
215
+ state.domainI_latest = domainI_latestRemote;
216
+ devLog(`4.9B Setup: ✓ Copied evolved identity tip (n=3) back to default space: ${domainLatestAddrRemote}`);
217
+
218
+ devLog('✓ 4.9B Setup Complete! Ready for 4.9B Sync.');
219
+ btn.textContent = '✓ 4.9B Setup Complete';
220
+
221
+ const syncBtn = document.getElementById('btn-4-9b-sync') as HTMLButtonElement | null;
222
+ if (syncBtn) { syncBtn.disabled = false; }
223
+
224
+ } catch (error) {
225
+ devLog(`✗ 4.9B Setup FAILED: ${extractErrorMsg(error)}`);
226
+ console.error(error);
227
+ btn.disabled = false;
228
+ }
229
+ });
230
+ }
231
+
232
+ export function init4_9bSyncButton(): void {
233
+ const btn = document.getElementById('btn-4-9b-sync') as HTMLButtonElement | null;
234
+ if (!btn) { return; }
235
+ btn.addEventListener('click', async () => {
236
+ try {
237
+ btn.disabled = true;
238
+ devLog('4.9B Sync: Initiating WebSocket Sync for divergent edit (LCS text merge)...');
239
+
240
+ const domainI = state.domainI_latest;
241
+ const targetAlphaV1Source = state.targetAlphaV1Source;
242
+ if (!domainI || !targetAlphaV1Source) {
243
+ devLog('⚠ 4.9B Sync: Missing setup state. Please run Setup first.');
244
+ btn.disabled = false;
245
+ return;
246
+ }
247
+
248
+ const domainAddr = getIbGibAddr({ ibGib: domainI });
249
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
250
+ const space = await metaspace.getLocalUserSpace({}) as any;
251
+ if (!space) { throw new Error("No default space."); }
252
+
253
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
254
+
255
+ const senderPeer = new SyncPeerWebSocketSender_V1({
256
+ classname: 'SyncPeerWebSocketSender_V1',
257
+ httpEvolveUrl: `${location.protocol}//${location.host}/api/keystone/evolve/${encodeURIComponent(domainAddr)}`,
258
+ wsUrl: `${protocol}//${location.host}/api/sync/ws/${encodeURIComponent(domainAddr)}`
259
+ });
260
+
261
+ const coordinator = new SyncSagaCoordinator();
262
+ const sagaId = await getUUID();
263
+
264
+ await senderPeer.initializeOpts({
265
+ localMetaspace: metaspace,
266
+ localSpace: space,
267
+ senderIdentity: domainI,
268
+ fnSenderSecret: async () => 'test-sender-secret-phase4-9b',
269
+ sagaId,
270
+ sessionConnectPoolConfig: SESSION_KEYSTONE_POLICY.CONNECT_POOL as any,
271
+ sessionSyncPoolConfig: SESSION_KEYSTONE_POLICY.DEFAULT_POOL as any,
272
+ targetAddrs: [domainAddr]
273
+ });
274
+
275
+ // Run Sync. Client has appended text edit, server has prepended text edit.
276
+ // This will trigger optimistic graft merge using LCS algorithm on the text field!
277
+ const syncSaga = await coordinator.sync({
278
+ domainIbGibs: [targetAlphaV1Source],
279
+ senderIdentity: domainI,
280
+ fnSenderSecret: async () => 'test-sender-secret-phase4-9b',
281
+ peer: senderPeer,
282
+ localSpace: space,
283
+ metaspace,
284
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
285
+ });
286
+ await syncSaga.done;
287
+
288
+ devLog('✓ 4.9B Sync Execution Complete! Ready for 4.9B Check.');
289
+ btn.textContent = '✓ 4.9B Sync Run';
290
+
291
+ const checkBtn = document.getElementById('btn-4-9b-check') as HTMLButtonElement | null;
292
+ if (checkBtn) { checkBtn.disabled = false; }
293
+
294
+ } catch (error) {
295
+ devLog(`✗ 4.9B Sync FAILED: ${extractErrorMsg(error)}`);
296
+ console.error(error);
297
+ btn.disabled = false;
298
+ }
299
+ });
300
+ }
301
+
302
+ export function init4_9bCheckButton(): void {
303
+ const btn = document.getElementById('btn-4-9b-check') as HTMLButtonElement | null;
304
+ if (!btn) { return; }
305
+ btn.addEventListener('click', async () => {
306
+ try {
307
+ btn.disabled = true;
308
+ devLog('4.9B Check: Verifying LCS merged text on client and server...');
309
+
310
+ const domainI = state.domainI_latest;
311
+ const testRoot = state.testRoot;
312
+ const targetAlphaV1Source = state.targetAlphaV1Source;
313
+ if (!domainI || !testRoot || !targetAlphaV1Source) {
314
+ devLog('⚠ 4.9B Check: Missing state. Did Setup and Sync run?');
315
+ btn.disabled = false;
316
+ return;
317
+ }
318
+
319
+ const domainAddr = getIbGibAddr({ ibGib: domainI });
320
+ const tjpAddr = getTjpAddr({ ibGib: testRoot, defaultIfNone: 'incomingAddr' }) ?? getIbGibAddr({ ibGib: testRoot });
321
+
322
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
323
+ const space = await metaspace.getLocalUserSpace({}) as any;
324
+ if (!space) { throw new Error("No default space."); }
325
+
326
+ // 1. Check client default space tip
327
+ const clientKV = await metaspace.getLocalUserSpace({}).then((space: any) => {
328
+ return new SyncSagaCoordinator().getKnowledgeMap({
329
+ space,
330
+ metaspace,
331
+ domainIbGibs: [testRoot]
332
+ });
333
+ });
334
+ const clientTipAddr = clientKV[tjpAddr];
335
+ devLog(`4.9B Check: Client tip address: ${clientTipAddr}`);
336
+
337
+ if (!clientTipAddr) {
338
+ throw new Error("Client default space is missing the timeline tip.");
339
+ }
340
+
341
+ const resTip = await metaspace.get({ addr: clientTipAddr, space });
342
+ if (!resTip.success || !resTip.ibGibs || resTip.ibGibs.length === 0) {
343
+ throw new Error("Client tip not found in default local space.");
344
+ }
345
+ const clientTip = resTip.ibGibs[0];
346
+
347
+ const expectedText = DEST_PREPEND + INITIAL_TEXT + SOURCE_APPEND;
348
+ if (clientTip.data?.text !== expectedText) {
349
+ throw new Error(`Merged client tip text mismatch! Expected:\n${expectedText}\nActual:\n${clientTip.data?.text}`);
350
+ }
351
+ devLog('4.9B Check: ✓ Client tip text contains both prepend and append edits merged correctly.');
352
+
353
+ // Verify graftinfo rel8n exists
354
+ const graftInfoRel = clientTip.rel8ns?.[GRAFT_INFO_REL8N_NAME];
355
+ if (!graftInfoRel?.[0]) {
356
+ throw new Error("Client tip is missing graftinfo relations.");
357
+ }
358
+ const resGraft = await metaspace.get({ addr: graftInfoRel[0], space });
359
+ if (!resGraft.success || !resGraft.ibGibs || resGraft.ibGibs.length === 0) {
360
+ throw new Error("GraftInfo not found in local space.");
361
+ }
362
+ const graftInfo = resGraft.ibGibs[0];
363
+ const baseRel = graftInfo.rel8ns?.[GRAFT_BASE_REL8N_NAME];
364
+ const orphanRel = graftInfo.rel8ns?.[GRAFT_ORPHAN_REL8N_NAME];
365
+
366
+ if (!baseRel || !orphanRel) {
367
+ throw new Error("GraftInfo is missing graftbase or graftorphan relations.");
368
+ }
369
+ devLog('4.9B Check: ✓ Client graft base and orphan relations are correctly set.');
370
+
371
+ // 2. Check server tip
372
+ const apiBridge = new SpaceGibApiBridge();
373
+ devLog('4.9B Check: Fetching server timeline graph...');
374
+ const resServerGraph = await apiBridge.getIbGibGraph(domainAddr, clientTipAddr, true);
375
+ if (!resServerGraph.success || !resServerGraph.graph) {
376
+ throw new Error(`Failed to fetch merged graph from server: ${resServerGraph.message}`);
377
+ }
378
+
379
+ const serverTip = resServerGraph.graph[clientTipAddr];
380
+ if (!serverTip) {
381
+ throw new Error("Merged tip is not present on the server!");
382
+ }
383
+
384
+ if (serverTip.data?.text !== expectedText) {
385
+ throw new Error(`Server tip text mismatch! Expected:\n${expectedText}\nActual:\n${serverTip.data?.text}`);
386
+ }
387
+ devLog('4.9B Check: ✓ Server tip text matches the client-side merged text.');
388
+
389
+ devLog('✓ 4.9B Check SUCCESS: LCS text field merge conflict resolved and verified on both client and server!');
390
+ btn.textContent = '✓ 4.9B Check Success';
391
+
392
+ } catch (error) {
393
+ devLog(`✗ 4.9B Check FAILED: ${extractErrorMsg(error)}`);
394
+ console.error(error);
395
+ btn.disabled = false;
396
+ }
397
+ });
398
+ }