---
id: w5/m4/t004
title: Lifecycle row actions (suspend/resume/restart) with poll-to-converge
worker: worker5
status: todo
estimate: 60m
depends_on: [w5/m4/t002]
---

## Objective

Add per-row lifecycle actions to the services list — suspend, resume, restart — wired to bex-api's Render-named mutations, with a confirm dialog, in-flight disabling, and polling of `phase`/`suspended` until the operator's async reconcile converges.

## Context

- bex-api GraphQL mutations (`docs/ADR006-bex-api.md`): `suspendService(id)`, `resumeService(id)`, `restartServer(id)` — each returns the updated service; the patch is accepted synchronously but the operator converges asynchronously, so the client polls `GET`/`server` for `suspended`/`phase`.
- Render's verb semantics: suspend → `not_suspended`→`suspended`; restart stamps `restartedAt`.
- shadcn dialog + dropdown primitives under `src/common/components/ui/`.

## Steps

1. Add `suspendService`/`resumeService`/`restartServer` mutations to `src/features/services/api/services.graphql`; regenerate codegen.
2. Add a row action menu; gate destructive verbs behind a confirm dialog.
3. On success, poll the `services`/`server` query (or refetch) until `phase`/`suspended` reflects the new state; disable the action while in flight.
4. Surface errors via the toaster; keep strings in the `services.*` locale.

## Files

- `src/features/services/api/services.graphql`
- `src/graphql/definitions.ts` (regenerated)
- `src/routes/index.tsx` (+ any row-action component under `src/features/services/components/`)
- `src/features/services/locales/{en,zh}.ts`

## Acceptance criteria

- [ ] Each row exposes suspend/resume/restart via the Render-named mutations.
- [ ] Destructive actions confirm first; the control is disabled while in flight.
- [ ] After a mutation, the row's status badge converges via polling (not a stale optimistic value only).

## Out of scope

- Service-detail page and header actions (m5).
