fix(symptoms): assign an occurrence to an illness by its link, not its time

The episode detail listed every symptom occurrence that fell inside the
episode's onset-to-recovery span as well as the ones filed against it, so a
long-running condition showed the symptoms of every illness overlapping it,
and picking an illness in the entry sheet changed nothing on the page. The
occurrence edit sheet carried no illness link at all, so once logged the
assignment could be neither seen nor changed.

- The episode card reads only the occurrences filed against the episode
  (`SymptomEvent.episodeId`), with a guard against any other.
- The edit sheet loads the link and edits it like the time and intensity;
  an unchanged link is not re-sent, so a link the list cannot show stays.
- One rule decides which episodes may take an occurrence
  (`isEpisodeLinkableAt`): an open one, or a resolved one whose span holds
  the occurrence's time. The server checks it against the time after the
  edit, and the entry and edit sheets offer exactly what it admits, so a
  backdated occurrence can be filed with an illness that has since ended.
- The illness page names the illness on each occurrence row.
- The day view opens a symptom's illness only while that episode exists.

The stored links were always written correctly, so no data repair is needed.

Refs #1237
This commit is contained in:
Marc-André Bombeck committed 2026-10-17 11:00:02 +02:00
1 parent 048ef0020e
commit c1df60693b
23 files changed
+904 -139

No files matched your search

+4 -2
View File
@@ -35563,7 +35563,8 @@ components:
- intensity
description: Log one occurrence. `intensity` is 0-10 (0 none, 10 the worst imaginable). `occurredAt` defaults to now and
may not lie in the future beyond five minutes. `episodeId`, when given, must name one of the record's own
illness episodes that is still open (422 `symptoms.episode.notOpen` otherwise). The note is encrypted at rest.
illness episodes that is still open, or a resolved one whose onset-to-recovery span holds `occurredAt` (422
`symptoms.episode.notOpen` otherwise). The note is encrypted at rest.
UpdateSymptomEventRequest:
type: object
properties:
@@ -35587,7 +35588,8 @@ components:
maxLength: 40
- type: "null"
description: "Partial edit of one occurrence. `note: null` clears the note, `episodeId: null` drops the link. A changed
`episodeId` must name an open episode; an unchanged link to an episode that has since resolved is kept."
`episodeId` must name an open episode or a resolved one whose span holds the occurrence's time after the edit;
an unchanged link to an episode that has since resolved is kept."
CreateLifeEventRequest:
type: object
properties: