fix(magazine): stale audio on unapproved articles no longer counts as reviewed in the page grid

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-09-28 16:35:33 +02:00
parent d712058d68
commit 19af236bcd
3 changed files with 21 additions and 4 deletions

View File

@ -41,7 +41,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
- **Unboxed ink in or beside a picture, chart or advert is no longer reported as missed text** (labels of a chart, text set into a photo, an advert's own wording).
### Fixed
- **An approved page is no longer purple.** A page the quality check flagged stayed purple even after you approved its article; now your approval wins and the page shows its review colour (the warning badge on the card still lists what the check found). Purple means flagged *and* nobody has approved anything on the page.
- **An approved page is no longer purple.** A page the quality check flagged stayed purple even after you approved its article; now your approval wins and the page shows its review colour (the warning badge on the card still lists what the check found). Purple means flagged *and* nobody has approved anything on the page. An old MP3 on an article nobody approved (left over from before pages were linked) no longer turns a page green either: audio only counts for approved articles.
- **Reading order is now applied automatically after import, *OCR again* and every per-page fix**, not only by the manual *Fix reading order* button. Until now the order after import was whatever the vision/LLM step returned, so a column's short last box could be read after the whole other column; on the measured issue 101 of 204 pages had a saved order that differs from the column-aware order. Pages whose order you fixed by hand stay untouched, except after *OCR again*, when every region is new and a hand order can no longer apply.
- **The review list shows each region's whole text**, not the first 220 characters, so every misspelling gets its red wavy underline (like a word processor). The engine's `[Non-Text]` marker and hairline boxes (under 3 pt tall) are now treated as OCR noise by *Clean up boxes* and after a page re-OCR, so they no longer show up as near-invisible regions. Fitted boxes keep a little air on grown sides.
- **The quality check no longer flags correct text as "suspect OCR".** The word check guessed the language per fragment (`iPhones statt Token` read as English) and flagged valid German; it now uses the page's language, the same lenient German rules as the spellchecker, and treats words that recur on the page (names, brands) as vocabulary. Page 61 of the measured issue went from 11 suspect regions to 3 (one a genuinely truncated word), and the issue from 758 to 610 before the language fix's full effect.

View File

@ -809,7 +809,7 @@ const MAG_PAGES_COLS_KEY = 'ttsvc_mag_pages_cols';
// The last whole-issue quality run splits the two unreviewed states further:
// checked -- quality run found nothing left to fix (blue)
// flagged -- quality run found uncovered text / overlaps / clipping / suspect words (purple)
// flagged shows only while nothing on the page is approved (an approval wins); clean only replaces 'unreviewed'.
// flagged shows only while nothing on the page is approved (an approval wins; old audio does not count); clean only replaces 'unreviewed'.
// Mirrors page_is_advertisement() in core/magazine.py: flagged, or detection found advertisement
// regions and nothing narratable on the page.
const MAG_NARRATABLE_TYPES = ['title', 'heading', 'deck', 'byline', 'body', 'quote'];
@ -829,11 +829,13 @@ function magPageState(arts, q, page) {
const flagged = !!(q && q.needs_review);
if (page != null && magIsAdPage(page)) return 'ad'; // approved as advertisement: settled, green
const rejectedOnly = arts.length > 0 && arts.every((a) => a.status === 'rejected');
const reviewed = arts.some((a) => a.status === 'approved' || a.has_audio);
// Audio does not count as a review: an article nobody approved can still carry an MP3 from before
// its pages were re-linked.
const reviewed = arts.some((a) => a.status === 'approved');
if (flagged && !rejectedOnly && !reviewed) return 'flagged';
const unreviewed = q ? 'checked' : null;
if (!arts.length) return unreviewed || 'blank';
if (arts.some((a) => a.has_audio)) return 'audio';
if (arts.some((a) => a.has_audio && a.status === 'approved')) return 'audio';
if (arts.some((a) => a.status === 'detected')) return unreviewed || 'detected';
if (arts.some((a) => a.status === 'approved')) return 'approved';
return 'empty';

View File

@ -482,6 +482,21 @@ test.describe('Magazine: page colours', () => {
await expect(await openGrid(page, 'detected')).toHaveClass(/is-page-flagged/);
});
test('an old MP3 on an unapproved article does not turn a flagged page green', async ({ page }) => {
await mockBackend(page, { articleStatus: 'detected', hasAudio: true });
await page.route(`**/api/magazine/${DOC_ID}/quality/report`, route => route.fulfill({ json: {
summary: {}, pages: [{ page: 3, uncovered: [], uncovered_ratio: 0, suspect_blocks: [], overlaps: [], clipped: [{ id: 'b3', side: 'right' }],
measured: true, spelling: [], needs_review: true, words_to_check: false }] } }));
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
await navTo(page, 's-magazine');
await page.locator('#mag-library-list .mag-card').first().click();
await page.locator('#mag-contents-view-toggle').click();
const card = page.locator('.mag-card-wrap').filter({ hasText: 'Page 3' }).locator('.mag-card');
await expect(card).toHaveClass(/is-page-flagged/);
await expect(card).not.toHaveClass(/is-page-audio/);
});
test('once the page\'s article is approved the approval wins over the flag', async ({ page }) => {
const card = await openGrid(page, 'approved');
await expect(card).not.toHaveClass(/is-page-flagged/);