You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Opening this as the maintainer-facing design thread that the triage on #2956 asked for. The symptoms there are confirmed by the bot against the repo; this thread is only about how to fix them, and about the two decisions that are not mine to make.
The one-line version
A new entry has no row until the first manual save. Everything else follows from that: ContentEditor.tsx:540 skips autosave while isNew, and ContentSettingsPanel.tsx hides Taxonomies, SEO, Revisions, Translations, Delete and plugin panels because they all need an entryId. The proposal is to create the row earlier, as an auto_draft, the way WordPress has done since 3.0.
Design sketch, in the order it would ship
1. Status. Add auto_draft below draft. packages/core/src/schema/registry.ts defines status as a plain text column defaulting to "draft", and packages/core/src/api/schemas/content.ts accepts only "draft" on create, so this is a new accepted value plus every read path learning to exclude it. No migration of existing rows is needed — nothing is ever auto_draft retroactively.
2. Where auto_draft must be invisible. This is the part worth agreeing on before code, because a miss here is a data leak, not a bug:
content list and search (ContentList, FTS)
dashboard stats and per-collection counts
sitemap, feeds, public content API
webhooks and content hooks (an auto_draft must never fire "content created")
MCP / plugin content reads
translations listing
The safest shape is one predicate in the repository layer that every read path already goes through, rather than N call sites remembering to filter. If that predicate does not exist today, adding it is arguably the real first PR, independent of this feature.
3. Creation timing: first edit, not route mount. Opening /content/:collection/new and walking away must create nothing. The trigger is the first keystroke or first field change. This is the difference between "a few abandoned rows" and "a row per curious click".
4. Route swap in place.history.replaceState from /content/:collection/new to /content/:collection/:id. No reload, no navigation, no focus loss mid-typing. After that isNew is false, autosave passes its own guard with no change to the guard, and every panel receives an entryId. That is the property that makes this proposal small in the editor: the editor barely changes.
5. Slug. Stays optional and unset while auto_draft, so empty rows never hit the unique-slug constraint (the failure mode of #2034). Promotion to draft on the first real save applies the normal slug rules.
6. Garbage collection. A scheduled job hard-deletes auto_draft rows untouched for N days (WordPress uses 7). Hard delete, not trash: they were never content. Worth deciding whether N is configurable per install or fixed.
7. Permissions. Creating an auto_draft requires exactly the permission that creating content requires today. Nothing new is granted, and the check happens at creation, not at promotion.
The two decisions I cannot make
Is auto_draft a status, or a nullable is_draft_placeholder flag? A status is closer to WordPress and reads naturally in queries, but it widens an enum that other code branches on. A flag keeps the status enum untouched at the cost of a second thing to remember in filters. I lean status; the maintainers own that call.
Scope of the first PR. Full feature, or step one being only the read-path predicate plus the status, with the editor still creating on explicit save? The second is boring and safe and makes the third PR trivial.
What the tests should prove
Straight from the triage, plus one:
A new entry autosaves after the first keystroke.
The route swaps without reload or focus loss (caret position survives).
auto_draft rows are invisible to dashboard counts, content lists, search, sitemap, feeds and the public API.
Webhooks and content hooks do not fire for auto_draft creation or for its promotion to draft (the promotion is the "created" event, not the placeholder).
GC deletes untouched auto_draft rows and never touches a promoted one.
Why it is worth the cross-cutting cost
The current behaviour is not a missing nicety. Someone writing a long post in a new entry has no autosave, no local copy and no warning; closing the tab loses the work. Editors coming from WordPress have the opposite expectation, formed by every existing entry in the same admin, which does autosave every 2 s. Refs #2956, and #2857 from the labelling angle.
Happy to implement whichever scope the maintainers pick.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Opening this as the maintainer-facing design thread that the triage on #2956 asked for. The symptoms there are confirmed by the bot against the repo; this thread is only about how to fix them, and about the two decisions that are not mine to make.
The one-line version
A new entry has no row until the first manual save. Everything else follows from that:
ContentEditor.tsx:540skips autosave whileisNew, andContentSettingsPanel.tsxhides Taxonomies, SEO, Revisions, Translations, Delete and plugin panels because they all need anentryId. The proposal is to create the row earlier, as anauto_draft, the way WordPress has done since 3.0.Design sketch, in the order it would ship
1. Status. Add
auto_draftbelowdraft.packages/core/src/schema/registry.tsdefinesstatusas a plain text column defaulting to"draft", andpackages/core/src/api/schemas/content.tsaccepts only"draft"on create, so this is a new accepted value plus every read path learning to exclude it. No migration of existing rows is needed — nothing is everauto_draftretroactively.2. Where
auto_draftmust be invisible. This is the part worth agreeing on before code, because a miss here is a data leak, not a bug:ContentList, FTS)auto_draftmust never fire "content created")The safest shape is one predicate in the repository layer that every read path already goes through, rather than N call sites remembering to filter. If that predicate does not exist today, adding it is arguably the real first PR, independent of this feature.
3. Creation timing: first edit, not route mount. Opening
/content/:collection/newand walking away must create nothing. The trigger is the first keystroke or first field change. This is the difference between "a few abandoned rows" and "a row per curious click".4. Route swap in place.
history.replaceStatefrom/content/:collection/newto/content/:collection/:id. No reload, no navigation, no focus loss mid-typing. After thatisNewis false, autosave passes its own guard with no change to the guard, and every panel receives anentryId. That is the property that makes this proposal small in the editor: the editor barely changes.5. Slug. Stays optional and unset while
auto_draft, so empty rows never hit the unique-slug constraint (the failure mode of #2034). Promotion todrafton the first real save applies the normal slug rules.6. Garbage collection. A scheduled job hard-deletes
auto_draftrows untouched for N days (WordPress uses 7). Hard delete, not trash: they were never content. Worth deciding whether N is configurable per install or fixed.7. Permissions. Creating an
auto_draftrequires exactly the permission that creating content requires today. Nothing new is granted, and the check happens at creation, not at promotion.The two decisions I cannot make
auto_drafta status, or a nullableis_draft_placeholderflag? A status is closer to WordPress and reads naturally in queries, but it widens an enum that other code branches on. A flag keeps the status enum untouched at the cost of a second thing to remember in filters. I lean status; the maintainers own that call.What the tests should prove
Straight from the triage, plus one:
auto_draftrows are invisible to dashboard counts, content lists, search, sitemap, feeds and the public API.auto_draftcreation or for its promotion todraft(the promotion is the "created" event, not the placeholder).auto_draftrows and never touches a promoted one.Why it is worth the cross-cutting cost
The current behaviour is not a missing nicety. Someone writing a long post in a new entry has no autosave, no local copy and no warning; closing the tab loses the work. Editors coming from WordPress have the opposite expectation, formed by every existing entry in the same admin, which does autosave every 2 s. Refs #2956, and #2857 from the labelling angle.
Happy to implement whichever scope the maintainers pick.
All reactions