# Draft and Publish States

## Objective

Let users prepare changes privately before anyone else sees them.

An explicit draft/published status with server-enforced access, a defined edit model, and cache invalidation on every transition.

## Before You Begin

This feature is being added to an application that already exists and already
works. Do not scaffold a new project, and do not assume a blank slate.

Inspect the codebase first and establish:

- The existing application structure and where code of this kind already lives.
- The framework and version in use.
- The existing design system — colours, spacing, typography, and component conventions.
- Existing UI components you can reuse instead of writing new ones.
- The existing database structure, if this feature needs to persist anything.
- The existing authentication and authorization system, if this feature is user-scoped.
- Dependencies already installed, so you don't add a library that duplicates one.
- The existing test setup and conventions.

Only start writing code once you understand the above. If the application
already implements part of this feature, extend it rather than replacing it.

## Implementation Instructions

1. Add an explicit status to the content model and enforce it in the query layer. Public reads must be unable to return a draft even when the ID is guessed; a hidden link is not access control.
2. Decide and document one edit model: either edits to published content go live immediately, or edits create a separate draft revision that must be published. Pick one and apply it everywhere — a mixed model is where data gets lost.
3. Support the full set of transitions, not just publish: unpublish back to draft, republish, discard a draft without touching the live version, and delete.
4. Invalidate every cache the moment status changes — page cache, CDN, sitemap, feeds, search index. A published record that a stale cache still serves as missing is the most common bug here.
5. Allow a shareable preview to be pinned to a specific unpublished version, so a reviewer sees the version that was sent, not whatever the draft has become since. The token mechanics — expiry, revocation, unguessability, noindex — are owned by Signed Share Links; use that rather than minting your own tokens.

## UI and UX Requirements

Match the application's existing design system exactly. Reuse its components,
spacing, and typography. This feature should look like it was always there.

## Responsive Requirements

Works on mobile, tablet, and desktop. Touch targets are large enough to hit on a
phone, and nothing overflows horizontally at 320px.

## Accessibility Requirements

- Fully keyboard navigable.
- Correct semantic elements and ARIA roles.
- Visible focus states.
- Meets WCAG AA contrast.
- Dynamic changes are announced to screen readers.
- Respects prefers-reduced-motion.

## Edge Cases

- A published record that is unpublished must disappear from listings, search, sitemaps, and feeds — not just from its own page.
- A URL that was public and is now a draft needs a deliberate answer: 404, 410, or a signed-in-only view. Decide it, do not let the framework decide.
- Two editors working a draft at once must not silently overwrite each other; detect the conflict at save time.
- Publishing must validate that required fields are present. Draft state is exactly where incomplete records live.
- A draft revision of a deleted parent record must be cleaned up, not left readable.
- Scheduled transitions to a future time are owned by Scheduled Publishing; this feature owns only the status model those jobs act on.

## Testing

Exercise the feature end to end in the running application. Cover every edge case
above, then run the existing test suite and confirm nothing regressed.

## Acceptance Criteria

- [ ] Draft content is unreachable by unauthorised users even with a direct ID or URL.
- [ ] The edit model is one documented rule applied consistently across all content types.
- [ ] Unpublish, republish, and discard-draft all work and are reflected everywhere the content appeared.
- [ ] Every status change invalidates page, CDN, feed, sitemap, and search-index caches.
- [ ] Publishing enforces required-field validation.
- [ ] A preview can be pinned to a specific unpublished version, using the existing signed-link mechanism.
- [ ] The feature matches the existing design system.
- [ ] No existing functionality is broken.

## Adaptation Rules

- Match the existing design system. Do not introduce a new colour palette,
  spacing scale, or component library.
- Reuse existing components and utilities wherever they fit.
- Follow the naming, file layout, and code style already present.
- Do not upgrade, replace, or remove existing dependencies to make this
  feature fit. Adapt the feature to the app, not the app to the feature.
- Do not break existing functionality. If a change is genuinely required in
  existing code, make the smallest one that works and say so.
- If something in these instructions conflicts with how the application is
  built, follow the application and explain the deviation.

## Final Verification

Before you report the work as done:

1. Re-read the acceptance criteria above and check each one against what you
   actually built.
2. Run the application and exercise the feature end to end.
3. Run the existing test suite and confirm you have broken nothing.
4. Check the feature on mobile, tablet, and desktop widths.
5. Check keyboard navigation and focus handling.
6. Summarize what changed: files added, files modified, and anything you
   deliberately did differently because of how this application is built.

If any acceptance criterion is unmet, fix it before reporting completion.
