From 6e83291444e9da72e196a7af4b06380e8270275a Mon Sep 17 00:00:00 2001 From: Kristian Wind Date: Wed, 19 Aug 2026 18:59:34 +0200 Subject: [PATCH] =?UTF-8?q?feat(notes):=20opgaven=20viser=20de=20noter,=20?= =?UTF-8?q?der=20n=C3=A6vner=20den=20=E2=80=94=20og=20har=20f=C3=A5et=20en?= =?UTF-8?q?=20adresse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Den anden vej af samme forbindelse. En note, der nævner en opgave, viste den; nu viser opgaven noten. Uden det virker forbindelsen kun, hvis man tilfældigvis sidder og læser noten, og det er den halvdel, ingen har brug for hjælp til. Undervejs viste det sig, at en opgave slet ikke havde en adresse. Skuffen åbner over den liste, man står på, og ændrer ingenting i URL'en — hvilket er rigtigt for et klik og ubrugeligt for et link: man kan ikke sende nogen en opgave, og en note, der nævner en, har ingen steder at sende dem hen. Min egen henvisningsform pegede altså på noget, der ikke fandtes. /opgave/{id} findes nu. Den åbner skuffen og træder til side — erstatter sig selv med opgavens eget projekt, så man lander hvor opgaven bor, med den åben, og tilbage-knappen ikke går gennem en side, der kun nogensinde viderestillede. Prøven henter opgavens id fra API'et frem for fra adresselinjen, netop fordi den ikke står der. Co-Authored-By: Claude Opus 5 --- web/e2e/smoke.spec.js | 47 ++++++++++++++++++++++ web/src/lib/components/TaskDetail.svelte | 41 +++++++++++++++++++ web/src/lib/locales/da.js | 1 + web/src/lib/locales/en.js | 1 + web/src/routes/noter/+page.svelte | 11 ++++++ web/src/routes/opgave/[id]/+page.svelte | 50 ++++++++++++++++++++++++ 6 files changed, 151 insertions(+) create mode 100644 web/src/routes/opgave/[id]/+page.svelte diff --git a/web/e2e/smoke.spec.js b/web/e2e/smoke.spec.js index 97cac17..6708c22 100644 --- a/web/e2e/smoke.spec.js +++ b/web/e2e/smoke.spec.js @@ -2211,3 +2211,50 @@ test('et tag finder sit projekt uanset store og små bogstaver', async ({ page } expect(trouble).toEqual([]); }); + +test('en opgave viser de noter, der nævner den, og linket åbner noten', async ({ page }) => { + const trouble = watchForTrouble(page); + await page.goto('/'); + + const box = page.getByLabel('Ny opgave'); + await box.fill('ring til leverandøren i dag'); + await box.press('Enter'); + await expect(page.getByText('ring til leverandøren', { exact: true })).toBeVisible(); + + // Asked for rather than read off the address bar: opening a task shows a drawer + // without navigating, so the URL is still the list's. + const taskId = await page.evaluate(async () => { + const r = await fetch('/api/v1/tasks?limit=100', { + credentials: 'include', + headers: { 'Sec-Fetch-Site': 'same-origin' } + }); + const j = await r.json(); + return (j.tasks ?? []).find((t) => t.content === 'ring til leverandøren')?.id; + }); + expect(taskId, 'opgaven blev ikke oprettet').toBeTruthy(); + + const sidebar = page.getByRole('navigation', { name: 'Hovedmenu' }); + await sidebar.getByRole('link', { name: 'Noter', exact: true }).click(); + await page.getByRole('button', { name: 'Ny note' }).click(); + const ed = page.getByRole('textbox', { name: 'Notens tekst' }); + await ed.click(); + await page.keyboard.type('Referat fra tirsdag'); + await page.keyboard.press('Enter'); + await page.keyboard.type('Aftalt at han ringer, se /opgave/' + taskId); + await ed.blur(); + + // Back on the task: the other direction of the same link. Without it the + // connection only works if you happen to be reading the note, which is the half + // nobody needs help with. + await page.goto('/opgave/' + taskId); + const linked = page.locator('.linked-notes').getByRole('link', { name: /Referat fra tirsdag/ }); + await expect(linked).toBeVisible(); + + // And it opens the note rather than dropping you on a list to find it again. + await linked.click(); + await expect(page.getByRole('textbox', { name: 'Notens tekst' })).toContainText( + 'Aftalt at han ringer' + ); + + expect(trouble).toEqual([]); +}); diff --git a/web/src/lib/components/TaskDetail.svelte b/web/src/lib/components/TaskDetail.svelte index a41f347..4d26252 100644 --- a/web/src/lib/components/TaskDetail.svelte +++ b/web/src/lib/components/TaskDetail.svelte @@ -43,6 +43,7 @@ let subtasks = $state([]); let comments = $state([]); let attachments = $state([]); + let notes = $state([]); let reminders = $state([]); let newSubtask = $state(''); @@ -80,6 +81,7 @@ subtasks = []; comments = []; attachments = []; + notes = []; reminders = []; const id = task.id; @@ -96,6 +98,11 @@ api.listReminders(id) .then((r) => id === seededId && (reminders = r.reminders)) .catch(() => {}); + // Quietly, because a task with no notes is the ordinary case and a failure + // here must not put an error in front of somebody reading a task. + api.notesLinking('task', id) + .then((r) => id === seededId && (notes = r.notes ?? [])) + .catch(() => {}); }); /** @@ -540,6 +547,22 @@ + + {#if notes.length} +
+

{t('notes.title')}

+ {#each notes as note (note.id)} + + {note.title || t('notes.untitled')} + + {/each} +
+ {/if} +

{t('detail.comments')}

@@ -582,6 +605,24 @@