# standup_save

Create or Update Standup Entry

## Beschreibung

Linear-style upsert for a daily standup entry (Yesterday /
Today / Blockers). Omit `id` to upsert by `(user, account,
report_date)` — the same shape the web form uses. Pass `id`
(sup_…) to update a specific entry.

- `report_date` defaults to today. One entry per (user, date).
- `submit: false` (the default) leaves the entry in draft
  (completed_at IS NULL) so a human can review in the UI
  before submitting. Set `submit: true` to submit immediately;
  on an already-submitted entry, `submit: true` is a no-op
  (completed_at is preserved).
- At least one of `previous` / `next` / `blockers` must be
  non-blank across create + update combined (matches the
  model's `present_content` validation).
- `previous_completed` is the "yesterday's plan finished?"
  carryover flag surfaced by `standup_list`. Pass `true` /
  `false` explicitly; omit to leave unchanged.

## Body format

Bodies are stored as Markdown and rendered through the
Standups account pipeline (ENG-1387). Write bullet lists;
the renderer decorates each line with a coloured status
pill when it ends with `result: <slug>`. Ticket keys like
`ENG-1234` are autolinked automatically — do not wrap them
in Markdown links yourself.

Recognised status slugs (case-insensitive):
`done` (also: ok, completed, shipped, landed),
`in progress` (wip, ongoing, started, working),
`in review` (review, reviewing, pr),
`blocked` (blocker, waiting, stuck),
`postponed` (on hold, paused, deferred, delayed, later),
`cancelled` (canceled, dropped, abandoned, skipped).
Anything else falls back to a neutral `planned` pill.

Example `previous` body:

    - Wrapped up ENG-1387 rendering pipeline, result: done
    - Reviewed PR for ENG-1400, result: in review
    - Waiting on staging deploy for ENG-1234, result: blocked

Plain paragraphs and lists without `result: …` render fine
too — they just skip the pill decoration.

## Access (StandupEntryPolicy)

- Regular members can only save their own entry — a `user_id`
  for someone else is silently forced back to the caller.
- Admin / employer can `user_id` to any active member.

## Zugriff

Zugriffsprofil: `gated`. Module: `standups`.
Die Freigabe im Profil ersetzt keine Account-, Workspace- oder Rollenberechtigung.

## Parameter

| Name | Typ | Pflicht | Beschreibung |
| --- | --- | --- | --- |
| `account_id` | string | Ja |  |
| `id` | string | Nein | StandupEntry prefix ID (sup_…). Omit to upsert by (user, report_date). |
| `user_id` | string | Nein | User prefix (user_…) or @handle. Admin / employer only; regular members always save their own row. |
| `report_date` | string | Nein | YYYY-MM-DD. Defaults to today. |
| `previous` | string / null | Nein | Yesterday's work / outcomes. |
| `next` | string / null | Nein | Today's plan. |
| `blockers` | string / null | Nein | Blockers. Non-blank flips derived `blocked` on. |
| `previous_completed` | boolean / null | Nein | User confirmed yesterday's plan finished. |
| `submit` | boolean | Nein | true = submit now; false (default) = leave as draft. |

## Eingabeschema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "account_id": {
      "type": "string"
    },
    "id": {
      "type": "string",
      "description": "StandupEntry prefix ID (sup_…). Omit to upsert by (user, report_date)."
    },
    "user_id": {
      "type": "string",
      "description": "User prefix (user_…) or @handle. Admin / employer only; regular members always save their own row."
    },
    "report_date": {
      "type": "string",
      "format": "date",
      "description": "YYYY-MM-DD. Defaults to today."
    },
    "previous": {
      "type": [
        "string",
        "null"
      ],
      "description": "Yesterday's work / outcomes."
    },
    "next": {
      "type": [
        "string",
        "null"
      ],
      "description": "Today's plan."
    },
    "blockers": {
      "type": [
        "string",
        "null"
      ],
      "description": "Blockers. Non-blank flips derived `blocked` on."
    },
    "previous_completed": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "User confirmed yesterday's plan finished."
    },
    "submit": {
      "type": "boolean",
      "description": "true = submit now; false (default) = leave as draft."
    }
  },
  "required": [
    "account_id"
  ],
  "type": "object"
}
```

## Ausgabeschema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "id": {
      "type": "string"
    },
    "user": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "handle": {
          "type": "string"
        },
        "name": {
          "type": "string"
        }
      }
    },
    "report_date": {
      "type": "string",
      "format": "date"
    },
    "previous": {
      "type": [
        "string",
        "null"
      ]
    },
    "next": {
      "type": [
        "string",
        "null"
      ]
    },
    "blockers": {
      "type": [
        "string",
        "null"
      ]
    },
    "blocked": {
      "type": "boolean"
    },
    "previous_completed": {
      "type": [
        "boolean",
        "null"
      ]
    },
    "submitted": {
      "type": "boolean"
    },
    "completed_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "created": {
      "type": "boolean",
      "description": "true iff this call inserted a fresh row (as opposed to updating an existing draft/submitted one)."
    }
  },
  "required": [
    "id",
    "user",
    "report_date",
    "blocked",
    "submitted",
    "created"
  ],
  "type": "object"
}
```
