# Feature 8 – Redigera uppgift ## Status Färdig och mergad till `main`. ## Bakgrund HemHub stödjer skapande, visning, tilldelning, statusändring, drag-and-drop och permanent radering av uppgifter. Det saknas fortfarande möjlighet att korrigera eller uppdatera en befintlig uppgifts grundläggande innehåll. Feature 8 inför därför redigering av: - titel; - beskrivning; - poäng. Redigering hålls separat från de specialiserade flödena för ansvarig, status och radering. ## Mål Feature 8 ska: - låta användaren öppna en redigeringsdialog från uppgiftskortet; - låta användaren ändra titel, beskrivning och poäng; - återanvända samma valideringsregler som vid skapande; - införa ett avgränsat backend-API för uppgiftens redigerbara detaljfält; - använda serverbekräftad uppdatering; - återanvända befintlig låsning och felhantering per task-id; - fungera tillsammans med status, tilldelning, drag-and-drop och radering; - behålla uppgiftens kolumn och ordning efter redigering. ## Omfattning Feature 8 omfattar redigering av `title`, `description` och `points`. Följande egenskaper ska inte kunna ändras genom redigeringsflödet: - `id`; - `status`; - `assignee`; - `createdAt`. Ansvarig ska fortsatt ändras genom tilldelningsflödet. Status ska fortsatt ändras genom status-API:t och drag-and-drop-flödet. ## Avgränsningar Feature 8 ska inte införa: - statusändring eller ändring av ansvarig i redigeringsformuläret; - inline-redigering eller en generell detaljvy; - deadline, återkommande uppgifter, kategorier, etiketter, kommentarer eller bilagor; - status-, poäng- eller versionshistorik eller revisionslogg; - behörigheter, batchredigering eller autosave; - realtidsuppdatering mellan browsers; - en generell formulär- eller modalplattform; - en ny global state-lösning; - `updatedAt`; - en ny åtgärdsmeny på uppgiftskortet. ## Redigeringsflöde Redigering sker i en separat modal med: - titel; - beskrivning; - poäng; - knappen `Avbryt`; - knappen `Spara`; - ett stängningskryss. Inline-redigering direkt på kortet ingår inte. En separat modal väljs eftersom fälten redigeras som en sammanhållen operation, kortets layout ska förbli stabil, inline-formulär skulle störa dragytan och ett serverbekräftat spara-/avbrytflöde kan hanteras isolerat. ## Initiering från uppgiftskortet Varje uppgiftskort får en separat synlig redigeringsknapp bredvid den befintliga sopkorgsknappen. Den ska: - ligga i kortets befintliga åtgärdsområde uppe till höger; - använda en neutral inline-SVG-ikon; - ha ungefär samma klickyta som raderingsknappen; - ha en tillgänglig etikett som identifierar uppgiften, exempelvis `Redigera Töm diskmaskinen`; - kunna aktiveras med tangentbord; - inte initiera drag-and-drop; - stoppa relevanta pointer-händelser innan de når dragytan; - vara inaktiverad när samma uppgift har en pågående operation. Feature 8 inför ingen åtgärdsmeny. Om kortåtgärder senare flyttas till en meny ska redigerings-API:t och det underliggande redigeringsflödet kunna behållas. ## Separat redigeringsmodal Redigeringen implementeras som en separat komponent, exempelvis `EditTaskModal`. Komponenten ska följa samma visuella och beteendemässiga mönster som den befintliga skapandemodalen. Feature 8 kräver inte att skapande- och redigeringsmodalerna slås ihop till en generell komponent med flera lägen. Mindre gemensamma valideringsfunktioner eller formulärhjälpare får brytas ut om repositoryts faktiska kod tjänar på det. ## Formulärets initiala värden När redigeringsmodalen öppnas fylls den med uppgiftens aktuella titel, beskrivning och poäng. En beskrivning som är `null` visas som tom sträng. Titelfältet får initialt fokus. Texten markeras inte automatiskt. Formuläret baseras på task-versionen i frontend-state när dialogen öppnas. Om dialogen stängs utan att spara kastas lokala ändringar. När den öppnas igen hämtas initialvärdena på nytt från den aktuella uppgiften i frontend-state. Ingen särskild synkronisering eller versionshantering införs om task-data skulle ändras medan modalen är öppen. ## Tillåtna statusar och användare Titel, beskrivning och poäng får redigeras i `WAITING`, `IN_PROGRESS` och `COMPLETED`, oavsett ansvarig eller aktiv browseranvändare. Aktiv användare är ett lokalt browserval och inte autentisering eller behörighetskontroll. Att poäng kan ändras på en slutförd uppgift är accepterat i nuvarande modell eftersom HemHub ännu saknar poänghistorik. En framtida historikfeature ska besluta om intjänade poäng använder ett snapshot eller uppgiftens aktuella poängvärde. ## Stängningsbeteende Innan save-anropet har startat ska redigeringsmodalen kunna stängas med: - `Avbryt`; - Escape; - klick på modalens bakgrund; - stängningskrysset. Osparade ändringar kastas utan extra bekräftelse. Under pågående save-anrop ska samtliga stängningsvägar blockeras: - `Avbryt` och `Spara` är inaktiverade; - stängningskrysset är inaktiverat eller otillgängligt; - Escape ignoreras; - klick på bakgrunden ignoreras. Modalen ligger kvar öppen tills backend-anropet har slutförts. ## Validering Redigering använder samma valideringsregler och användarmeddelanden som skapande. Backend är alltid slutlig garant. ### Titel Titeln trimmas, är obligatorisk och får innehålla högst 100 Unicode-kodpunkter. Tom eller enbart blank titel är ogiltig. ### Beskrivning Beskrivningen trimmas, är valfri och får innehålla högst 500 Unicode-kodpunkter. Den skickas och lagras som `null` när den är tom efter trimning. ### Poäng Poäng är obligatoriskt, måste vara ett heltal mellan 1 och 99 och får inte ersättas med ett backend-defaultvärde. Frontend blockerar submit vid tom eller för lång titel, för lång beskrivning, tomt poängfält, text eller decimaltal samt poäng utanför 1–99. ## Oförändrad submit `Spara` är tillgänglig även när användaren inte har ändrat något. Frontend skickar ett normalt uppdateringsanrop och backend behandlar samma värden som en giltig idempotent uppdatering. Ingen dirty-state införs. ## Backend-API Redigering sker genom: ```http PUT /api/tasks/{taskId}/details ``` Endpointen ändrar endast uppgiftens redigerbara detaljfält. ### Request Requesten innehåller alltid hela den redigerbara uppsättningen: ```json { "title": "Töm diskmaskinen", "description": "Ställ in allt i rätt skåp", "points": 3 } ``` Samtliga tre fält ska finnas. `description` får vara `null`. Backend ska inte implementera patchsemantik för saknade fält. ### Lyckad uppdatering En lyckad uppdatering ger `200 OK` och hela den uppdaterade uppgiften i samma task-format som övriga task-operationer. Serverns fullständiga respons är slutlig sanning. ### Fel - okänd uppgift ger `404 Not Found` och `TASK_NOT_FOUND`; - ogiltigt task-id använder repositoryts befintliga hantering för ogiltiga path-parametrar; - ogiltig titel, beskrivning eller poäng ger `400 Bad Request` med den befintliga task-valideringen och normalt felkoden `INVALID_TASK`. Repositoryts faktiska implementation har företräde. ## Backendens uppdateringsregler Backend hämtar först den befintliga uppgiften och uppdaterar uttryckligen endast `title`, `description` och `points`. Operationen får inte ändra `id`, `status`, `assignee` eller `createdAt`. Den ska vara transaktionell, tillåten i samtliga statusar, idempotent för samma värden, inte påverka andra uppgifter och använda samma trimning och normalisering som skapandeflödet. Ingen `updatedAt` införs. ## Databas Den befintliga task-tabellen innehåller redan titel, beskrivning och poäng. Feature 8 ska därför inte kräva någon Flyway-migrering. Implementation ska verifiera kolumnlängder, `points NOT NULL`, poängconstrainten 1–99, nullhantering för beskrivning och att övriga kolumner inte påverkas. Ingen ny kolumn eller relation införs. ## Frontendens uppdateringsstrategi Redigering är serverbekräftad. När användaren trycker `Spara` ska frontend: 1. validera formuläret; 2. markera uppgiften som upptagen genom låsningen per task-id; 3. behålla kortets tidigare värden och modalen öppen; 4. inaktivera formuläret och samtliga stängningsvägar; 5. skicka `PUT /api/tasks/{taskId}/details`; 6. vid framgång ersätta uppgiften med serverns fullständiga respons på samma plats i task-listan; 7. stänga modalen och frigöra låsningen. Frontend visar inte de redigerade värdena optimistiskt. Ingen rollback behövs eftersom kortet behåller sina tidigare värden tills servern svarar. ## Vänteläge och gemensam låsning Feature 8 återanvänder den befintliga låsningen per task-id. Under save ska: - formulärfält, knappar och stängningsvägar vara inaktiverade; - kortet ligga kvar i samma kolumn och tonas ned; - samma uppgift inte kunna dras, ändra status eller ansvarig, raderas, öppnas för ny redigering eller skicka dubbla save-anrop; - andra uppgifter förbli interaktiva. Ingen separat redigeringslåsning, global vänteläge eller parallell requesthantering införs. En öppen modal låser inte tasken innan `Spara`. ## Samspel med befintliga flöden Redigeringsknappen använder samma pointer-hantering som raderingsknappen och startar inte drag-and-drop. Drag-and-drop förblir optimistiskt med rollback, medan redigering är serverbekräftad. Status ändras fortsatt genom statusknappar eller drag-and-drop. Ansvarig ändras fortsatt genom tilldelningsflödet. Raderingsknappen ligger bredvid redigeringsikonen. Samtliga flöden delar låsningen per task-id. ## Kortets ordning och kolumn En lyckad redigering ersätter uppgiften på befintlig plats i frontendens task-lista utan omsortering. Status ändras inte, så kortet ligger normalt kvar i samma kolumn. Serverns fullständiga task-respons ersätter ändå det lokala värdet i sin helhet. ## Felhantering ### Frontendvalideringsfel Vid frontendvalideringsfel skickas inget API-anrop. Modalen och inmatningen behålls, ett begripligt fel visas och användaren kan korrigera och försöka igen. ### Vanliga API-fel Vid backendvalideringsfel, nätverksfel, serverfel eller oväntad respons ligger kortet kvar oförändrat. Modalen och inmatningen behålls, vänteläget avslutas, kontrollerna aktiveras och användaren kan försöka igen eller avbryta. Generellt meddelande: > Det gick inte att spara ändringarna. Försök igen. Frontend använder strukturerad felkod och HTTP-status där relevant och tolkar inte meddelandetext. ### `404 TASK_NOT_FOUND` Endast kombinationen HTTP `404` och `code === "TASK_NOT_FOUND"` behandlas som ett inaktuellt lokalt kort. Frontend tar då bort uppgiften, stänger modalen och frigör låsningen utan generellt redigeringsfel. Andra 404-fel behandlas som vanliga fel. ## Frontendtester Frontendtesterna ska verifiera beteende och state, inte exakt CSS eller intern komponentstruktur. De ska minst täcka: - redigeringsknapp, inline-SVG, tillgänglig etikett, tangentbordsaktivering och skydd mot dragstart; - att en låst uppgift inte kan öppnas; - rätt uppgift och initialvärden, inklusive `null` som tom beskrivning; - initialt fokus i titelfältet; - stängning med `Avbryt`, Escape, bakgrund och kryss; - att osparade ändringar kastas och aktuell task-data används vid nästa öppning; - frontendvalidering av titel, beskrivning och poäng; - rätt endpoint och fullständigt requestformat med trimmade värden och tom beskrivning som `null`; - oförändrad submit; - serverbekräftat vänteläge, blockerade stängningsvägar, gemensam task-låsning och blockerade dubbla save-anrop; - att andra kort förblir interaktiva; - fullständig serverrespons, bibehållen plats, ordning och kolumn; - vanliga fel med bevarad modal/inmatning och fungerande återförsök; - `404 TASK_NOT_FOUND` samt att andra 404-fel behandlas som vanliga fel. ## Backendtester Backendtesterna bör ligga i en separat integrationstestklass, exempelvis `TaskEditingApiTest`, om det passar repositoryts teststruktur. Testerna ska minst täcka: - samtidig ändring och trimning av titel, beskrivning och poäng; - tom beskrivning som `null`; - gränsvärdena 1/99 poäng, 100 kodpunkter i titel och 500 i beskrivning; - idempotent uppdatering; - redigering i samtliga tre statusar och fullständig `200 OK`-respons; - saknad, null, tom eller för lång titel; - för lång beskrivning; - saknat, null, text, decimal eller poäng utanför 1–99; - saknade fält i det fullständiga requestobjektet; - okänt task-id och ogiltigt UUID; - att id, status, ansvarig och `createdAt` bevaras; - att andra uppgifter och ansvarig användare är oförändrade. ## Implementerad lösning Backend exponerar `PUT /api/tasks/{taskId}/details`. Requestmodellen kräver `title`, `description` och `points`; explicit `null` är endast tillåtet för beskrivningen. Service-lagret återanvänder skapandeflödets trimning och validering och uppdaterar en hämtad entitet genom en avgränsad `changeDetails`-operation. ID, status, ansvarig och skapandetid bevaras. Frontend visar en neutral redigeringsknapp med inline-SVG bredvid raderingsknappen. Den separata `EditTaskModal` fylls från aktuell task, fokuserar titeln, validerar fälten och blockerar samtliga stängningsvägar under save. Uppdateringen är serverbekräftad och återanvänder samma låsning per task-id som status, tilldelning, drag-and-drop och radering. En fullständig serverrespons ersätter tasken på dess befintliga plats. Endast ett strukturerat `404 TASK_NOT_FOUND` tar bort ett inaktuellt lokalt kort. Ingen Flyway-migrering behövdes eftersom befintliga kolumner och constraints täcker de redigerbara fälten. ## Automatisk verifiering - Backendens riktade redigeringstester: 20 passerade. - Fullständig backendtestsvit: 68 passerade. - Frontendtester: 61 passerade. - Frontendens TypeScript-kompilering och produktionsbygge passerade. - `git diff --check` passerade. En verifierad begränsning i den lokala H2-databasen är att `VARCHAR` räknar UTF-16-kodenheter för vissa tecken utanför BMP. Applikationen validerar enligt Unicode-kodpunkter, men en titel med 100 sådana astrala tecken kan därför avvisas av H2-kolumnen. Feature 8 ändrar inte databasschemat; PostgreSQL-målet ska verifiera denna skillnad när produktionsdatabasen införs. ## Manuell verifiering Följande verifierades manuellt före merge: 1. Redigeringsikonen, klickytan, stilen, etiketten och skyddet mot dragstart. 2. Klick- och tangentbordsöppning av rätt uppgift. 3. Redigering i `WAITING`, `IN_PROGRESS` och `COMPLETED`. 4. Initialvärden, tom beskrivning och initialt fokus. 5. Gränser och fel för titel, beskrivning och poäng. 6. Stängning med `Avbryt`, Escape, bakgrund och kryss samt kastade osparade ändringar. 7. Oförändrad submit. 8. Fördröjt svar med gamla kortvärden, låst modal och nedtonat kort. 9. Gemensam låsning och fortsatt interaktion med andra kort. 10. Vanligt serverfel, bevarad inmatning och lyckat återförsök. 11. `404 TASK_NOT_FOUND` och annat 404-fel. 12. Bibehållen kolumn, ordning, status och ansvarig. 13. Sparade värden efter omladdning. 14. Desktop, mobil, touch och tangentbordsordning. ## Dokumentation Feature 8 dokumenteras i: ```text docs/features/008-task-editing.md ``` `README.md`, `docs/architecture.md` och `docs/roadmap.md` uppdaterades tillsammans med implementationen. `docs/development.md` krävde ingen ändring. Ett nytt ADR behövs normalt inte. Separat modal, redigeringsikon, `PUT /api/tasks/{taskId}/details` och serverbekräftad uppdatering är lokala beslut för Feature 8. ## Acceptanskriterier Feature 8 är klar när: - varje kort har en tangentbordsåtkomlig redigeringskontroll som inte startar drag; - titel, beskrivning och poäng kan redigeras i en separat modal; - aktuella värden fylls i, titeln får fokus och `null` beskrivning visas tom; - redigering fungerar i samtliga statusar utan behörighetsregler; - skapande och redigering använder samma valideringsregler; - backend använder `PUT /api/tasks/{taskId}/details` med hela fältuppsättningen; - samma värden accepteras idempotent; - endast titel, beskrivning och poäng ändras; - `200 OK` returnerar hela task-responsen; - frontend är serverbekräftad och behåller gamla kortvärden under anropet; - modal och task är låsta under save genom befintlig per-task-låsning; - andra uppgifter förblir interaktiva; - serverresponsen ersätter tasken på befintlig plats och kolumn; - vanliga fel behåller modal och inmatning och kan återförsökas; - endast `404 TASK_NOT_FOUND` tar bort ett inaktuellt lokalt kort; - stängningsvägar fungerar före och blockeras under anrop; - ingen inline-redigering, generell modalplattform, Flyway-migrering eller `updatedAt` införs; - automatiska och manuella kontroller genomförs; - relevant dokumentation uppdateras. ## Implementationsprinciper Före implementation ska Codex läsa repositoryts faktiska: ```text AGENTS.md README.md docs/architecture.md docs/development.md docs/roadmap.md docs/decisions/ docs/features/002-task-creation.md docs/features/003-task-points.md docs/features/004-task-assignment.md docs/features/005-task-status.md docs/features/006-task-drag-and-drop.md docs/features/007-task-deletion.md ``` Codex ska även läsa relevant backendkod, frontendkod och befintliga tester och särskilt verifiera entitet, controller, service, repository, request/response, validering, schema, felmodell, task-listans ordning, modal- och kortstruktur, per-task-låsning samt befintliga status-, tilldelnings-, drag- och deleteflöden. Repositoryts faktiska kod, tester och dokumentation har företräde framför antaganden i detta dokument. Implementation, tester och relevant dokumentation ska uppdateras tillsammans. Codex ska inte committa, pusha, skapa pull request eller merga utan uttrycklig instruktion. ## Relaterade commits - Feature-commit: `443f686` - Merge-commit: `28258f4`