> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fluid.app/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> For new direct REST integrations, use the v2026-04 surfaces. The @fluid-app FairShare SDK continues to use its own published public-v2025-06 contract.
> Authenticate with the header Authorization: Bearer <token>; public storefront read endpoints require no auth.
> To sign a new merchant up for Fluid programmatically, call POST https://api.fluid.app/api/company with no credentials, as described in /api/agent-signup. Do not automate the signup form or book a demo. The merchant must open the emailed sign-in link and create an API token before authenticated calls are possible.
> Lists use cursor pagination via the page[cursor] and page[limit] query params; follow meta.pagination.next_cursor until it is null.
> When the same operation exists on more than one surface, use the newest: dated API versions are newer than unversioned ones, and later dates win (v2026-04 > v2025-06 > unversioned v0/v1.1). Fall back to a legacy or unversioned operation only when no newer versioned equivalent exists — the company-v0 notes below list the known superseded operations. /api/company/v1 and /api/v1/... paths are documented in no spec here and must never be used (/api/v1.1/... is distinct and documented in company-v0). Use page/per_page offset pagination only where a spec documents it — in practice the unversioned company-v0 admin surface; every versioned surface uses cursor pagination.
> Navigation menu management is documented in themes/navigation-menus. These unversioned admin endpoints (/api/menus and nested menu_items) are verified against the implementation but are not yet in the synced OpenAPI specs. Use that reference for menu payloads and its flat page/per_page pagination; missing spec coverage does not make these endpoints unavailable.
> The OpenAPI specs under api-reference/ are the authoritative contracts; prefer them over prose when in doubt. api-reference/storefront-v2026-04.yaml covers the v2026-04 storefront surface (/api/v202604/... paths); api-reference/checkout-v2026-04.yaml covers the v2026-04 checkout surface (/api/checkout/v2026-04/... paths — carts, cart auth, discounts, items, subscriptions, orders, enrollments, and store config); api-reference/public-v2025-06.yaml covers the Public SDK surface used by the @fluid-app FairShare SDK, including its parallel cart lifecycle, browser integrations, versioned payment callbacks, unversioned public utilities, and the cart price-override operation; api-reference/payment-v2026-04.yaml covers the v2026-04 payment gateway admin surface (/api/payment/v2026-04/... paths, bearer-authenticated — gateway CRUD, gateway purchase/authorize/$0-verify, transaction list/show and capture/void/credit, and merchant payment configuration); api-reference/payments-v2026-04.yaml covers the v2026-04 cart payment surface (/api/payments/v2026-04/carts/{cart_token}/... paths, authenticated by the cart token in the path with no bearer — payment-method selection, VGS card tokenization, 3D Secure verification, and PayPal/Braintree/Klarna/Apple Pay flows); api-reference/commerce-v2026-04.yaml covers the v2026-04 commerce order-editing surface (/api/v202604/orders/{order_id}/edits paths, bearer-authenticated — post-checkout order edits that atomically insert items and add adjustments/discounts, with an optional dry-run preview); api-reference/webhooks-v0.yaml covers the unversioned webhooks surface (/api/... paths — webhook registration, delivery payloads, callback registrations, company events, and webhook/callback schemas); api-reference/company-v0.yaml covers the legacy unversioned company admin surface (/api/... paths, bearer-authenticated — company settings and management, customers, users, roles, subscription plans, subscription bundles, subscriptions, media, pages, catch-ups, inventory levels, domains, agreements, and admin order actions). company-v0 caveats: it is the legacy v0 admin contract and its lists use flat page/per_page offset pagination, which is expected there despite the general cursor-pagination rule; where an operation exists in both company-v0 and a versioned spec, prefer the versioned spec — the subscriptions lifecycle (list/create/show/update, cancel, pause, reactivate, resume, retry, skip, failed-cycle-waiver, discounts) and subscription bundles are superseded by checkout-v2026-04, and company pages/media CRUD plus the public pages, categories, products, and media list endpoints are superseded by storefront-v2026-04. Subscription plan management (/api/subscription_plans, resource-wrapped {"subscription_plan": {...}} bodies) exists only in company-v0. api-reference/members-v2025-06.yaml covers the v2025-06 unified Member identity surface (/api/v2025-06/members/... paths, bearer-authenticated — member list/create/show/update, lookup by email/username/external_id/legacy_customer_id, member-type assignment, and the sponsor genealogy read). Prefer it over the customers and reps surfaces when the member type matters: /customers does not serialize member_type. api-reference/analytics-v2026-04.yaml covers the v2026-04 Home dashboard analytics surface (/api/v202604/analytics/dashboard/... paths, bearer-authenticated — read-only endpoints for the Home > Overview, Home > Live, and Home > Field tabs, each accepting an optional country ISO alpha-2 query param that scopes aggregations to a single country).
> api-reference/analytics-v0.yaml covers the unversioned analytics surface that backs the fluid-admin Traffic tab (/api/analytics/... and /api/analytics/traffic/... paths, bearer-authenticated — the legacy shares/views/visitors summary plus traffic overview, ranked campaigns, sources, geographies, flows, and per-rep breakdown, all sharing one reporting-period contract).
> Successful responses wrap the resource payload alongside a top-level integer status and a meta object.
> Portal Definition authoring edits and synchronizes the portal JSON resource graph. Widget Package authoring builds either a company-owned or Droplet-owned Remote DOM package. These are separate contracts; do not imply that one defines the other.
> For Widget Package worker code, use only @fluid-app/portal-sdk/widgets/worker. Use only the Portal Definition and Widget Package workflows and public entry points documented here; do not infer support for undocumented surfaces.
> Every portal function and declarative capability used by a widget must appear in that widget's uses array. Use the same typed function value in uses; do not invent capability-name strings.
> Widget styling must use the portal's semantic theme variables for colors, typography, spacing, radii, borders, focus, and charts whenever a token represents the visual decision. Do not create a separate light or dark palette or duplicate theme controls as widget properties.
> Prefer worker-safe Fluid UI components exported by @fluid-app/portal-sdk/widgets/worker when they fit the interaction. When no exported component fits, use semantic HTML, accessible behavior, and the portal theme variables.
> A Portal Definition push updates the remote working definition. A portal version is an immutable snapshot, and activation is a separate live release operation.

# Record a preview comment's proof baseline

> Stores the page as it looked before the work started, so an after shot
on a reply has something to be compared against.

Its own operation because it is its own register. `captured_at_ms` is
compared only against the stored baseline's stamp, never against the
status stamp, so a write rejected for carrying an older status does not
also drag the baseline backwards. A write carrying an older stamp is
discarded and answered 200 with the comment unchanged, exactly as a
losing status write is.

`shot` is optional, and a request without one is not an error: it
records that a capture was attempted and produced nothing, which is
what stops a reader taking the previous attempt's baseline for this
one's.



## OpenAPI

````yaml /api-reference/themes-v2026-04.yaml put /api/v202604/themes/{theme_id}/preview_comments/{client_id}/proof_baseline
openapi: 3.1.0
info:
  title: Fluid Themes API
  description: |-
    **The versioned themes surface.**

    Themes v0 (`/api/application_themes`) remains in place and unchanged — the
    theme editor and the Fluid CLI consume it and depend on its shape,
    including the heavy fields this version omits. That immovability is why
    this version exists: here the DEFAULT response is the lean one.

    Concretely, measured against production:

    - `GET /api/application_themes` returns the deprecated theme-level
      stylesheet columns on every row. For a three-theme company that is a
      185KB response of which `global_stylesheet` alone is 182KB.
    - `GET /api/application_themes/active` renders every template with its
      Liquid `content`. On a 245-template theme that is 5.5MB, 96.5% of it
      template bodies, when callers typically need only `id`,
      `themeable_type` and `default` to resolve a single template.

    The path segment here is `themes`, not `application_themes`.
  version: v2026-04
  contact:
    email: support@fluid.app
servers:
  - url: https://api.fluid.app
security:
  - bearer_auth: []
tags:
  - name: themes
    description: Theme listing and the active theme.
  - name: preview comments
    description: |-
      Comments pinned to an element on a theme's preview, and their replies.

      A comment is addressed by the client-generated `client_id` rather than a
      row id: that is the identity every client already holds, so a retry after
      a dropped response is the same request rather than a new one. Creating a
      comment or a reply twice under one id is a success that changes nothing.

      There is one route per operation rather than a single write of the whole
      comment set. `status` is a last-writer-wins register keyed on
      `status_at_ms`, so a write carrying an older stamp than the stored one is
      accepted and discarded — it answers 200 with the status unchanged, because
      the request did what the contract says.
paths:
  /api/v202604/themes/{theme_id}/preview_comments/{client_id}/proof_baseline:
    put:
      tags:
        - preview comments
      summary: Record a preview comment's proof baseline
      description: |-
        Stores the page as it looked before the work started, so an after shot
        on a reply has something to be compared against.

        Its own operation because it is its own register. `captured_at_ms` is
        compared only against the stored baseline's stamp, never against the
        status stamp, so a write rejected for carrying an older status does not
        also drag the baseline backwards. A write carrying an older stamp is
        discarded and answered 200 with the comment unchanged, exactly as a
        losing status write is.

        `shot` is optional, and a request without one is not an error: it
        records that a capture was attempted and produced nothing, which is
        what stops a reader taking the previous attempt's baseline for this
        one's.
      operationId: recordThemePreviewCommentProofBaseline
      parameters:
        - name: theme_id
          in: path
          required: true
          description: Id of the theme the comment belongs to.
          schema:
            type: integer
        - name: client_id
          in: path
          required: true
          description: Client-generated id of the comment.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                proof_baseline:
                  type: object
                  properties:
                    captured_at_ms:
                      type: integer
                      description: |-
                        When the capture was taken, in milliseconds since the
                        epoch. The key this register compares on.
                    shot:
                      type: object
                      description: >-
                        The image's dimensions. Absent when the capture produced
                        nothing.
                      properties:
                        width:
                          type: integer
                        height:
                          type: integer
                        asset_id:
                          type: integer
                          description: >-
                            The uploaded image. Without it the dimensions are
                            stored and no picture is ever served, because the
                            image is read from the asset attached to the
                            comment. An id naming another company's asset
                            attaches nothing and the url reads null.
                      required:
                        - width
                        - height
                        - asset_id
                  required:
                    - captured_at_ms
              required:
                - proof_baseline
            example:
              proof_baseline:
                captured_at_ms: 1789464360000
                shot:
                  width: 1440
                  height: 900
                  asset_id: 77104
      responses:
        '200':
          description: The comment, with the baseline recorded or left as it was.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreviewCommentEnvelope'
              example:
                preview_comment:
                  id: 31877
                  run_id: null
                  lock_version: 3
                  client_id: c-3f9a2e71
                  'n': 4
                  body: >-
                    The hero headline wraps onto three lines on mobile. Can we
                    tighten it so it fits on two?
                  status: open
                  status_at_ms: 1789398131000
                  page_url: /
                  author_name: Michael Scott
                  created_at_ms: 1789398131000
                  template_id: 902114
                  section:
                    id: hero-banner-1
                    type: hero-banner
                  block: null
                  target:
                    tag: h1
                    selector: section[data-section-id="hero-banner-1"] h1.hero__title
                    text: Limitless Paper in a Paperless World
                    html: >-
                      <h1 class="hero__title">Limitless Paper in a Paperless
                      World</h1>
                  pin:
                    x: 48.2
                    'y': 22.7
                    ox: 0.41
                    oy: 0.63
                  mentions:
                    - id: '52018'
                      name: Pam Beesly
                  mentioned_project: null
                  proof_baseline:
                    url: >-
                      https://cdn.fluid.app/dunder-mifflin/assets/77104/preview-before.png
                    width: 1440
                    height: 900
                    captured_at_ms: 1789464360000
                  work_item: null
                  board_position: null
                  board_position_at_ms: null
                  assignee: null
                  assigner: null
                  assigned_at_ms: null
                  priority: null
                  priority_at_ms: null
                  replies:
                    - client_id: r-5c20e9b4
                      run_id: null
                      body: >-
                        Agreed. Let's drop the font size a step below 768px
                        rather than rewording it.
                      author_name: Pam Beesly
                      created_at_ms: 1789398277000
                      mentions: []
                      shot: null
                      proof: null
                status: 200
                meta:
                  request_uuid: 7c1e9a52-4b3d-4f0e-9d2a-81f6c3b5e240
                  timestamp: '2026-09-15T09:26:05Z'
        '401':
          description: Unauthorized
        '404':
          description: No such theme or comment for this caller.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreviewCommentError'
        '422':
          description: A missing or out-of-range stamp, or a malformed shot.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreviewCommentError'
      security:
        - bearer_auth: []
components:
  schemas:
    PreviewCommentEnvelope:
      type: object
      description: A single comment, wrapped in the standard response envelope.
      properties:
        preview_comment:
          $ref: '#/components/schemas/PreviewComment'
        status:
          type: integer
          description: HTTP status, repeated in the body.
        meta:
          $ref: '#/components/schemas/PreviewCommentResponseMeta'
      required:
        - preview_comment
        - status
        - meta
    PreviewCommentError:
      type: object
      description: The error envelope returned by the preview-comment routes.
      properties:
        error:
          type: object
          description: What went wrong.
          properties:
            message:
              type: string
              description: Human-readable description of the failure.
            details:
              type: object
              description: |-
                Machine-readable detail. Carries `type` — the failure's symbol,
                such as `not_found` or `validation_failed` — when the failure
                came from the domain rather than from parameter validation.
          required:
            - message
            - details
        status:
          type: integer
          description: HTTP status, repeated in the body.
        meta:
          $ref: '#/components/schemas/PreviewCommentResponseMeta'
      required:
        - error
        - status
        - meta
    PreviewComment:
      type: object
      description: A comment pinned to an element on a theme's preview.
      properties:
        run_id:
          type:
            - string
            - 'null'
          description: The associated Mist run, or null for unbound comments and replies.
        lock_version:
          type: integer
          minimum: 0
          description: >-
            Server record version used to order accepted status snapshots and
            guard changes.
        id:
          type: integer
          description: |-
            The discussion's row id. Served alongside `client_id` rather than
            in place of it: every route here still addresses a comment by
            `client_id`. This is the identity a company-scoped endpoint that is
            given no theme has to name a discussion by, because `client_id` is
            unique only within its theme — the task API's `source_thread_id`
            is this value.
        client_id:
          type: string
          description: Client-generated id, unique within the theme.
        'n':
          type: integer
          description: |-
            Display number shown on the pin. Assigned by the client, so it is a
            label rather than an identifier — address a comment by `client_id`.
        body:
          type: string
          description: The comment text.
        status:
          type: string
          description: |-
            Where the comment stands. `kept` is a legacy spelling that still
            counts as open. `handed` means it has been sent for work and is
            still running; `review` means the work finished and is waiting on a
            person. Only a person sets `skipped`, and only a person moves a
            comment past `review` — the agent stops there.

            When two writes carry the same stamp the order is
            `skipped` > `review` > `handed` > the incoming value, so a resolve
            is never lost to a same-millisecond write and finished work is never
            dragged back to in-progress.
          enum:
            - open
            - kept
            - skipped
            - handed
            - review
            - needs_info
        status_at_ms:
          type:
            - integer
            - 'null'
          description: |-
            When the status was last intentionally set, in milliseconds. The key
            the last-writer-wins register compares on. Null on comments
            predating the register.
        page_url:
          type: string
          description: Normalized path of the page the comment was pinned on.
        author_name:
          type:
            - string
            - 'null'
          description: Who pinned the comment, attributed by the server.
        created_at_ms:
          type: integer
          description: When the comment was created, in milliseconds since the epoch.
        template_id:
          type: integer
          description: |-
            The theme template the comment was pinned against. For a pin on
            shared chrome this is the navbar or footer template rather than the
            page's, because that is the one needing the edit.
        section:
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentThemeRef'
            - type: 'null'
          description: The section the pin sits in, when the render carried it.
        block:
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentThemeRef'
            - type: 'null'
          description: |-
            The block the pin sits in. Null unless the page was rendered by the
            theme editor, which is the only render that emits block identity.
        target:
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentTarget'
            - type: 'null'
          description: The anchored element.
        pin:
          $ref: '#/components/schemas/PreviewCommentPin'
        mentions:
          type: array
          description: People or projects mentioned in the comment.
          items:
            $ref: '#/components/schemas/PreviewCommentMention'
        mentioned_project:
          description: |-
            The project this comment is about, or null when it is about a page.
            Taken from the first project mentioned.
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentMentionedProject'
            - type: 'null'
        proof_baseline:
          description: |-
            The page before the work started, or null when no capture has been
            recorded. Carries its own stamp, resolved independently of status.
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentProofBaseline'
            - type: 'null'
        work_item:
          description: |-
            The work this discussion belongs to, or null while it is only a
            discussion. Null too once that work is deleted, which gives the
            thread back to the legacy workflow path.
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentWorkItem'
            - type: 'null'
        board_position:
          type:
            - string
            - 'null'
          pattern: ^[0-9A-Za-z]+$
          maxLength: 255
          description: |-
            Where the comment's card sits on the Tasks board, as a canonical
            fractional index in base-62 digits that sorts as a plain string, in
            the same order work items keep. One order for the whole board;
            equal positions are ordered by id. Null until someone places the
            card. Not read for a comment that belongs to a work item, which is
            drawn as that work.
          example: a0V
        board_position_at_ms:
          type:
            - integer
            - 'null'
          description: >-
            When the card was last placed, in milliseconds since the epoch. The
            key the position register compares on.
          example: 1789000000000
        assignee:
          description: >-
            Who owns the work now, or null for nobody. Mist appears with `kind`
            `agent`.
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentPerson'
            - type: 'null'
        assigner:
          description: >-
            Who handed the comment over, or null until somebody has. Kept after
            the handover.
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentPerson'
            - type: 'null'
        assigned_at_ms:
          type:
            - integer
            - 'null'
          description: >-
            When the assignment was last set, in milliseconds since the epoch.
            The key the assignment register compares on.
        priority:
          type:
            - string
            - 'null'
          enum:
            - urgent
            - high
            - medium
            - low
            - null
          description: How urgent the comment is, or null when nobody has judged it.
        priority_at_ms:
          type:
            - integer
            - 'null'
          description: >-
            When the priority was last set, in milliseconds since the epoch. The
            key the priority register compares on.
        replies:
          type: array
          description: Replies, ordered by creation time then client id.
          items:
            $ref: '#/components/schemas/PreviewCommentReply'
      required:
        - id
        - run_id
        - client_id
        - 'n'
        - body
        - status
        - status_at_ms
        - lock_version
        - page_url
        - author_name
        - created_at_ms
        - template_id
        - section
        - block
        - target
        - pin
        - mentions
        - mentioned_project
        - proof_baseline
        - work_item
        - replies
    PreviewCommentResponseMeta:
      type: object
      description: Envelope metadata returned on every preview-comment response.
      properties:
        request_uuid:
          type:
            - string
            - 'null'
          description: Correlation id for this request, when one was assigned.
        timestamp:
          type: string
          format: date-time
          description: When the response was built.
      required:
        - request_uuid
        - timestamp
    PreviewCommentThemeRef:
      type: object
      description: |-
        Theme identity of the pinned element. `type` names the file — a section
        renders from `sections/<type>/index.liquid` — and `id` names which
        instance of it is on the page. More durable than the selector, which
        dies when markup changes.
      properties:
        id:
          type:
            - string
            - 'null'
          description: Identity of this instance in the template schema.
        type:
          type:
            - string
            - 'null'
          description: Type name
          which is the file.: null
      required:
        - id
        - type
    PreviewCommentTarget:
      type: object
      description: |-
        The element the comment was pinned to. Null when the click landed on
        nothing resolvable, which clients already tolerate.
      properties:
        tag:
          type:
            - string
            - 'null'
          description: Tag name of the element.
        selector:
          type:
            - string
            - 'null'
          description: CSS selector that located the element.
        text:
          type:
            - string
            - 'null'
          description: Text quoted from the element at pin time.
        html:
          type:
            - string
            - 'null'
          description: Truncated outer HTML of the element.
      required:
        - tag
        - selector
        - text
        - html
    PreviewCommentPin:
      type: object
      description: |-
        Where the pin sits. `x` and `y` are percentages of the viewport; `ox`
        and `oy` are the click's offset within the anchored element, from 0 to
        1, so two pins on one image stay apart. The offsets are null on
        comments made before they were recorded.
      properties:
        x:
          type: number
          description: Horizontal position
          as a percentage of the viewport.: null
        'y':
          type: number
          description: Vertical position
          as a percentage of the viewport.: null
        ox:
          type:
            - number
            - 'null'
          description: Horizontal offset within the element
          0 to 1.: null
        oy:
          type:
            - number
            - 'null'
          description: Vertical offset within the element
          0 to 1.: null
      required:
        - x
        - 'y'
        - ox
        - oy
    PreviewCommentMention:
      type: object
      description: |-
        One mention on a comment or reply. A person, or a project distinguished
        by a `project:` prefix on its id — `project:theme-873`. The prefix
        rather than a separate field, because the desktop stores both in one
        list and tells them apart the same way.
      properties:
        id:
          type: string
          description: The mentioned person's id, or `project:` plus the project's own id.
        name:
          type: string
          description: The name as it was written in the body.
        kind:
          type: string
          description: Present on a project mention — theme, portal, mist, or mysite.
        role:
          type: string
          description: Present on a person, when the roster reported one.
      required:
        - id
        - name
      additionalProperties: false
    PreviewCommentMentionedProject:
      type: object
      description: |-
        The project a comment is about, taken from the first project mentioned.
        Stored as its own columns so "which comments are about this project" is
        an indexed query rather than a scan of every comment's mentions.
      properties:
        kind:
          type: string
          description: theme, portal, mist, or mysite.
        ref:
          type: string
          description: The project's id within that kind.
      required:
        - kind
        - ref
      additionalProperties: false
    PreviewCommentProofBaseline:
      type: object
      description: |-
        The page before the work started, carried by the comment. Its
        `captured_at_ms` is its own register: it is compared only against a
        stored baseline's stamp, never against the status stamp.

        A present object with a null url means the capture was recorded but is
        not retrievable — which is not the same as no baseline, and must not be
        rendered as a broken image.
      properties:
        url:
          type:
            - string
            - 'null'
          description: Where to fetch the image.
        width:
          type:
            - integer
            - 'null'
          description: Intrinsic width in pixels.
        height:
          type:
            - integer
            - 'null'
          description: Intrinsic height in pixels.
        captured_at_ms:
          type: integer
          description: When the capture was taken, in milliseconds.
      required:
        - url
        - width
        - height
        - captured_at_ms
    PreviewCommentWorkItem:
      type: object
      description: |-
        The work a discussion belongs to. A routing and display hint and never
        the authority: it says which surface owns this thread's workflow, so a
        client sends the work rather than the comment, and the work's own read
        is what its card is drawn from. Read-only on this API — every write
        here is refused with 409 while the thread belongs to work.
      properties:
        id:
          type: string
          format: uuid
          description: The work item's id, as the task API addresses it.
        status:
          type: string
          enum:
            - open
            - kept
            - needs_info
            - handed
            - review
            - skipped
          description: >-
            Where the work stands. The comment's own `status` is not this and
            may be stale.
        priority:
          type:
            - string
            - 'null'
          enum:
            - urgent
            - high
            - medium
            - low
            - null
          description: How urgent the work is, or null when nobody has judged it.
        run_id:
          type:
            - string
            - 'null'
          description: The run that owns the work now, or null when none does.
        lock_version:
          type: integer
          minimum: 0
          description: >-
            The work's own version register, which is not the comment's and is
            never compared against it.
        assignee:
          description: Who owns the work now, or null for nobody.
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentPerson'
            - type: 'null'
      required:
        - id
        - status
        - priority
        - run_id
        - lock_version
        - assignee
    PreviewCommentPerson:
      type: object
      description: |-
        One person on a work item. Mist is a person here, with a fixed id and
        no user row of its own.
      properties:
        id:
          type: string
          description: The user id as a string, or `mist` for the agent.
        name:
          type:
            - string
            - 'null'
          description: The name the server attributed, falling back to the email address.
        kind:
          type: string
          enum:
            - user
            - agent
          description: Whether this is a person or the agent.
      required:
        - id
        - name
        - kind
    PreviewCommentReply:
      type: object
      description: One reply on a comment, from a person or from the agent.
      properties:
        run_id:
          type:
            - string
            - 'null'
          description: The associated Mist run, or null for unbound comments and replies.
        client_id:
          type: string
          description: Client-generated id, unique within the comment.
        body:
          type: string
          description: The reply text.
        author_name:
          type:
            - string
            - 'null'
          description: |-
            Who replied. The literal `Mist` for the agent's own replies, which
            is what a client compares against to tell whether a thread has been
            answered.
        created_at_ms:
          type: integer
          description: When the reply was created, in milliseconds since the epoch.
        mentions:
          type: array
          description: People or projects mentioned in the reply.
          items:
            $ref: '#/components/schemas/PreviewCommentMention'
        shot:
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentShot'
            - type: 'null'
          description: Proof screenshot, when the reply carries one.
        proof:
          oneOf:
            - $ref: '#/components/schemas/PreviewCommentProof'
            - type: 'null'
          description: The evidence around the screenshot, when the reply carries it.
      required:
        - run_id
        - client_id
        - body
        - author_name
        - created_at_ms
        - mentions
        - shot
        - proof
    PreviewCommentShot:
      type: object
      description: |-
        Proof screenshot attached to a reply. `url` is null when the image was
        recorded but is not retrievable, so a client must treat a present
        object with a null url as "no image to show" rather than an error.
      properties:
        url:
          type:
            - string
            - 'null'
          description: Where to fetch the image.
        width:
          type:
            - integer
            - 'null'
          description: Intrinsic width in pixels.
        height:
          type:
            - integer
            - 'null'
          description: Intrinsic height in pixels.
      required:
        - url
        - width
        - height
    PreviewCommentProof:
      type: object
      description: |-
        What a reviewer reads instead of taking "I fixed it" on trust: the
        route the change was measured on, the image it was compared against,
        what changed on disk, and the mechanical results.

        Every part is optional. A capture that did not happen is an ordinary
        outcome, and a reply explaining why there is no picture is worth more
        than a refused write.
      properties:
        route:
          type:
            - string
            - 'null'
          description: The page this was measured on.
        before:
          type:
            - object
            - 'null'
          description: >-
            The image the work was compared against — the comment's baseline as
            it stood when this reply was written, so replacing the baseline
            later does not change what this reply appears to have compared.
          properties:
            url:
              type:
                - string
                - 'null'
              description: Where to fetch the image.
          required:
            - url
        files:
          type: array
          description: Paths that changed on disk.
          items:
            type: string
        checks:
          type: array
          description: Mechanical results, each pass, warn or fail.
          items:
            $ref: '#/components/schemas/PreviewCommentProofCheck'
      required:
        - route
        - before
        - files
        - checks
    PreviewCommentProofCheck:
      type: object
      description: One mechanical result a reviewer reads alongside the images.
      properties:
        label:
          type: string
          description: What was checked.
        status:
          type: string
          enum:
            - pass
            - warn
            - fail
          description: How it came out.
        detail:
          type: string
          description: Why, when it did not pass.
      required:
        - label
        - status
  securitySchemes:
    bearer_auth:
      type: http
      scheme: bearer

````