feat: add task editing

This commit is contained in:
Urban Modig
2026-07-28 11:18:13 +02:00
parent b6460a3924
commit 443f686c20
13 changed files with 1409 additions and 30 deletions

View File

@ -29,6 +29,7 @@ dnd-kit-ekosystemets aktuella React-adapter. Den ansvarar för:
- hämtning och presentation av användare och uppgifter;
- lokalt val av aktiv användare;
- formulär för att skapa användare och uppgifter;
- serverbekräftad redigering av uppgifters titel, beskrivning och poäng;
- val och visning av ansvarig användare på uppgifter;
- serverbekräftade statusändringar genom knappar på uppgiftskorten;
- optimistiska statusflyttar genom drag-and-drop mellan brädans kolumner;
@ -66,6 +67,7 @@ Aktuella endpoints:
- `POST /api/tasks`
- `PUT /api/tasks/{taskId}/assignee`
- `PUT /api/tasks/{taskId}/status`
- `PUT /api/tasks/{taskId}/details`
- `DELETE /api/tasks/{taskId}`
### Databas och migreringar

View File

@ -0,0 +1,493 @@
# Feature 8 Redigera uppgift
## Status
Implementerad och automatiskt verifierad på feature-branchen. Manuell
browserverifiering och merge till `main` återstår.
## 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 199.
## 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 199, 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 199;
- 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 ska verifieras manuellt:
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
```
Vid implementation uppdateras `README.md`, `docs/architecture.md`,
`docs/roadmap.md` och `docs/development.md` när relevant.
Roadmapen markerar Feature 8 som `Klar` först efter implementation, automatiska
tester, produktionsbygge, manuell verifiering, merge till `main` och slutlig
dokumentationsuppdatering.
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
Fylls i efter implementation och merge.

View File

@ -34,7 +34,9 @@ Följande statusvärden används:
## Nuvarande läge
Feature 07 är klara och finns på `main`. Den aktuella applikationen har:
Feature 07 är klara och finns på `main`. Feature 8 är implementerad på sin
feature-branch och inväntar manuell verifiering och merge. Den aktuella
applikationen på feature-branchen har:
- ett monorepo med separat React/Vite-frontend och Spring Boot-backend;
- centralt lagrade användare och ett lokalt browserval av aktiv användare;
@ -47,15 +49,17 @@ Feature 07 är klara och finns på `main`. Den aktuella applikationen har:
- automatisk tilldelning till aktiv användare när en otilldelad uppgift påbörjas;
- drag-and-drop mellan statuskolumner med optimistisk flytt och rollback;
- serverbekräftad permanent radering med bekräftelsedialog;
- serverbekräftad redigering av titel, beskrivning och poäng;
- en bräda med Väntande, Pågående och Klart;
- nya uppgifter som alltid skapas med status `WAITING`.
Tilldelning och status är separata egenskaper; tilldelningsflödet ändrar inte
uppgiftens status. Alla direkta statusövergångar är tillåtna och
`IN_PROGRESS` kräver ansvarig. Det finns ännu ingen redigering, deadline eller
återkommande uppgift. Nuvarande användarval är inte autentisering.
`IN_PROGRESS` kräver ansvarig. Det finns ännu ingen deadline eller återkommande
uppgift. Nuvarande användarval är inte autentisering.
**Feature 8 Redigera uppgift är nästa planerade produktfeature.**
**Feature 8 Redigera uppgift är pågående. Ingen senare produktfeature utses
som nästa innan Feature 8 har verifierats och mergats.**
## Featureöversikt
@ -69,7 +73,7 @@ uppgiftens status. Alla direkta statusövergångar är tillåtna och
| 5 Statusändring | Klar | 4 | Backendstyrda statusövergångar |
| 6 Drag-and-drop | Klar | 5 | Kortflytt via status-API |
| 7 Radera uppgift | Klar | 2 | Bekräftad permanent radering |
| 8 Redigera uppgift | Planerad | 3 | Titel, beskrivning och poäng |
| 8 Redigera uppgift | Pågående | 3 | Titel, beskrivning och poäng |
| 9 Deadline | Planerad | 2 | Valfri deadline och förseningsmarkering |
| 10 Sökning och filtrering | Planerad | 2; 4 för ansvarig; 9 för deadline | Sökning och filter på brädan |
| 11 Design av återkommande uppgifter | Planerad | 35, 9 | Beslut och plan, ingen produktionskod |
@ -231,7 +235,7 @@ featuren är mergad till `main`.
### Feature 8 Redigera uppgift
**Status:** Planerad
**Status:** Pågående
**Beroenden:** Feature 3
@ -245,6 +249,12 @@ Featuren ligger efter poäng för att redigeringsflödet ska omfatta den då
aktuella uppgiftsmodellen. Ansvarig ska fortsatt ändras genom
tilldelningsflödet från Feature 4 och status genom statusflödet från Feature 5.
Den implementerade lösningen använder `PUT /api/tasks/{taskId}/details` och
uppdaterar endast titel, beskrivning och poäng. Frontend använder en separat
redigeringsmodal och serverbekräftad uppdatering genom den gemensamma låsningen
per task-id. Implementation och automatiska kontroller är genomförda på
feature-branchen; manuell browserverifiering och merge återstår.
### Feature 9 Deadline
**Status:** Planerad
@ -443,6 +453,8 @@ Nuvarande aktiva användarval är uttryckligen inte autentisering.
## Ändringshistorik
- 2026-07-27: Feature 8 implementerades och verifierades automatiskt på
feature-branchen. Manuell verifiering och merge återstår.
- 2026-07-27: Feature 7 verifierades och mergades. Permanent,
serverbekräftad radering infördes, och Feature 8 blev nästa planerade
produktfeature.