Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-3346Done

motir-core: the retire control in the list and detail, honouring the page-state contract

The control itself, in the treatment the design already drew — the sibling story rendered the retired state, this card wires the action that produces it.

Retire and un-retire from the same place, in the list row and in the detail view.

The page-state contract, and it has two halves here — the recurring bug is assuming router.refresh() covers both:

  • The row's own state — the mutation response IS the confirmation. Keep the optimistic value; do not refresh it, because a refresh re-reads and produces a visible revert.
  • Anything server-rendered elsewhere on the surface — a count of active lessons, a summary line beside the setting — needs router.refresh(), which is the only thing that reaches it.

A mutation touching both does both.

Show who and when. Once retired, the row and the detail say who switched it off and when, from the data the write recorded.

Refusals are rendered, not thrown. The route returns distinguishable refusals; a global lesson or a stale row should produce a sentence a person can act on, not a toast saying something went wrong.

No new permission logic in the client. The control's presence follows what the server said the user can do; it is not a second implementation of the rule.

⚠️ Amendment (2026-08-23) — Apply again appears on BOTH not-applied rows

Authored 2026-08-21; the parent story MOTIR-3330's 2026-08-23 amendment outranks this card's original wording, and it matches what the design already drew.

design/ai-settings/design-notes.md §L6 draws two not-applied rows — Not applied (switched off) and Not seen in 90 days (aged out) — and gives both the action Apply again. The control therefore does not branch on which badge it sits under: it posts apply, and the server decides whether that means "clear the retirement" or "exempt this row from the clock". On an aged-out row the badge disappears on success, because the sentence it makes is no longer true of what the planner is being told.

Copy is already fixed by §L9 — row action Stop applying / Apply again, badges Not applied / Not seen in {n} days, detail action Stop applying this lesson, and the a11y name Stop applying {takeaway} (§L11). No new primitive (§L8).

Acceptance criteria

  • Retire and un-retire work from both the list row and the detail view, in the design's treatment.
  • The acted-on row keeps its optimistic state and is not refreshed into a revert.
  • Any server-rendered summary on the surface refreshes; both halves asserted.
  • The retired row shows the actor and the timestamp.
  • Each typed refusal renders as its own message.
  • A user without the retire permission sees no control, and the route refuses independently.
  • Copy in en and zh; the catalog-parity gate passes.
  • A "How to test" section in the PR body — route, role precondition, the seed that produces a retirable lesson, and what to click and see including a refusal case.

Context refs

  • motir-core design/ai-settings/ — the retired treatment and affordance, already drawn.
  • motir-core app/(authed)/settings/project/ai-planning/ — the surface and its i18n namespace.
  • MOTIR-3338 — the list and detail this adds the action to.
  • MOTIR-3345 — the route, its guard and its typed refusals.