[sync] T6406 fix filtered link propagation and guard conditional lookup inserts#3616
[sync] T6406 fix filtered link propagation and guard conditional lookup inserts#3616tea-artist wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c806776f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| handleBestEffortDataDbDropError({ | ||
| error, | ||
| isMetaFallback, | ||
| logger: this.logger, | ||
| target: `table ${table.id}`, | ||
| }); |
There was a problem hiding this comment.
Skip invalidation after swallowed BYODB drop failures
When this new handler swallows a dead BYODB drop, dropTables still falls through to invalidateDroppedTable. That invalidator resolves a V2 container via getContainerForBase, and on a cold cache container creation calls createTypeValidationStrategy, which immediately executes a query against the same data DB; for the deleted Supabase tenant/auth-failure case being handled here, that reconnect fails again and the trash cleanup remains stuck. Consider skipping or making the cache invalidation best-effort when the physical drop was already swallowed.
Useful? React with 👍 / 👎.
| const { isMetaFallback } = await this.dataDbClientManager.getDataDatabaseForBase(baseId, { | ||
| useTransaction: true, | ||
| }); |
There was a problem hiding this comment.
Avoid re-resolving BYODB routes after resolution failures
When the BYODB connection is already in error or needs migration, dataPrismaForBase can fail while DataDbClientManager.resolveSpaceDataDb runs ensureConnectionMigrated; this catch then calls getDataDatabaseForBase, which takes the same migration/connect path again before returning isMetaFallback. In that state the dead-tenant/auth failure never reaches handleBestEffortDataDbDropError, so permanent base deletion remains stuck instead of being swallowed.
Useful? React with 👍 / 👎.
| if (!classification || classification.retryable) { | ||
| throw error; | ||
| } |
There was a problem hiding this comment.
Restrict which non-retryable drop errors are swallowed
Because this only rethrows retryable or unclassified errors, a live BYODB that rejects the DDL with a classified non-retryable error such as Postgres 42501 (data_db.permission_denied) is logged and swallowed. In that scenario the metadata cleanup commits even though the physical table/schema was not dropped, leaving customer data orphaned in a reachable database; the helper should allowlist the missing/deleted/auth-failure cases it intends to tolerate rather than every non-retryable classification.
Useful? React with 👍 / 👎.
6c80677 to
b39c6f8
Compare
b39c6f8 to
6eefd41
Compare
6eefd41 to
da63387
Compare
da63387 to
c88d1ad
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c88d1add52
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return rows.map((row) => ({ | ||
| ...row, | ||
| content: parseSettingContent(row.content), | ||
| content: stripCanarySpaceIdsForForceV2Cache(row.name, parseSettingContent(row.content)), |
There was a problem hiding this comment.
Avoid stripping canary IDs from API reads
When FORCE_V2_ALL=true, this stripped value is what SettingService.getSetting() returns to clients, not just what Redis stores. The admin canary settings UI reads that response and, when toggling the canary switch, posts spaceIds: canaryConfig?.spaceIds ?? [], so with an existing canary list a save under FORCE_V2_ALL overwrites the DB with an empty list. Keep the lean cache separate from the API/model value or omit spaceIds on unrelated updates.
Useful? React with 👍 / 👎.
c88d1ad to
3a17e14
Compare
3a17e14 to
13870f4
Compare
13870f4 to
ce9b62f
Compare
ce9b62f to
b81a1d2
Compare
b81a1d2 to
6a80708
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a807086cc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Central policy switch: with the escape hatch on, nothing counts as a | ||
| // cross-space target, which disables the create-forbid and the | ||
| // field-duplicate downgrade in one place. | ||
| if (isCrossSpaceReferenceAllowed()) return false; |
There was a problem hiding this comment.
Gate cross-space links by data-DB compatibility
When ALLOW_CROSS_SPACE_REFERENCE=true, this returns false before checking where the two tables live. The only data-DB guard added in this change blocks starting a new space migration, so an instance with spaces already bound to different data databases can now create a link/lookup across those spaces; later reads and computed updates route by one table’s data DB and try to join the foreign table on that same connection, where it does not exist. Please keep the escape hatch limited to pairs that share the same physical data DB, or reject it for already-migrated spaces.
Useful? React with 👍 / 👎.
| if (!share) return ''; | ||
| return getShareUrl(share.shareId); | ||
| }, [share]); | ||
| return shortUrl ?? (isFallback ? getShareUrl(share.shareId, origin) : ''); |
There was a problem hiding this comment.
Keep a valid base-share URL while short link loads
When a base share already exists, useShortLink initially returns no shortUrl and no error, so this passes '' into BaseShareContent; that component always renders EmbedConfigPopover, which calls new URL(shareUrl) during render and throws on the empty string. Opening an existing base share dialog, or the duplicated node-share path, can therefore crash before the short-link request finishes; keep using the long URL or gate the embed controls while loading.
Useful? React with 👍 / 👎.
6a80708 to
9e4a2f7
Compare
9e4a2f7 to
3daac16
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3daac1639e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ICsvParser, | ||
| } from '@teable/v2-core'; | ||
| import { domainError } from '@teable/v2-core'; | ||
| import { safeFetch } from '@teable/v2-utils'; |
There was a problem hiding this comment.
Keep browser parser off the Node SSRF barrel
When @teable/v2-container-browser is used, it imports PapaparseCsvParser, so this new root import pulls in @teable/v2-utils' ./ssrf barrel; that barrel also re-exports agents.ts/fetch.ts, which import Node-only request-filtering-agent and undici. Browser builds using the browser container now have those Node transport modules in the dependency graph and can fail to bundle or require unusable polyfills; import a browser-safe registry subpath or split the Node SSRF exports from the root barrel.
Useful? React with 👍 / 👎.
| ? { type: ShortLinkType.Template, resourceId: templateDetail.id } | ||
| : undefined | ||
| ); | ||
| const displayShareUrl = shortUrl ?? (isFallback ? shareUrl : ''); |
There was a problem hiding this comment.
Keep the publish success URL while short link is unavailable
After publishing a previously unpublished base, onSuccess sets shareUrl and immediately opens the success dialog, but templateDetail has not refetched as published yet, so useShortLink is disabled and isFallback is false. This expression therefore returns '', making the success dialog, copy button, external open, and social share actions use an empty URL until a later refetch/short-link request completes; keep the permalink from shareUrl in this loading/disabled state.
Useful? React with 👍 / 👎.
3daac16 to
6bc7755
Compare
85a19ba to
4a43c0c
Compare
4a43c0c to
fd01730
Compare
fd01730 to
0e59d61
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e59d617e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| export const safeFetch: ISsrfSafeFetchFn = (url, init) => | ||
| (registeredFetch ?? globalThis.fetch)(url, init); |
There was a problem hiding this comment.
Register SSRF-safe fetch before URL imports
When URL-backed CSV imports run through the public createV2OrpcRouter() default container path, createV2NodePgContainer() registers PapaparseCsvParser but no code calls setSafeFetch(createSsrfSafeFetch()); only the Nest V2ContainerService does that. Because safeFetch falls back to globalThis.fetch when unregistered, these imports still allow server-side requests to loopback/RFC1918 targets outside the Nest path, so register the guarded fetch in the node container/bootstrap path or fail closed when no implementation is registered.
Useful? React with 👍 / 👎.
0e59d61 to
087f28c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 087f28c1f6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .map((fieldDbName) => `coalesce(${quoteIdentifier(fieldDbName)}::text, '')`) | ||
| .join(` || ' ' || `); | ||
| return `to_tsvector(${quoteLiteral(languageConfig)}::regconfig, ${document || quoteLiteral('')})`; | ||
| .join(` || E'\\n' || `); | ||
| return `lower(${document || quoteLiteral('')})`; |
There was a problem hiding this comment.
Normalize long-text search documents before LIKE prefilter
When V2_TABLE_QUERY_OPS_SEARCH_ACCESS_PATH_RUNTIME=auto uses a generated-text access path that covers a long-text field, this builds the indexed document from the raw column text, but the default search predicate in buildFieldSearchCondition replaces CR/LF/tabs with spaces before applying ILIKE. A cell containing foo\nbar therefore matches a search for foo bar on the default path, while the generated document fails the LIKE lower('%foo bar%') prefilter and the row is excluded before the exact predicate can recheck it. Use the same long-text projection as the default search builder, or avoid the prefilter for fields whose document text is not a superset of the exact predicate.
Useful? React with 👍 / 👎.
| this.configService.get('V2_TABLE_QUERY_OPS_SEARCH_ACCESS_PATH_RUNTIME') ?? | ||
| this.configService.get('V2_TABLE_QUERY_OPS_SEARCH_VECTOR_RUNTIME') |
There was a problem hiding this comment.
Allow access-path rebuild tasks for the new runtime
When V2_TABLE_QUERY_OPS_SEARCH_ACCESS_PATH_RUNTIME=auto enables the task worker without manual index execution, this new env path makes searchVectorRuntimeEnabled true, but the default allowed kinds below still only include rebuild_search_vector. The schema-change scheduler now queues rebuild_search_access_path, and claimNextAccepted filters by allowedKinds, so those rebuilds are never claimed and the search config stays rebuild_pending instead of returning to ready. Include the new access-path rebuild kind in the default and parsed allowlists.
Useful? React with 👍 / 👎.
| (recordSearchAccessPath?.kind === 'generated_tsvector' || | ||
| recordSearchAccessPath?.kind === 'generated_text') && | ||
| query.search && | ||
| query.includeQueryExtra !== true |
There was a problem hiding this comment.
Preserve grouped query extras on generated-text search
When generated_text is selected for a searched V2 records request that also has groupBy or collapsedGroupIds, this early return skips getQueryExtra() entirely unless the caller explicitly sets includeQueryExtra=true. That drops extra.groupPoints/allGroupHeaderRefs even though the returned record ids are still grouped, so grouped REST clients cannot render the matching group headers during search; keep loading the group extras and only skip the expensive search-hit extra, or make grouped requests opt in automatically.
Useful? React with 👍 / 👎.
087f28c to
3a8b2c4
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3a8b2c43b9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const ssrDataFresh = | ||
| initialAnchorRef.current.tableId === tableId && initialAnchorRef.current.viewId === viewId; |
There was a problem hiding this comment.
Stop reusing the initial SSR snapshot after navigation
When the user shallow-navigates away from the page-load table/view and later returns to that same initial pair, this expression becomes true again, so useTableSeed is disabled and the providers are seeded from the original page-load props instead of a fresh bounded seed. In a long-lived session this can briefly show obsolete fields/records or allow interacting with stale doc-less records until the ShareDB query catches up; track that the user has left the initial anchor once, or invalidate the SSR snapshot on the first anchor change.
Useful? React with 👍 / 👎.
3a8b2c4 to
014e76c
Compare
014e76c to
e251ac4
Compare
e251ac4 to
381b80d
Compare
381b80d to
fab7775
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bfa173325b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const strippedFieldOptions = stripLookupFormulaExecutableOptions(field.options) ?? {}; | ||
| const { | ||
| formatting = inheritableOptions.formatting, | ||
| showAs, | ||
| ...inheritOptions | ||
| } = field.options as Record<string, unknown>; | ||
| } = strippedFieldOptions; |
There was a problem hiding this comment.
Preserve null lookup showAs tombstones
When a regular lookup field has explicitly cleared an inherited display mode (options.showAs === null), this newly stripped field.options path extracts that null into showAs, but the later options rebuild only spreads showAs when it is truthy. If the linked/source field changes and updateLookupField runs, the null tombstone is dropped from the persisted options; after reload the lookup can inherit the source field's showAs again, undoing the user's clear. Preserve the property based on ownership (including null) rather than truthiness.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc1823befc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| await this.checkInvitationLimits(); | ||
|
|
||
| const departmentIds = this.cls.get('organization.departments')?.map((d) => d.id); |
There was a problem hiding this comment.
Restore the invitation rate-limit check
When MAX_INVITATIONS_PER_HOUR is configured, email invitations now proceed without any quota check because the only call to the removed checkInvitationLimits() was deleted and a repo-wide search shows no remaining reference to that env var. In deployments that relied on this setting for anti-abuse, a user can send unlimited invitations instead of being blocked/deactivated after the hourly threshold; keep the check here or replace it with an equivalent risk-control path.
Useful? React with 👍 / 👎.
| SELECT EXISTS ( | ||
| SELECT 1 | ||
| FROM computed_task_field_ref AS refs | ||
| WHERE refs.table_id = ${tableId} |
There was a problem hiding this comment.
Add a table_id index before polling activity refs
This new reconciliation path runs on every compute-activity read and filters computed_task_field_ref by table_id, but the migration only creates computed_task_field_ref_field_id_idx, not a table_id index. On instances with many queued/completed compute refs, each healthy poll for any table has to scan the whole ref table (twice, including the grouped check below), which can make the diagnostics endpoint and toolbar polling expensive; add an index such as (table_id) or (table_id, field_id) with this query.
Useful? React with 👍 / 👎.
| value.trim() | ||
| ); | ||
| const exactOrigins = normalizeOrigins([ | ||
| publicOrigin, |
There was a problem hiding this comment.
Keep PUBLIC_ORIGIN out of exact-origin SSRF trust
When PUBLIC_ORIGIN is an internal or loopback app URL, an authenticated URL import can submit an absolute URL on that origin such as /admin; attachmentUrlSchema accepts absolute http(s) URLs, and adding publicOrigin to exactOrigins makes createTrustedUrlPredicate() bypass the filtering agent for every path on that origin. This reopens the app-path SSRF case the schema is trying to block, so keep PUBLIC_ORIGIN limited to the attachment-read path unless an origin was explicitly configured as fully trusted.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6c7d342c3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (this.isLookup) { | ||
| return []; |
There was a problem hiding this comment.
Restore lookup-formula link dependencies
When a regular lookup targets a formula (type === Formula and isLookup), this branch makes getReferenceFields() empty; because FormulaFieldCore.getLinkFields() still derives link dependencies only from those reference fields, it no longer returns the lookupOptions.linkFieldId. The record CTE builder relies on field.getLinkFields(this.table) to emit hidden link CTEs before resolving projected lookups, so lookup-formula values whose link field is hidden/not projected can be resolved without the required join and come back null. Keep expression references suppressed, but have getLinkFields() fall back to the base lookupOptions path for lookup fields.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e82e385dc9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| this.eventEmitterService.emitAsync( | ||
| Events.COLLABORATOR_CREATE, | ||
| new CollaboratorCreateEvent(base.spaceId, { | ||
| resourceId: baseId, | ||
| resourceType: CollaboratorType.Base, |
There was a problem hiding this comment.
Defer invite notifications until collaborator transactions commit
When this helper is invoked inside existing transactions such as DashboardService.installPlugin (checked its $tx wrapper) or the plugin context-menu install flow, this new contextual event is emitted immediately after createMany, before the surrounding transaction performs later writes. If a later write rolls back, CollaboratorInviteNotificationListener can already have inserted/sent an invite notification for a collaborator that was never committed; either keep skipEvent for transactional callers and emit post-commit, or register the notification after the outer transaction commits.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
If deleteResource() throws after this call (for example a table/drop or dashboard delete failure), the method exits before deleting the base_node, but deleteNodeShares() has already hard-deleted the node's base-share rows and marked their short links dead. That leaves the still-existing table/dashboard with its share links permanently revoked after a failed delete attempt; perform this cleanup only after the resource/node delete succeeds, or make the whole sequence transactional where possible.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const startIndex = queryRef.current.skip ?? 0; | ||
| const take = queryRef.current.take ?? LOAD_PAGE_SIZE; | ||
| lastMergedSkipRef.current = startIndex; | ||
| const isFirstFreshDelivery = pendingFreshRef.current && (extra != null || records.length > 0); |
There was a problem hiding this comment.
Treat empty record deliveries as fresh
When switching back to a cached grid whose current result is empty and ungrouped, the first ShareDB ready event has records.length === 0 and usually no extra, so this condition never marks it as the authoritative fresh delivery. The merge below then copies the seeded preLoadedRecords back into state instead of clearing them, and settledRef never evicts that snapshot, leaving rows from the previous result visible until another non-empty/extra update arrives. Treat the first ready/changed delivery as fresh even when it is empty, or use an explicit readiness signal, before retaining cached rows.
Useful? React with 👍 / 👎.
| // previous view's structure — writing it here would poison the new | ||
| // view's slot, so skip until state and key agree again | ||
| if (keyChanged) return; | ||
| if (view?.id && groupPoints != null) { |
There was a problem hiding this comment.
Clear cached groups when grouping is removed
When a view that has cached groupPoints is later changed to an ungrouped view, the fresh subscription result typically has no groupPoints (and may have no extra), so this guard skips updating the cache. The old grouped structure remains in useGridViewCacheStore and is seeded the next time the user returns to the view, causing stale group headers/layout to appear for an ungrouped result until another grouped delivery overwrites it; clear the cached group facet when the authoritative result has no group points.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 388019c370
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const { provider, publicUrl, publicBucket, privateBucket, privateBucketEndpoint, s3, minio } = | ||
| storageConfig(); |
There was a problem hiding this comment.
Include local encryption in preview cache fingerprint
When local storage is used and BACKEND_STORAGE_ENCRYPTION_KEY/IV is rotated while Redis/SQLite preview cache survives a deploy, this fingerprint does not change because it omits storageConfig().encryption. LocalStorage.getPreviewUrl() embeds an encrypted read token in the cached URL, so after rotation verifyReadToken() cannot decrypt the old token and attachment previews keep returning broken cached URLs until the preview cache TTL expires; include the encryption settings (digesting secrets like the S3/MinIO keys) in the config signature.
Useful? React with 👍 / 👎.
…up inserts Synced from teableio/teable-ee@ccc4386 Co-authored-by: Aries X <caoxing9@gmail.com> Co-authored-by: Bieber <artist@teable.io> Co-authored-by: Boris <boris2code@outlook.com> Co-authored-by: Jocky Zhou <jocky@teable.ai> Co-authored-by: Jun Lu <hammond@teable.io> Co-authored-by: Pengap <penganpingprivte@gmail.com> Co-authored-by: SkyHuang <sky.huang.fe@gmail.com> Co-authored-by: Uno <uno@teable.ai> Co-authored-by: nichenqin <nichenqin@hotmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 55b8263fd3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Promise.resolve() | ||
| .then(() => view.updateColumnMeta(columnMetaRo)) | ||
| .catch(() => { |
There was a problem hiding this comment.
Serialize consecutive column-order writes
When a user performs a second drag before the first updateColumnMeta request settles, this launches another independent write whose order was calculated from the first drag's optimistic state, while persisting only the field moved by the second drag. If the first request then fails or the requests are applied out of order, the second write is committed against server ordering that never contained its prerequisite move, and the first rejection also clears the newer optimistic state, leaving a different column order than the user selected. Serialize these writes or persist/rollback each optimistic ordering as one versioned operation.
Useful? React with 👍 / 👎.
🔄 Automated sync from EE repository.
54 commit(s) synced since last sync.
Authors
Included commits
Latest source commit: teableio/teable-ee@ccc4386
This PR was automatically created by the sync workflow.