import type { ContentStatus } from '../db/schema.js'; /** * The editorial workflow, as a transition model rather than a free-form status field. * * Until now `status` was a column anyone with the right role could set to anything: a contributor * could not publish, and that was the whole of it. Nothing said an archived page should come back * as a draft rather than reappearing live, or that "submit for review" is a different act from * "set the status to in_review" — even though the second is how the first was spelled. * * Modelled here in core and not in a screen, because the API is the boundary. A rule the editor * enforces and the REST API does not is not a rule. * * The graph is deliberately small. Every arrow is one somebody asked for; there is no * `archived → published`, because bringing an old page back is a decision that deserves a look at * its content first, and landing in `draft` is what forces that look. */ /** The lowest role that may make a transition. `null` means nobody: the move is not offered. */ export type TransitionRole = 'contributor' | 'editor' | null; export interface Transition { to: ContentStatus; role: Exclude; } /** Every move out of a status, with the role each needs. */ export declare function transitionsFrom(status: ContentStatus): Transition[]; /** * Which move out of a status deserves to be the visible button, if any. * * The editor renders one named button plus a "More" disclosure, because four full-width buttons is * most of the sidebar and three of them are rare on any given edit. Which one is promoted is an * editorial judgement, so it is a table here rather than "whichever the loop reaches first" — that * would promote `in_review` on a draft for an editor who is about to publish, purely because of key * order in the object above. * * `published` deliberately has **no primary**. Everything reachable from it — back to draft, back to * review, schedule, archive — is an unusual thing to do to a live page, and the usual reason to open * one is to edit its content and press Save. Promoting any of them would be putting a button nobody * wants next to the one they do. * * `archived` needs no entry either: it has exactly one move, so there is nothing to hide behind a * disclosure and `transitionsFrom` already returns a list of one. */ export declare function primaryTransition(from: ContentStatus, canPublish: boolean): ContentStatus | undefined; /** * The role a transition needs, or `null` if it is not a legal move at all. * * Staying put is always allowed and needs nothing: the editor posts the whole form, so an * unchanged status arrives as a value on every save and must not be read as a transition. */ export declare function transitionRole(from: ContentStatus, to: ContentStatus): TransitionRole | 'unchanged'; export declare function isLegalTransition(from: ContentStatus, to: ContentStatus): boolean; /** * How to describe a transition to the person making it. * * The label is the *act*, not the destination. "Submit for review" and "set status to in_review" * are the same row in the table and different things to an editor — and the second is how the * first has been spelled until now, which is why nobody could find it. */ export declare function transitionLabel(from: ContentStatus, to: ContentStatus): string;