fix: stop stripping objectName prefix from record detail URL IDs#935
fix: stop stripping objectName prefix from record detail URL IDs#935
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ilView The navigation code passes record._id directly into the URL without adding any prefix. Stripping the prefix incorrectly truncated backend-generated IDs (e.g. "opportunity-1772370455623-1" → "1772370455623-1"), causing findOne to fail with "Record not found". Also adds robust fallback in DetailView: if findOne returns null, retry with the prefix stripped or prepended for maximum backward compatibility. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ling Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes record detail navigation failures caused by stripping the objectName- prefix from URL record IDs, while keeping compatibility with mixed ID conventions across deployments.
Changes:
- Stop stripping the
objectName-prefix fromrecordIdin the console’sRecordDetailView. - Add a fallback retry in
DetailViewto resolve records using either prefixed or unprefixed ID formats. - Update console tests to assert
findOnereceives the full URLrecordId.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| packages/plugin-detail/src/DetailView.tsx | Adds findOne fallback retry with alternate ID format when the initial lookup returns null. |
| apps/console/src/components/RecordDetailView.tsx | Stops stripping objectName- from the URL recordId before passing it downstream. |
| apps/console/src/tests/RecordDetailEdit.test.tsx | Updates expectations to match the new “pass-through” recordId behavior. |
Comments suppressed due to low confidence (1)
packages/plugin-detail/src/DetailView.tsx:112
- If the initial
findOnecall rejects, the catch handler only setsloadingto false and leavesdataunchanged. If the component previously showed another record, this can incorrectly keep rendering the old record after a navigation/fetch failure. Consider clearingdata(e.g., set it to null) in this error path so the UI can transition to the not-found/error state consistently.
}).catch((err) => {
console.error('Failed to fetch detail data:', err);
setLoading(false);
});
| // Fallback: try alternate ID format for backward compatibility | ||
| const altId = resourceId.startsWith(prefix) | ||
| ? resourceId.slice(prefix.length) // strip prefix | ||
| : `${prefix}${resourceId}`; // prepend prefix | ||
| return dataSource.findOne(objectName, altId).then((fallbackResult) => { | ||
| setData(fallbackResult); | ||
| setLoading(false); |
There was a problem hiding this comment.
The new alternate-ID retry logic isn’t covered by the existing DetailView tests (there are tests for the null/not-found state, but none assert that a second findOne call is attempted with the computed alternate ID). Adding a focused test for the retry behavior would help prevent regressions across the supported ID conventions.
| // Use the URL recordId as-is — it contains the actual record _id. | ||
| // Navigation code passes `record._id || record.id` directly into the URL | ||
| // without adding any prefix, so no stripping is needed. | ||
| const pureRecordId = recordId; | ||
|
|
There was a problem hiding this comment.
pureRecordId is no longer “pure”/stripped (it’s now the raw recordId). Renaming this to something like resourceId/urlRecordId would reduce confusion for future readers since it’s used for presence/comments thread IDs, DetailView.resourceId, and the onEdit payload.
| // Use the URL recordId as-is — it contains the actual record _id. | ||
| // Navigation code passes `record._id || record.id` directly into the URL | ||
| // without adding any prefix, so no stripping is needed. | ||
| const pureRecordId = recordId; | ||
|
|
There was a problem hiding this comment.
With pureRecordId = recordId and the new DetailView fallback that may resolve a record using an alternate ID format, downstream uses of pureRecordId (presence recordId filter, comment threadId, and the onEdit payload) can become inconsistent with the ID that actually exists in the backend (e.g., when the URL uses a prefixed ID but the backend record ID is unprefixed, or vice versa). Consider deriving these downstream identifiers from the resolved record (or querying both threadId/recordId variants for backward compatibility) so edit/comments/presence stay aligned with whichever ID format was found.
| if (dataSource && schema.objectName && schema.resourceId) { | ||
| setLoading(true); | ||
| dataSource.findOne(schema.objectName, schema.resourceId).then((result) => { | ||
| setData(result); | ||
| setLoading(false); | ||
| const objectName = schema.objectName; | ||
| const resourceId = schema.resourceId; | ||
| const prefix = `${objectName}-`; |
There was a problem hiding this comment.
This effect now reads schema.objectName, schema.resourceId, and dataSource (captured into locals here), but the effect’s dependency array doesn’t include schema.objectName or dataSource. If those values change while schema.resourceId stays the same, the fetch won’t rerun and the view can show stale data. Consider updating the effect dependencies (or restructuring the fetch) so it re-triggers whenever the inputs used for findOne change.
| const resourceId = schema.resourceId; | ||
| const prefix = `${objectName}-`; | ||
|
|
||
| dataSource.findOne(objectName, resourceId).then((result) => { | ||
| if (result) { | ||
| setData(result); | ||
| setLoading(false); | ||
| return; | ||
| } | ||
| // Fallback: try alternate ID format for backward compatibility | ||
| const altId = resourceId.startsWith(prefix) | ||
| ? resourceId.slice(prefix.length) // strip prefix | ||
| : `${prefix}${resourceId}`; // prepend prefix |
There was a problem hiding this comment.
schema.resourceId is typed as string | number in DetailViewSchema, but this fallback logic calls resourceId.startsWith(prefix), which will throw if resourceId is a number. Consider normalizing resourceId to a string (or guarding by type) before using string methods and constructing altId.
Navigating from a list page to a record detail page shows "Record not found" because
RecordDetailViewstrips theobjectName-prefix from the URL recordId before callingfindOne. The navigation code putsrecord._iddirectly into the URL — no prefix is added artificially — so stripping it truncates backend-generated IDs likeopportunity-1772370455623-1→1772370455623-1, which doesn't match.Changes
RecordDetailView.tsx— Remove prefix stripping; pass URLrecordIdthrough as-isDetailView.tsx— Add ID format fallback: iffindOnereturns null, retry with the alternate format (strip or prependobjectName-prefix) for backward compatibility with either ID conventionRecordDetailEdit.test.tsx— Update expectations to assertfindOnereceives the full URL recordIdBefore/After
The
DetailViewfallback ensures both ID formats resolve correctly regardless of backend convention:Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.