Once a submission's due date passes, the legacy homework page renders the
student's text read-only in a `<div class="comment">` that is a sibling
*after* `</section id="submission">`, which then holds only the file list.
`parseHomeworkPage` searched for that div *inside* the submission section, so
for every past-due submission it silently returned no submitted text while
still reporting the grade and feedback — the reader would conclude the student
handed in nothing.
Search the whole page instead. `class="comment"` (quote right after the word)
stays specific: the teacher's feedback is `class="comment ckcontent"` and does
not match, and the editable-textarea branch is still tried first.
Found by standing up a local instance from the deployed images and reading a
real 33.40 page for a seeded past-due submission; the earlier test fixture had
nested the div inside the section, which is why the gap was invisible. The
fixtures now match the real DOM.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
You were right that this data never reaches the browser as an API call.
The legacy front end calls the Feathers API server-side for
submission.comment, submission.grade and submission.gradeComment and
renders them into GET /homework/{taskId}. That Feathers API is not
exposed publicly — /api/v1/* 404s — so the rendered page is the only way
to reach these fields from outside.
core/homework-page.ts parses it, hooked on the data-testid attributes
the project's own e2e tests use rather than incidental markup. The page
authenticates by jwt *cookie*; an Authorization header is ignored and
redirects to the identity provider. Every field is optional and parse
failures return undefined, so a markup change degrades to "not found"
and cannot break get_task. The wording distinguishes the two: absent
feedback is reported as not found, never as none given.
Measured on one course: 4 of 7 graded submissions carry feedback no API
call can return — "vollständig und nachvollziehbar", "Feedback siehe
Zettel", and so on.
This exposed a bug in a shared utility: htmlToText decoded only six
entities, so any named entity passed through raw. German content makes
that routine — "vollständig" would have reached the model verbatim
from boards and task descriptions too, not just here. It now decodes
named, decimal and hex references in one pass, so &auml; stays
literal instead of decoding twice, and leaves unknown names alone rather
than mangling them.
Also fixes a documented-recovery bug found while restoring the session:
`docker compose restart` does not re-read env_file, so it silently kept
serving the dead token. `up -d` is correct and the docs said the wrong
thing.
78 tests, 38/38 smoke; verified end to end through Claude Code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>