feat(slug): keep slug in sync with title, lockable slug field, reject-on-collision option#8
Open
openrijal wants to merge 3 commits into
Open
feat(slug): keep slug in sync with title, lockable slug field, reject-on-collision option#8openrijal wants to merge 3 commits into
openrijal wants to merge 3 commits into
Conversation
…-on-collision option - Move slug auto-sync watcher out of create-only gate so the slug also follows the title on update — but only while the field is locked. - Add lock state per slug field (provided to AutoFormField via inject); slug fields render readonly with a lock toggle. Unlocking pauses the auto-sync so manual edits stick. - New `slugCollision: 'suffix' | 'reject'` option on registered models (default `'suffix'` keeps existing behavior). With `'reject'` the server pre-checks uniqueness and returns a validation error keyed by the slug field, surfaced on the form. - Pass `slugFields` through the update formspec route (was missing). Closes awecode#7 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New per-resource option `slugLockedByDefault?: boolean` (default `true`). When `false`, slug fields start unlocked so editors can type a custom slug immediately on create — the lock toggle is still available to re-enable auto-sync from the source field. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the same three options to `useJsonResourceRegistry().register(...)` for `kind: 'array'` resources: `slugFields`, `slugCollision`, `slugLockedByDefault`. Behavior matches the DB-registered models so editors can use one mental model regardless of storage backend. - `jsonResourceRegistry.ts`: new options on input + config; carried through `defaultArrayConfig`. - `jsonFormSpec.ts`: passes `slugFields` and `slugLockedByDefault` through both create and update form specs so the existing AutoForm/AutoFormField client wiring picks them up. - `slug.ts`: new in-memory helpers `ensureUniqueSlugsInRows` and `assertUniqueSlugsInRows` for array resources (mirroring the DB-side helpers, but pure functions over the in-memory rows). - `jsonResourceCrud.ts`: `createJsonArrayRecord` and `updateJsonArrayRecord` now run the uniqueness check (suffix or reject) inside the existing `writeArrayWithRetry` closure, before writing back to storage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #7
What changed
AutoForm.vuewatcher used to be gated onprops.mode === 'create'; it now runs in both modes, but only while the slug field is locked (see next point).slugFields,AutoFormField.vuerenders a lock icon in the trailing slot. Click it to unlock the field for manual editing — the auto-sync pauses so the editor's slug isn't overwritten when the title changes again.slugCollisionoption ('suffix' | 'reject', default'suffix'). The existing silent-1/-2suffix behavior is unchanged. With'reject', the server pre-checks uniqueness and throws a 422 withdata.errorskeyed by the slug field, so the form surfaces a per-field validation error.slugFieldsthrough to the client (was only sent by the create route).AutoFormtoAutoFormFieldviaprovide/inject, so non-slug forms have zero change in behavior.Files
server/utils/registry.ts— addedslugCollisiontoAdminModelOptionsandAdminModelConfig.server/utils/slug.ts— addedassertUniqueSlugs(cfg, data, excludeLookupValue?)that throws a 422 validation error when any slug collides.server/services/create.ts,server/services/update.ts— callassertUniqueSlugspre-insert whenslugCollision === 'reject'; re-assert on DB unique violation to handle races.server/api/autoadmin/formspec/[modelKey]/update/[lookupValue].ts— surfacecfg.slugFieldsto the client.components/AutoForm.vue— reactiveslugLocksmap, watcher gated onlocked,provided to children. Update mode skips clearing the slug when source is briefly empty.components/AutoFormField.vue— injects lock state; for text inputs that are slug fields, binds:readonlyand renders a lock/unlock toggle in the trailing slot.Backwards compat
slugCollisiondefaults to'suffix'— every existing registered model behaves the same.slugFieldsare unaffected (the lock UI, watcher, and validation only run whenslugFieldsis set).type: 'text'fields, which is what slug fields are in practice.How to test
slugFields: { slug: ['title'] }.slugCollision: 'reject', try to save a duplicate slug — see a validation error on the slug field instead of a silent-2suffix.