/** * Public data model for the comment progress indicator. * * A customer creates a comment in a progress state via `addComment`, pushes * updates via repeated `updateComment` calls, and finally writes the real * content with `state: 'completed'` on the SAME comment. * * DELIBERATELY NOT AGENT-SPECIFIC, and this is the whole point of the shape. * An earlier revision nested this under the agent block as `AgentData.progress`. * That forced a caller to supply a valid agent identity — `agentSource`, * `agentId`, `agentName`, and backend-side a full `reason` with * title/description/severity — merely to render three animated dots. Progress * is a property of the COMMENT, not of who authored it: translation pipelines, * moderation checks and multi-step workflows all want it without pretending to * be an agent. It therefore sits on `Comment.progress`, beside * `Comment.actions`, and nothing about progress touches the agent block. * * Part of the SDK public API (exported via `src/models.ts` → velt-types). * * NAMING: this feature uses `state`, never `status`. `status` already means * four different things on adjacent objects (`CommentAnnotation.status` is a * `CustomStatus`, `Comment.status` is `'added' | 'updated'`, * `SuggestionData.status` is the suggestion lifecycle, plus per-step). */ /** * Lifecycle of one progress run. * * REQUIRED on `CommentProgress`, and deliberately so: presence-based state * would force the final update to DELETE the field, and a Firestore merge write * cannot remove a field without `FieldValue.delete()`. An explicit * `state: 'completed'` makes the final update a plain merge. * * `'active'` deliberately matches the per-step `'active'`: both mean "the one * currently running", at different levels. */ export type CommentProgressState = 'active' | 'completed' | 'failed' | 'cancelled'; /** Lifecycle of one step within a progress run. */ export type CommentProgressStepState = 'pending' | 'active' | 'completed' | 'failed'; /** * One step of the progress trace. * * Phase 1 renders only the current step's label. The array exists so this can * grow into an expandable trace later without a model change. */ export interface CommentProgressStep { id?: string; /** * Customer-authored label shown on the progress row. * * This is CUSTOMER CONTENT and is never translated — see * `resolveProgressLabel` in `src/app/utils/comment-progress.utils.ts`. */ label: string; state?: CommentProgressStepState; startedAt?: number; completedAt?: number; metadata?: any; } /** * Progress payload on a comment. */ export interface CommentProgress { /** REQUIRED. See `CommentProgressState` for why presence-based state was rejected. */ state: CommentProgressState; /** * Progress steps. * * **The customer owns this array outright.** `updateComment` replaces the * comment wholesale, so whatever `steps` you send IS the new array. There is * deliberately NO server-side appending: it would make the SDK stateful about * array order, and it would break the checklist pattern below, where * re-sending the same list with a different step marked active is exactly the * right move. * * Three authoring patterns are supported, and label resolution handles all * three without being told which is in play: * * - **Replace** — send a single-element array on each push. * ``` * push 1: [ { label: 'Getting data', state: 'active' } ] * push 2: [ { label: 'Processing data', state: 'active' } ] * ``` * Simplest, and keeps no history. * * - **Append** — grow the array, marking the newest step active. * ``` * push 1: [ { label: 'Getting data', state: 'active' } ] * push 2: [ { label: 'Getting data', state: 'completed' }, * { label: 'Processing data', state: 'active' } ] * ``` * Richer, and what a streaming agent naturally produces. It preserves the * history a future expandable trace needs. Phase 1 renders only the tail. * * - **Checklist** — send the full list up front and move the `active` marker. * ``` * push 1: [ { label: 'Getting data', state: 'active' }, * { label: 'Processing data', state: 'pending' } ] * push 2: [ { label: 'Getting data', state: 'completed' }, * { label: 'Processing data', state: 'active' } ] * ``` * * **At most one step should be `state: 'active'` at a time.** The resolver * tolerates violations gracefully — it takes the LAST active step, so an * append-without-demoting still advances correctly — but a single active step * is the contract, and relying on the tolerance makes the rendered label * depend on array order rather than on what you marked. */ steps?: CommentProgressStep[]; /** * DISPLAY-ONLY. Phase 1 defaults to everyone and NEVER scopes. * * This is explicitly **not a security boundary**: Velt visibility is * annotation-level, so a comment inside a readable annotation is readable by * all. Do not add a filter keyed on this field without changing that * architecture first. */ visibleToUserIds?: string[]; /** * Customer-supplied start time. * * NEVER used for staleness — the staleness fail-safe reads the comment's own * SERVER-STAMPED `lastUpdated` (spec AC-046). Also never used for ordering * multiple progress rows, which sort by server-stamped `createdAt`, because * `startedAt` is optional and has no value when absent (spec AC-036). */ startedAt?: number; }