1760 cms save content before creating slug-change redirect#1774
Conversation
Renaming a published post's slug produced a redirect the API rejected
("Published content already exists for slug"): the redirect change
request reached the API before the content slug-change, so the old slug
was still occupied by published content.
Build the redirects from pre-save state, save the content first (vacating
the old slug), then queue the redirects claiming it. The sequential FIFO
localChanges drain guarantees the content commits before the redirect is
sent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
would it not be safer to create the redirect in the API? We are relying on the CMS to add the redirect, but in principle we cannot trust a client to handle mission critical operations. |
toEditable.save() returns an optimistic local ack before the server validates the change, so a lost slug-uniqueness race showed a false "Redirect created" toast and closed the modal, only reporting failure later via an unrelated generic toast. Wait for the queued localChanges entry to drain (falling back to the old optimistic behavior offline) and re-check the doc in Dexie before declaring success. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
| // ponytail: works around toEditable.save()'s optimistic-only ack (shared/ is currently | ||
| // locked to the senior) by watching the localChanges queue drain instead. Delete this once | ||
| // toEditable exposes a real awaitAck option. | ||
| export async function awaitLocalChangeResolution( | ||
| docId: Uuid, | ||
| timeoutMs = 8000, | ||
| ): Promise<"resolved" | "pending"> { | ||
| if (!isConnected.value) return "pending"; | ||
| return new Promise((resolve) => { | ||
| const scope = effectScope(true); | ||
| const timer = setTimeout(() => finish("pending"), timeoutMs); | ||
| function finish(result: "resolved" | "pending") { | ||
| clearTimeout(timer); | ||
| scope.stop(); | ||
| resolve(result); | ||
| } | ||
| scope.run(() => { | ||
| const count = useDexieLiveQuery(() => db.localChanges.where({ docId }).count(), { | ||
| initialValue: undefined as number | undefined, | ||
| }); | ||
| watch(count, (n) => n === 0 && finish("resolved"), { immediate: true }); | ||
| watch(isConnected, (online) => !online && finish("pending")); | ||
| }); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Tthis is done in the wrong place
Create auto-redirects in the API after published content slug changes, return ack info for CMS confirmations, and remove CMS-side redirect queuing.
|
purge() clears this.localChanges directly but doesn't resolve any pending localChangeAckWaiters. Since Redirect docs go through the local-queue path (they're always "in window" per isSyncable.ts), CreateOrEditRedirectModal.vue's saveRedirect(..., { awaitAck: true }) / removeRedirect(..., { awaitAck: true }) calls resolve via waitForLocalChangeAck. If a user saves a redirect while offline and then purges local data (Settings) before the change is ever acked, that save()/remove() call hangs forever — same failure mode the overwriteLocalChanges branch was fixed to avoid, just via a different path to losing the queued entry. Might be worth resolving (or rejecting) any outstanding waiters in purge() too, e.g. with the same synthetic-ack pattern used for overwriteLocalChanges. |
Renaming a published post's slug produced a redirect the API rejected ("Published content already exists for slug"): the redirect change request reached the API before the content slug-change, so the old slug was still occupied by published content.
Build the redirects from pre-save state, save the content first (vacating the old slug), then queue the redirects claiming it. The sequential FIFO localChanges drain guarantees the content commits before the redirect is sent.