From ecb1d00f91e8e3e8b1a8b7d29875984825535f98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 03:46:09 +0000 Subject: [PATCH 1/3] Initial plan From 45f0f7e62cebe9e91e82db9633a875052a67d05e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 04:11:53 +0000 Subject: [PATCH 2/3] fix: preserve seed data _id as stable record id across page refreshes Fixes: record not found when refreshing detail page Root cause: syncDriverIds() unconditionally set _id = id, overwriting seed data's stable _id values with driver-generated timestamp-based ids. On page refresh, the in-memory kernel reboots with new generated ids, making old URL record ids invalid. Changes: - syncDriverIds now promotes seed _id to canonical id when present - findOne $expand path handles array results defensively - Added tests verifying stable seed ids and HTTP fetch by _id Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- apps/console/src/__tests__/MSWServer.test.tsx | 29 +++++++++++++++++++ apps/console/src/mocks/createKernel.ts | 15 ++++++++-- packages/data-objectstack/src/index.ts | 4 +++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/apps/console/src/__tests__/MSWServer.test.tsx b/apps/console/src/__tests__/MSWServer.test.tsx index 607e53b9e..d77b395ef 100644 --- a/apps/console/src/__tests__/MSWServer.test.tsx +++ b/apps/console/src/__tests__/MSWServer.test.tsx @@ -95,4 +95,33 @@ describe('MSW Server Integration', () => { expect(record.priority).toBe('high'); } }); + + // ── Stable seed-data IDs ────────────────────────────────────────────── + // Seed records carry an explicit `_id`. After kernel bootstrap and + // syncDriverIds(), `id` should equal the seed `_id`, NOT a random + // driver-generated value. This ensures URLs with record IDs remain + // valid across page refreshes. + + it('should preserve seed _id as canonical id (stable across refreshes)', async () => { + const driver = getDriver(); + const opportunities = await driver!.find('opportunity', { object: 'opportunity' }); + expect(opportunities.length).toBeGreaterThan(0); + + // Seed data defines _id "101" for the first opportunity. + // After syncDriverIds, id must equal _id (both "101"). + const first = opportunities.find((r: any) => r._id === '101'); + expect(first).toBeDefined(); + expect(first.id).toBe('101'); + expect(first._id).toBe('101'); + }); + + it('should fetch a seed record by _id via HTTP', async () => { + // GET /data/opportunity/101 — uses the stable seed _id + const res = await fetch('http://localhost/api/v1/data/opportunity/101'); + expect(res.ok).toBe(true); + const body = await res.json(); + const record = body.data?.record ?? body.record; + expect(record).toBeDefined(); + expect(record.name).toBe('ObjectStack Enterprise License'); + }); }); diff --git a/apps/console/src/mocks/createKernel.ts b/apps/console/src/mocks/createKernel.ts index 1fa636e51..fbb03c7cb 100644 --- a/apps/console/src/mocks/createKernel.ts +++ b/apps/console/src/mocks/createKernel.ts @@ -102,14 +102,23 @@ async function installBrokerShim(kernel: ObjectKernel): Promise { * generated identity as `id`. Seed data may also carry its own * `_id` that differs from the driver-assigned `id`. * - * This helper ensures every record has `_id === id` so that - * protocol lookups via `_id` match the driver-assigned `id`. + * When seed data provides an explicit `_id`, that value is promoted + * to `id` so that record identifiers remain stable across page + * refreshes (the driver would otherwise generate a new timestamp-based + * `id` every time the in-memory kernel reboots). + * + * When no explicit `_id` exists, `_id` is derived from the + * driver-assigned `id` so protocol lookups still work. */ function syncDriverIds(driver: InMemoryDriver): void { const db = (driver as any).db as Record; for (const records of Object.values(db)) { for (const record of records) { - if (record.id) { + if (record._id != null && record._id !== record.id) { + // Seed data carries an explicit _id → promote it to canonical id + record.id = record._id; + } else if (record.id) { + // No explicit seed _id → derive _id from driver-assigned id record._id = record.id; } } diff --git a/packages/data-objectstack/src/index.ts b/packages/data-objectstack/src/index.ts index e1b2831cc..871a8fd1b 100644 --- a/packages/data-objectstack/src/index.ts +++ b/packages/data-objectstack/src/index.ts @@ -289,6 +289,10 @@ export class ObjectStackAdapter implements DataSource { $top: 1, }; const result = await this.rawFindWithPopulate(resource, findParams); + // Handle array responses (some servers return data as flat arrays) + if (Array.isArray(result)) { + return result[0] || null; + } const resultObj = result as { records?: T[]; value?: T[] }; const records = resultObj.records || resultObj.value || []; return records[0] || null; From 76f1969d4ad4e91bf82601ab0d2f7191cb5c49b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 04:13:00 +0000 Subject: [PATCH 3/3] refactor: address code review feedback - improve test clarity Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- apps/console/src/__tests__/MSWServer.test.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/console/src/__tests__/MSWServer.test.tsx b/apps/console/src/__tests__/MSWServer.test.tsx index d77b395ef..09d01d2dc 100644 --- a/apps/console/src/__tests__/MSWServer.test.tsx +++ b/apps/console/src/__tests__/MSWServer.test.tsx @@ -109,14 +109,16 @@ describe('MSW Server Integration', () => { // Seed data defines _id "101" for the first opportunity. // After syncDriverIds, id must equal _id (both "101"). - const first = opportunities.find((r: any) => r._id === '101'); - expect(first).toBeDefined(); - expect(first.id).toBe('101'); - expect(first._id).toBe('101'); + const targetOpportunity = opportunities.find((r: any) => r._id === '101'); + expect(targetOpportunity).toBeDefined(); + expect(targetOpportunity.id).toBe('101'); + expect(targetOpportunity._id).toBe('101'); }); it('should fetch a seed record by _id via HTTP', async () => { - // GET /data/opportunity/101 — uses the stable seed _id + // GET /data/opportunity/101 — uses the stable seed _id. + // Response may be wrapped in { success, data: { record } } (HttpDispatcher) + // or returned as { record } (direct protocol). const res = await fetch('http://localhost/api/v1/data/opportunity/101'); expect(res.ok).toBe(true); const body = await res.json();