fix(hurtig tilføjelse): fire ting, der blev læst forkert eller tabt i stilhed

Fundet under arbejdet med genopbygnings-prompten, hvor den rigtige parser
blev kørt over en tabel af sætninger.

- "at" før et 12-timers klokkeslæt blev stående i titlen: "file the VAT
  return tomorrow at 10am" blev opgaven "file the VAT return at". Markøren
  (at, kl, klokken) hører nu med til tiden, også foran "7:30 pm".
- "køb 1/2 liter fløde" fik forfaldsdato 1. februar. En d/m efterfulgt af en
  mængdeenhed (liter, dl, kg, stk, tsk, kop, del, …) er en mængde. Uden enhed
  er det stadig en dato — "3/4" alene, "møde 3/4 med Anders" og også "3/4
  kage", fordi intet i "kage" skiller tredje april fra tre fjerdedele, og en
  dato, der stille forsvinder, er værre end en, man selv sletter.
- Et "#navn" eller "/navn", der ikke fandtes, blev klippet ud af titlen, og
  opgaven landede et andet sted uden et ord. Serveren har længe sendt
  unknown_project og unknown_section; fladen siger dem nu i en besked, også
  når en underopgave beholder sin opgaves projekt.
- Titlen i opgaveruden blev læst om, men gentagelsen blev læst under
  `parsed.recurrence`, et felt der ikke findes, så "hver mandag" forsvandt.
  Den læser nu `recurrence_rule` og tager også klokkeslæt, projekt og alarm
  med, og siger gentagelsen på læserens sprog i stedet for et fast
  "gentagelse". Alarmen lægges før rettelsen, så den skrevne ikke stables
  oven på standardalarmen.

Prøver: to nye Go-prøver i quickadd og to e2e-prøver. Fem plantede fejl,
hver fanget af sin egen prøve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kristianwind
2026-10-31 00:32:16 +01:00
co-authored by Claude Opus 5.5
parent ec5cf8f9f9
commit ed30eb4c36
7 changed files with 249 additions and 13 deletions
+66 -5
View File
@@ -14,7 +14,7 @@
import { app, projectName, clockOf } from '$lib/stores.svelte.js';
import { focusOnMount } from '$lib/focus.js';
import { t, tag } from '$lib/i18n.svelte.js';
import { REPEATS, reminderOffsetLabel } from '$lib/when.js';
import { REPEATS, reminderOffsetLabel, repeatLabel, shortDate } from '$lib/when.js';
import { readList } from '$lib/pastelist.js';
let { task, onclose } = $props();
@@ -215,24 +215,83 @@
const patch = { content: trimmed };
const took = [];
// Det, der ikke kan stå i en PATCH: alarmen er sin egen række.
let reminder = null;
if (parsed && parsed.content && parsed.content !== trimmed) {
patch.content = parsed.content;
// Samme felter som hurtig tilføjelse læser, og med de navne serveren
// sender dem under. Feltet hedder `recurrence_rule`; det læste en gang
// `parsed.recurrence`, som aldrig findes, så "hver mandag" i en titel
// forsvandt fra titlen og blev ingen steder.
if (parsed.due_date) {
patch.due_date = parsed.due_date;
took.push(parsed.due_date);
took.push(shortDate(parsed.due_date));
}
// Parseren fylder altid datoen ud, når den har læst et klokkeslæt, så
// due_date er sat ovenfor, når due_time er.
if (parsed.due_time) {
patch.due_time = parsed.due_time;
took.push(parsed.due_time);
}
if (parsed.priority && parsed.priority < 4) {
patch.priority = parsed.priority;
took.push(`P${parsed.priority}`);
}
if (parsed.recurrence) {
patch.recurrence_rule = parsed.recurrence;
took.push('gentagelse');
if (parsed.recurrence_rule) {
patch.recurrence_rule = parsed.recurrence_rule;
took.push(repeatLabel(parsed.recurrence_rule, parsed.recurrence_text));
}
if (parsed.labels?.length) {
patch.labels = [...new Set([...(task.labels ?? []), ...parsed.labels])];
took.push(parsed.labels.map((l) => `@${l}`).join(' '));
}
if (parsed.project) {
// Navnet slås op som hurtig tilføjelse gør: uden forskel på store og
// små bogstaver. Et navn, der ikke findes, siges højt — det er klippet
// ud af titlen, og uden en besked var ordet bare væk.
const wanted = parsed.project.toLowerCase();
const found = app.projects.find((p) => p.name.toLowerCase() === wanted);
if (!found) {
app.toast(
t('task.unknownProject', {
name: parsed.project,
where: projectName(app.projectById(task.project_id))
})
);
} else if (found.id !== task.project_id && !task.parent_id) {
patch.project_id = found.id;
took.push(`#${found.name}`);
}
}
if (parsed.reminder_at) {
// Vægurstid i læserens zone, ligesom feltet nedenfor giver den.
reminder = { remind_at: new Date(parsed.reminder_at).toISOString() };
} else if (parsed.reminder_offset_min !== undefined && parsed.reminder_offset_min !== null) {
// En forskydning regnes fra klokkeslættet. Uden et ville rækken aldrig
// ringe, og intet bagefter ville afsløre det.
if (patch.due_time || task.due_datetime) {
reminder = { offset_min: parsed.reminder_offset_min };
} else {
app.toast(t('task.alarmNeedsATime'));
}
}
}
// Alarmen før rettelsen: får opgaven sit klokkeslæt i samme gemning, lægger
// serveren standardalarmen på — medmindre opgaven allerede har en. Den,
// der blev skrevet, skal være den, og ikke stå oven på standarden.
if (reminder) {
try {
const created = await api.createReminder(task.id, reminder);
reminders = [...reminders, created];
took.push(
reminder.offset_min !== undefined
? reminderOffsetLabel(reminder.offset_min)
: parsed.reminder_at.slice(11)
);
} catch (e) {
app.toast(humanMessage(e));
}
}
await save(patch);
@@ -244,6 +303,8 @@
if (took.length) {
content = patch.content;
if (patch.due_date) dueDate = patch.due_date;
if (patch.due_time) dueTime = patch.due_time;
if (patch.project_id) projectId = patch.project_id;
if (patch.priority) priority = patch.priority;
if (patch.recurrence_rule) recurrence = patch.recurrence_rule;
if (patch.labels) labels = patch.labels.join(', ');