# Feature 6 – Drag-and-drop ## Status Färdig och mergad till main. ## Bakgrund Feature 5 införde backendstyrda statusövergångar mellan `WAITING`, `IN_PROGRESS` och `COMPLETED`, ett särskilt status-API samt ett första knappbaserat gränssnitt för statusändring. Feature 6 ska komplettera detta med drag-and-drop mellan brädans statuskolumner. Featuren ska återanvända den befintliga statusmodellen, status-API:t, tilldelningsreglerna och frontendens hantering av vänteläge och lokala fel. Den ska inte skapa en parallell statusmekanism. ## Mål Feature 6 ska: - låta användaren dra uppgiftskort mellan statuskolumner; - använda status-API:t från Feature 5; - ge omedelbar visuell återkoppling genom optimistisk flytt; - återställa kortet om backend avvisar statusändringen; - hantera automatisk tilldelning när en otilldelad uppgift dras till Pågående; - behålla befintliga statusknappar som ett tillfälligt alternativ; - fungera med mus och på en rimlig nivå med touch; - hålla dragmekanik, statuslogik och kortlayout tillräckligt separerade för framtida ändringar. ## Omfattning Feature 6 är primärt en frontendfeature. Backendens befintliga endpoint används: ```http PUT /api/tasks/{taskId}/status ``` Requesten innehåller målstatus och kan innehålla aktiv användares id: ```json { "status": "IN_PROGRESS", "activeUserId": "d56b54dd-31b0-4d71-8a10-82464be59a61" } ``` Backend ska endast ändras om granskning av den faktiska implementationen visar att en mindre korrigering behövs för att det befintliga kontraktet ska kunna återanvändas korrekt. ## Implementerad lösning Frontend använder `@dnd-kit/react` 0.5.0 som aktuell React-adapter och `@dnd-kit/dom` 0.5.0 för en konfigurerad pointer-sensor. Legacy-paketen `@dnd-kit/core` och `@dnd-kit/sortable` används inte. Sortable-stöd behövs inte eftersom kort inte ordnas inom kolumner. `TaskDragAndDrop.tsx` avgränsar bibliotekskopplingen. Adaptern: - registrerar uppgiftskort som draggable och statuskolumner som droppable; - översätter ett lyckat drop-event till task-id och målstatus; - ignorerar avbrutna dragningar och ogiltiga mål; - använder sex pixlars aktiveringsavstånd för mus och penna; - använder 250 millisekunders fördröjning med åtta pixlars tolerans för touch; - behåller dnd-kits standardplugins och tangentbordssensor; - förlitar sig på sensorns standardskydd mot dragstart från interaktiva element. Ingen särskild `touch-action`, collision detector eller drag-overlay har lagts till. Den befintliga responsiva layouten är oförändrad. `TaskBoard` har fortsatt en gemensam requestväg och låsning per task-id för statusändringar. Ett presentationsval avgör beteendet: - statusknappar använder `server-confirmed` och flyttar inte kortet före svar; - drag-and-drop använder `optimistic`, sparar hela tidigare uppgiften och flyttar kortet direkt; - lyckade anrop ersätter uppgiften med hela serverresponsen; - misslyckade optimistiska anrop återställer hela rollback-värdet och visar felet lokalt. När en otilldelad uppgift dras till `IN_PROGRESS` visar det optimistiska värdet den aktiva användarens id och namn. En befintlig ansvarig behålls. Backendens befintliga status-API och Feature 5-regler återanvänds utan backendändringar. Frontendens 44 tester, TypeScript-kompilering, Vites produktionsbygge och `git diff --check` passerar på feature-branchen. Manuell browserverifiering av de centrala drag-, server- och rollbackflödena är genomförd. ## Avgränsningar Feature 6 ska inte införa: - en ny statusmodell; - ett nytt eller parallellt status-API; - manuell sortering inom en kolumn; - persistent kortordning; - generell redigering av uppgifter; - radering; - deadlines; - återkommande uppgifter; - status- eller poänghistorik; - undo-funktion; - flera ansvariga; - autentisering eller behörigheter; - realtidsuppdatering mellan browsers; - en ny global state-lösning; - horisontell scrollning som ett särskilt mobilkoncept; - ett specialbyggt tangentbordsflöde för drag-and-drop. Att flytta ett kort inom samma kolumn ska inte ändra någon ordning och ska inte ge ett backend-anrop. ## Befintliga statusregler Feature 6 ska behandla följande regler som redan beslutade: - statusarna är `WAITING`, `IN_PROGRESS` och `COMPLETED`; - alla direkta statusövergångar är tillåtna; - samma målstatus är giltig och idempotent; - `IN_PROGRESS` kräver ansvarig; - `WAITING` och `COMPLETED` får vara otilldelade; - när en otilldelad uppgift sätts till `IN_PROGRESS` skickar frontend aktiv användares id; - backend tilldelar då användaren och ändrar status atomärt; - om uppgiften redan har en ansvarig behålls denna; - statusoperationen byter aldrig en befintlig ansvarig; - backend returnerar hela den uppdaterade uppgiften; - serverns fullständiga task-respons är slutlig sanning. ## Uppdateringsstrategi Drag-and-drop använder en kontrollerad optimistisk uppdatering. När ett kort släpps i en annan statuskolumn ska frontend: 1. spara hela den nuvarande task-versionen som rollback-värde; 2. skapa ett optimistiskt lokalt task-läge; 3. visa kortet omedelbart i målkolumnen; 4. markera kortet som upptaget; 5. skicka statusanropet; 6. vid framgång ersätta den optimistiska uppgiften med serverns fullständiga respons; 7. vid fel återställa hela den tidigare task-versionen; 8. visa felet lokalt på det återställda kortet. Rollback ska återställa hela uppgiften, inte enbart statusfältet. Detta är viktigt eftersom en optimistisk flytt till `IN_PROGRESS` även kan innehålla en tillfällig antagen ansvarig. Ingen generell infrastruktur för optimistiska uppdateringar ska införas. Lösningen ska hållas lokal till uppgiftslistan och drag-and-drop-flödet. Feature 5:s statusknappar ska fortsatt vara serverbekräftade: kortet ligger kvar i sin aktuella kolumn tills backend svarar. ## Otilldelad uppgift till Pågående När en otilldelad uppgift dras till `IN_PROGRESS` ska frontend: - skicka målstatus `IN_PROGRESS`; - skicka aktiv användares id; - optimistiskt visa aktiv användare som ansvarig; - låta backend tilldela användaren och ändra status i samma transaktion. Om uppgiften redan har en ansvarig ska frontend inte optimistiskt ersätta denna med den aktiva användaren. Serverresponsen ersätter alltid det optimistiska antagandet. Drag-and-drop ska inte visa ett nytt ansvarigval. ## Drag-and-drop-bibliotek Feature 6 ska använda dnd-kit-ekosystemets aktuella stabila React-lösning. Featuredokumentet låser inte exakta paket eller API:n. Codex ska vid implementation verifiera den aktuella officiella dokumentationen, vilka paket som är aktuella respektive legacy, kompatibilitet med repositoryts React-version samt påverkan på Vitest/jsdom och React Testing Library. Den valda lösningen ska stödja: - draggable-kort; - droppable-statuskolumner; - pointer- och touchinteraktion; - aktiveringsvillkor; - avbruten dragning; - identifiering av målkolumn. Biblioteket ska inte användas för: - sortering inom kolumner; - persistent ordning; - egen domänmodell; - generell state-hantering; - parallell statuslogik. Dragbibliotekets händelser ska översättas till en gemensam, bibliotekoberoende ingång till statusflödet. ## Statusknapparnas roll Feature 5:s statusknappar behålls tills vidare som ett fullt fungerande alternativ. De betraktas inte som en permanent del av målbilden. Den ursprungliga visuella designen innehåller inte statusknappar, och de ska därför enkelt kunna tas bort efter utvärdering. Implementation ska följa dessa principer: - drag-and-drop får inte byggas ovanpå knappkomponenterna; - draglogik får inte placeras i knapparna; - statuslogik får inte dupliceras mellan knappar och drag-and-drop; - båda ska dela underliggande requestlogik, låsning per task-id, felhantering och ersättning med serverrespons; - drag-and-drop använder optimistisk visuell flytt, medan statusknapparna förblir serverbekräftade och låter kortet ligga kvar tills backend svarar; - den visuella presentationsstrategin får därför skilja sig mellan interaktionerna; - vänteläge, felhantering och serverrespons ska höra till uppgiften och det delade statusflödet, inte dupliceras per kontroll; - kortlayouten får inte strukturellt förutsätta att knapparna alltid finns. Att ta bort statusknapparna senare ska inte kräva ändringar i status-API, dragmekanik, rollback eller task-state. Statusarkitekturen får inte vara kopplad till att knapparna finns kvar. ## Vänteläge När statusanropet pågår ska det optimistiskt flyttade kortet tonas ned lätt. Ingen text som `Flyttar…` och ingen spinner krävs i första versionen. Under vänteläget ska just detta kort inte kunna: - dras igen; - initiera en ny statusändring; - ändra ansvarig. Övriga kort ska förbli interaktiva. Flera olika kort får ha statusanrop pågående samtidigt. Låsning, nedtoning, rollback och fel hanteras per task-id. ## Fel och återställning Vid ett misslyckat statusanrop ska: - hela den tidigare task-versionen återställas; - kortet återgå till ursprungskolumnen; - tidigare ansvarig återställas; - nedtoningen tas bort; - felet visas lokalt på kortet; - målkolumnen inte behålla någon tillfällig markering. Tidigare klientdata ska inte delvis blandas med det misslyckade optimistiska tillståndet. Ett fel på ett kort ska inte blockera resten av brädan. ## Serverrespons Vid lyckat statusanrop ska frontend alltid ersätta den optimistiska uppgiften med hela serverresponsen. Det gäller även om serverresponsen skiljer sig från frontendens antagande vad gäller exempelvis: - status; - ansvarig; - andra returnerade task-fält. Servern är slutlig sanning. ## Dragyta Kortets icke-interaktiva yta ska fungera som dragyta. Knappar, select och andra interaktiva element ska inte initiera dragning. Implementation får avgöra sensoruppsättning, aktiveringströskel samt avstånd, fördröjning och tolerans. Vanliga klick och små fingerrörelser får inte oavsiktligt starta dragning, och kortets interaktiva kontroller ska fungera normalt. Dragmekaniken ska implementeras så att ett separat draghandtag senare kan införas utan att statusoperation, API-anrop, rollback eller task-state behöver ändras. Det bör räcka att flytta bibliotekets draglisteners och tillhörande attribut från kortets rot till handtaget. ## Målkolumner Kolumnerna behöver ingen stark eller permanent drop-markering. Den kolumn som kortet befinner sig över kan vid behov få en mycket diskret hover-effekt, exempelvis: - en svag bakgrundsförändring; - en tunn kant; - annan lågmäld visuell återkoppling. Feature 6 ska inte införa stora färgade drop-zoner eller en generell redesign av brädan. Om manuell verifiering visar att ingen markering behövs kan även den diskreta hover-effekten utelämnas. ## Drop i samma kolumn Om ett kort släpps i kolumnen som motsvarar dess nuvarande status ska operationen vara no-op. Det innebär: - inget API-anrop; - ingen statusändring; - ingen ändring av ordning; - inget vänteläge; - inget felmeddelande. ## Avbruten dragning Om en dragning avbryts eller avslutas utanför en giltig målkolumn ska operationen vara no-op. Kortet ska återgå till sin normala position utan API-anrop eller felindikering. ## Desktop och touch Desktop är det primära användningsfallet för Feature 6. Touch ska fungera på en rimlig grundnivå, men featuren ska inte införa en särskild mobil Kanban-design. Brädans befintliga responsiva layout ska behållas. Feature 6 ska inte införa horisontell scrollning. På smala skärmar får kolumnerna fortsätta använda repositoryts nuvarande responsiva layout, även om de staplas vertikalt. Implementation får avgöra sensoruppsättning, aktiveringsvillkor, eventuell `touch-action`, collision detection och drag-overlay. Valen måste bevara normal vertikal scrollning på mobil och får inte göra interaktiva kortkontroller svåranvända. Draglogiken ska: - identifiera mål genom status, inte genom en fast skärmposition; - inte förutsätta att kolumnerna ligger horisontellt; - hållas separerad från layout-CSS; - kunna fortsätta fungera om kolumnlayouten senare ändras. Om manuell verifiering visar att dragning mellan staplade kolumner fungerar dåligt kan mobilbeteendet ändras senare utan att status- eller rollbacklogiken görs om. Statusknapparna finns kvar som alternativ, särskilt där dragning är opraktisk. ## Tangentbord och tillgänglighet Tangentbordsstyrd drag-and-drop ingår inte som grundkrav i Feature 6. Statusknapparna ska fortsatt ge en fungerande tangentbordsväg för statusändring. Drag-and-drop får därför inte vara den enda möjliga vägen. Feature 6 ska ändå uppfylla grundläggande tillgänglighetskrav: - interaktiva kontroller ska fortsatt gå att nå med tangentbord; - ett upptaget kort ska inte kunna aktiveras igen; - fokus ska inte tappas oförklarligt efter lyckad operation eller rollback; - begripliga etiketter ska bevaras; - dragbibliotekets standard-ARIA får användas; - ingen omfattande speciallösning för tangentbordsdragning ska byggas. Förbättrat tangentbordsstöd för själva dragningen kan införas i en senare uppdatering. ## Gemensamt statusflöde Frontend ska ha ett gemensamt, kontrolloberoende statusflöde för den underliggande statusändringen. Det delade flödet ska ansvara för: - kontroll av pågående operation för task-id; - requestformat; - aktiv användares id vid behov; - per-kort-vänteläge; - ersättning med serverrespons; - lokalt statusfel. Drag-and-drop-flödet ska därutöver beräkna den optimistiska task-versionen, spara rollback-värdet och återställa hela den tidigare uppgiften vid fel. Statusknapparna ska inte göra en optimistisk flytt. Drag-and-drop och statusknappar är separata sätt att ange målstatus till det delade flödet, men får använda olika visuell presentationsstrategi. Tilldelningskontrollen ska återanvända samma per-kort-låsning så att status och tilldelning inte kan ändras samtidigt på samma uppgift. ## Frontendtester Frontendtesterna verifierar beteende och state utan att förutsätta fysisk layout eller exakta pointer-koordinater i jsdom. Dragadaptern mockas i brädtesterna, medan den bibliotekoberoende mappningen från draghändelse till task-id och målstatus testas separat. Testerna täcker bland annat: - korrekt statusanrop och optimistisk flytt till en annan kolumn; - låsning och nedtoning per task-id medan anropet pågår; - att andra kort kan ha samtidiga operationer; - automatisk optimistisk tilldelning till aktiv användare; - att en befintlig ansvarig behålls; - att hela serverresponsen ersätter det optimistiska värdet; - fullständig rollback och lokalt fel vid misslyckande; - no-op för samma status, avbruten dragning och ogiltigt mål; - att statusknapparnas befintliga serverbekräftade beteende är bevarat. Totalt passerar 44 frontendtester. TypeScript-kompileringen och Vites produktionsbygge passerar också. ## Backendtester Backend ändrades inte. Feature 5:s befintliga tester fortsätter därför att utgöra verifiering av: - direkta statusövergångar; - idempotens; - automatisk tilldelning; - bevarad befintlig ansvarig; - statusvalidering; - statusberoende tilldelningsregler; - oförändrade övriga task-fält. ## Manuell verifiering Manuell browserverifiering är genomförd. Följande verifierades: - drag-and-drop mellan statuskolumner fungerar; - kortet flyttas optimistiskt och tonas ned under statusanropet; - en otilldelad uppgift som flyttas till Pågående får aktiv användare som ansvarig; - en befintlig ansvarig behålls; - serverns svar ersätter det optimistiska värdet; - statusknapparna fungerar fortsatt; - ett blockerat statusanrop visar `Det gick inte att ändra status. Försök igen.`; - kortet återställs till ursprungskolumnen vid fel; - tidigare ansvarig återställs, nedtoningen försvinner och kortet blir interaktivt igen; - övriga kort förblir interaktiva under operationen. ## Dokumentation Feature 6 ska dokumenteras i: ```text docs/features/006-task-drag-and-drop.md ``` Vid implementation ska även följande uppdateras när det är relevant: ```text README.md docs/architecture.md docs/roadmap.md ``` Roadmapen markerar Feature 6 som `Klar` efter verifiering och merge. Ett nytt ADR behövs endast om biblioteksvalet bedöms vara ett övergripande, långlivat frontendbeslut som påverkar fler delar av applikationen än Feature 6. Om `dnd-kit` endast används lokalt för denna feature bör beslutet normalt dokumenteras i feature- och arkitekturdokumentationen. ## Acceptanskriterier Feature 6 är klar när: - kort kan dras mellan olika statuskolumner; - dragningen använder Feature 5:s status-API; - kortet flyttas optimistiskt till målkolumnen; - kortet tonas ned under serveranropet; - samma kort är låst för status, dragning och tilldelning under anropet; - andra kort förblir interaktiva; - otilldelad uppgift till Pågående använder aktiv användares id; - befintlig ansvarig behålls; - serverresponsen ersätter det optimistiska tillståndet; - fel återställer hela tidigare task-versionen; - kortet återgår till ursprungskolumnen vid fel; - felet visas lokalt på kortet; - drop i samma kolumn är no-op; - avbruten dragning är no-op; - ingen manuell eller persistent kortordning har införts; - statusknapparna fungerar fortsatt men är arkitekturellt frikopplade; - statusknapparna förblir serverbekräftade medan drag-and-drop är optimistisk; - statusknapparna kan tas bort senare utan att drag- eller statuslogiken byggs om; - kortets icke-interaktiva yta fungerar som dragyta; - interaktiva kortkontroller initierar inte dragning; - ett senare draghandtag kan införas utan ändring av statusflödet; - normal vertikal scrollning på mobil bevaras; - ingen horisontell scrollning har införts; - desktop fungerar väl; - touch fungerar på rimlig grundnivå; - tangentbordsdragning inte krävs; - statusändring fortsatt är möjlig med tangentbord genom statusknapparna; - frontendtesterna täcker centrala stateövergångar och felfall; - manuell verifiering täcker dragkänsla, touch, layout och rollback; - backend är oförändrad om inget konkret behov av justering hittas; - relevant dokumentation är uppdaterad. ## Implementationsprinciper Vid implementationen användes följande dokumentation som tekniskt underlag: ```text AGENTS.md README.md docs/architecture.md docs/development.md docs/roadmap.md docs/decisions/ docs/features/004-task-assignment.md docs/features/005-task-status.md ``` Relevant frontendkod och tester granskades särskilt avseende: - aktuell React-version; - frontendens installerade beroenden; - aktuell officiell dnd-kit-dokumentation; - vilka dnd-kit-paket som är aktuella respektive legacy; - dnd-kit-lösningens kompatibilitet med React-versionen; - påverkan på Vitest/jsdom och React Testing Library; - aktuell task-typ; - brädans kolumnstruktur; - uppgiftskortets komponentstruktur; - befintliga statusknappar; - statusanropets requestformat; - hur aktiv användare representeras; - hur tasks ersätts i state; - det gemensamma vänteläget per kort; - status- och tilldelningsfel; - tilldelningskontrollens inaktiveringslogik; - aktuell teststil; - vilka pointer- och draghändelser testmiljön stödjer. Repositoryts faktiska kod och dokumentation har företräde framför antaganden i denna featurebeskrivning. Kod, tester och relevant dokumentation ska uppdateras tillsammans. Codex ska inte committa, pusha, skapa pull request eller merga utan uttrycklig instruktion. ## Relaterade commits - Feature-commit: `c3c64482c062f144fd6cb6036e3c9db0afa5ec1e` - Merge-commit till `main`: `2696195e741c155a197ae9838d1938bdf2148cc2`