494 lines
18 KiB
Markdown
494 lines
18 KiB
Markdown
# 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 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 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.
|