#10 lägger till backend för drift och merge: beräkning av drift, "restore desired", "accept running", förslag om flyttade services och merge. UI:t ingår inte där, eftersom sidorna skrivs om enligt Ledger-designen i #18. Den här issuen gör funktionerna synliga och klickbara i Ledger-stil.
Beror på #10 och #18. Påbörjas när båda är mergade.
POST /services/{id}/restore startar ett manuellt jobb till desired_ref och redirectar till jobbet.
POST /services/{id}/accept kräver formulärfältet actual={{.Service.RunningRef}} (det pinnade värdet som kommer att skrivas, inklusive digest). Fältet är bara ett skydd mot att acceptera en version användaren aldrig såg, och värdet som skrivs räknas alltid fram från raden.
POST /services/{id}/merge: {id} är den levande raden och formulärfältet from är den borttagna raden. Svaret redirectar till /services/{from}, eftersom den gamla raden behåller sitt ID.
Alla tre returnerar 409 och renderar service.html med .Error när åtgärden vägras.
accept stödjer return=overview, som setMode.
Fält i pageData:
RestoreBlocked string
MoveFrom *model.Move och MoveTo *model.Move. Move har FromID, ToID, FromAgent och ToAgent.
MergeSources []model.Service
Översikten behöver kopplas in:serviceData ska fylla en map[int64]model.Move från store.MoveCandidates, med ToID som nyckel. Det sköts medvetet inte i #10, eftersom #18 skriver om serviceData/groupServices.
Ändras inte:
CSRF på alla POST.
Inget template.HTML på värden som användare eller agenter styr. Refs och agentnamn är agentstyrda.
htmx/SSE som i #18: efter accept och merge publiceras events.Services, och efter merge även events.Jobs.
Scope
Ingår:
Drift-badge, restore och accept, flyttförslag med Merge, manuell merge.
Övriga badges och knappar i Ledger-stil från fas 2–4 (#18 eller egen issue).
Klart när:
Drift, restore, accept och merge går att utföra från UI:t.
Det är verifierat i webbläsare i båda teman, på desktop och vid 390 px bredd.
go test ./... passerar.
## Beskrivning
#10 lägger till backend för drift och merge: beräkning av drift, "restore desired", "accept running", förslag om flyttade services och merge. UI:t ingår inte där, eftersom sidorna skrivs om enligt Ledger-designen i #18. Den här issuen gör funktionerna synliga och klickbara i Ledger-stil.
Beror på #10 och #18. Påbörjas när båda är mergade.
## Design
Ledger-designen i Claude Design: https://claude.ai/artifact/8ARU9MzvDVxWC3VPmdgix8. Den har badgen för drift, som #18 uttryckligen lämnade till fas 5.
- **Services (overview):**
- En drift-badge på rader där `.Service.Drifted` är true.
- "Moved from A?" på den nya raden när ett flyttförslag finns, med en Merge-knapp.
- **Service-sidan:**
- Vid drift: desired- och running-ref sida vid sida, med knapparna "Restore desired" och "Accept running".
- Om restore inte är tillåten (`RestoreBlocked` är satt): knappen inaktiverad, med anledningen i `title` eller i text.
- "Moved from A?" med Merge på en levande rad (`MoveFrom`). "Moved to B" med länk på en borttagen rad (`MoveTo`).
- En manuell merge: en väljare över `MergeSources` ("Merge history from…") med en bekräftelseruta i popover, som Revoke i #18.
## Tekniska noteringar
- **Backend från #10:**
- `POST /services/{id}/restore` startar ett manuellt jobb till `desired_ref` och redirectar till jobbet.
- `POST /services/{id}/accept` kräver formulärfältet `actual={{.Service.RunningRef}}` (det pinnade värdet som kommer att skrivas, inklusive digest). Fältet är bara ett skydd mot att acceptera en version användaren aldrig såg, och värdet som skrivs räknas alltid fram från raden.
- `POST /services/{id}/merge`: `{id}` är den levande raden och formulärfältet `from` är den borttagna raden. Svaret redirectar till `/services/{from}`, eftersom den gamla raden behåller sitt ID.
- Alla tre returnerar 409 och renderar `service.html` med `.Error` när åtgärden vägras.
- `accept` stödjer `return=overview`, som `setMode`.
- **Fält i `pageData`:**
- `RestoreBlocked string`
- `MoveFrom *model.Move` och `MoveTo *model.Move`. `Move` har `FromID`, `ToID`, `FromAgent` och `ToAgent`.
- `MergeSources []model.Service`
- **Översikten behöver kopplas in:** `serviceData` ska fylla en `map[int64]model.Move` från `store.MoveCandidates`, med `ToID` som nyckel. Det sköts medvetet inte i #10, eftersom #18 skriver om `serviceData`/`groupServices`.
- **Ändras inte:**
- CSRF på alla POST.
- Inget `template.HTML` på värden som användare eller agenter styr. Refs och agentnamn är agentstyrda.
- htmx/SSE som i #18: efter accept och merge publiceras `events.Services`, och efter merge även `events.Jobs`.
## Scope
**Ingår:**
- Drift-badge, restore och accept, flyttförslag med Merge, manuell merge.
- Inkopplingen av översikten ovan.
- Webbtester för de nya fragmenten.
**Ingår inte:**
- Ändringar i drift- eller merge-logiken (#10).
- Övriga badges och knappar i Ledger-stil från fas 2–4 (#18 eller egen issue).
**Klart när:**
- Drift, restore, accept och merge går att utföra från UI:t.
- Det är verifierat i webbläsare i båda teman, på desktop och vid 390 px bredd.
- `go test ./...` passerar.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Beskrivning
#10 lägger till backend för drift och merge: beräkning av drift, "restore desired", "accept running", förslag om flyttade services och merge. UI:t ingår inte där, eftersom sidorna skrivs om enligt Ledger-designen i #18. Den här issuen gör funktionerna synliga och klickbara i Ledger-stil.
Beror på #10 och #18. Påbörjas när båda är mergade.
Design
Ledger-designen i Claude Design: https://claude.ai/artifact/8ARU9MzvDVxWC3VPmdgix8. Den har badgen för drift, som #18 uttryckligen lämnade till fas 5.
.Service.Driftedär true.RestoreBlockedär satt): knappen inaktiverad, med anledningen ititleeller i text.MoveFrom). "Moved to B" med länk på en borttagen rad (MoveTo).MergeSources("Merge history from…") med en bekräftelseruta i popover, som Revoke i #18.Tekniska noteringar
POST /services/{id}/restorestartar ett manuellt jobb tilldesired_refoch redirectar till jobbet.POST /services/{id}/acceptkräver formulärfältetactual={{.Service.RunningRef}}(det pinnade värdet som kommer att skrivas, inklusive digest). Fältet är bara ett skydd mot att acceptera en version användaren aldrig såg, och värdet som skrivs räknas alltid fram från raden.POST /services/{id}/merge:{id}är den levande raden och formulärfältetfromär den borttagna raden. Svaret redirectar till/services/{from}, eftersom den gamla raden behåller sitt ID.service.htmlmed.Errornär åtgärden vägras.acceptstödjerreturn=overview, somsetMode.pageData:RestoreBlocked stringMoveFrom *model.MoveochMoveTo *model.Move.MoveharFromID,ToID,FromAgentochToAgent.MergeSources []model.ServiceserviceDataska fylla enmap[int64]model.Movefrånstore.MoveCandidates, medToIDsom nyckel. Det sköts medvetet inte i #10, eftersom #18 skriver omserviceData/groupServices.template.HTMLpå värden som användare eller agenter styr. Refs och agentnamn är agentstyrda.events.Services, och efter merge ävenevents.Jobs.Scope
Ingår:
Ingår inte:
Klart när:
go test ./...passerar.