Skip to content

Commit 3e4a0d8

Browse files
authored
Merge pull request #935 from objectstack-ai/copilot/fix-detail-page-error
2 parents 710447d + 7819a11 commit 3e4a0d8

File tree

3 files changed

+39
-20
lines changed

3 files changed

+39
-20
lines changed

apps/console/src/__tests__/RecordDetailEdit.test.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/**
2-
* RecordDetailView — Edit recordId stripping tests
2+
* RecordDetailView — recordId handling tests
33
*
4-
* Validates that when editing from the detail page, the objectName prefix
5-
* is stripped from the URL-based recordId before passing to the onEdit callback.
4+
* Validates that the URL-based recordId is passed through as-is to the
5+
* findOne API and onEdit callback. The navigation code puts the actual
6+
* record._id into the URL, so no prefix stripping is needed.
67
*
78
* Related: objectstack-ai/objectui — "Record not found" bug
89
*/
@@ -29,7 +30,7 @@ function createMockDataSource(): DataSource {
2930
},
3031
};
3132
},
32-
findOne: vi.fn().mockResolvedValue({ id: '1772350253615-4', name: 'Alice' }),
33+
findOne: vi.fn().mockResolvedValue({ id: 'contact-1772350253615-4', name: 'Alice' }),
3334
find: vi.fn().mockResolvedValue({ data: [] }),
3435
create: vi.fn().mockResolvedValue({ id: '1' }),
3536
update: vi.fn().mockResolvedValue({ id: '1' }),
@@ -75,8 +76,8 @@ function renderDetailView(
7576

7677
// ─── Tests ───────────────────────────────────────────────────────────────────
7778

78-
describe('RecordDetailView — onEdit recordId stripping', () => {
79-
it('strips objectName prefix from recordId when editing', async () => {
79+
describe('RecordDetailView — recordId handling', () => {
80+
it('passes URL recordId as-is to findOne (with objectName prefix)', async () => {
8081
const onEdit = vi.fn();
8182
const ds = createMockDataSource();
8283

@@ -87,18 +88,18 @@ describe('RecordDetailView — onEdit recordId stripping', () => {
8788
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Alice');
8889
});
8990

90-
// findOne should be called with the stripped ID (no objectName prefix)
91-
expect(ds.findOne).toHaveBeenCalledWith('contact', '1772350253615-4');
91+
// findOne should be called with the FULL URL recordId (no stripping)
92+
expect(ds.findOne).toHaveBeenCalledWith('contact', 'contact-1772350253615-4');
9293

9394
// Click the Edit button
9495
const editButton = await screen.findByRole('button', { name: /edit/i });
9596
await userEvent.click(editButton);
9697

97-
// The onEdit callback should receive the STRIPPED recordId (no objectName prefix)
98+
// The onEdit callback should receive the FULL recordId
9899
expect(onEdit).toHaveBeenCalledWith(
99100
expect.objectContaining({
100-
id: '1772350253615-4',
101-
_id: '1772350253615-4',
101+
id: 'contact-1772350253615-4',
102+
_id: 'contact-1772350253615-4',
102103
}),
103104
);
104105
});
@@ -113,13 +114,13 @@ describe('RecordDetailView — onEdit recordId stripping', () => {
113114
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Alice');
114115
});
115116

116-
// findOne should be called with the original ID (no prefix to strip)
117+
// findOne should be called with the original ID unchanged
117118
expect(ds.findOne).toHaveBeenCalledWith('contact', 'plain-id-12345');
118119

119120
const editButton = await screen.findByRole('button', { name: /edit/i });
120121
await userEvent.click(editButton);
121122

122-
// No prefix to strip — should pass the original recordId unchanged
123+
// Should pass the original recordId unchanged
123124
expect(onEdit).toHaveBeenCalledWith(
124125
expect.objectContaining({
125126
id: 'plain-id-12345',

apps/console/src/components/RecordDetailView.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
3434
const [recordViewers, setRecordViewers] = useState<PresenceUser[]>([]);
3535
const objectDef = objects.find((o: any) => o.name === objectName);
3636

37-
// Strip objectName prefix from URL-based recordId (e.g. "contact-123" → "123")
38-
const pureRecordId = recordId && objectName && recordId.startsWith(`${objectName}-`)
39-
? recordId.slice(objectName.length + 1)
40-
: recordId;
37+
// Use the URL recordId as-is — it contains the actual record _id.
38+
// Navigation code passes `record._id || record.id` directly into the URL
39+
// without adding any prefix, so no stripping is needed.
40+
const pureRecordId = recordId;
4141

4242
const currentUser = user
4343
? { id: user.id, name: user.name, avatar: user.image }

packages/plugin-detail/src/DetailView.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,27 @@ export const DetailView: React.FC<DetailViewProps> = ({
8585

8686
if (dataSource && schema.objectName && schema.resourceId) {
8787
setLoading(true);
88-
dataSource.findOne(schema.objectName, schema.resourceId).then((result) => {
89-
setData(result);
90-
setLoading(false);
88+
const objectName = schema.objectName;
89+
const resourceId = schema.resourceId;
90+
const prefix = `${objectName}-`;
91+
92+
dataSource.findOne(objectName, resourceId).then((result) => {
93+
if (result) {
94+
setData(result);
95+
setLoading(false);
96+
return;
97+
}
98+
// Fallback: try alternate ID format for backward compatibility
99+
const altId = resourceId.startsWith(prefix)
100+
? resourceId.slice(prefix.length) // strip prefix
101+
: `${prefix}${resourceId}`; // prepend prefix
102+
return dataSource.findOne(objectName, altId).then((fallbackResult) => {
103+
setData(fallbackResult);
104+
setLoading(false);
105+
}).catch(() => {
106+
setData(null);
107+
setLoading(false);
108+
});
91109
}).catch((err) => {
92110
console.error('Failed to fetch detail data:', err);
93111
setLoading(false);

0 commit comments

Comments
 (0)