test(timeline): add the {@html} grep gate; docs(rtm): trace #850 REQ-001..013

The grep gate fails if any lib/timeline component reaches for the raw-HTML
directive (CWE-79, REQ-010). The RTM gains thirteen rows tracing every #850
requirement to its implementation file(s) and test(s), Status Done.

Refs #850
This commit is contained in:
Marcel
2026-06-15 20:49:28 +02:00
committed by marcel
parent 6834381cb9
commit 9b68ec31f9
2 changed files with 37 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
import { describe, it, expect } from 'vitest';
import { readdirSync, readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const timelineDir = dirname(fileURLToPath(import.meta.url));
/**
* REQ-010 / CWE-79: inline event clustering renders curator event titles and import-derived
* letter titles + sender/receiver text through every component under lib/timeline (the reused
* LetterCard, the new EventCluster card, the existing pills/bands/strip). That text must always
* render through Svelte's default `{...}` escaping — never `{@html}`. This grep gate fails loudly
* the moment any timeline component reaches for the raw-HTML directive.
*/
describe('lib/timeline never uses {@html} (REQ-010)', () => {
it('no timeline component contains the raw-HTML directive', () => {
const components = readdirSync(timelineDir).filter((file) => file.endsWith('.svelte'));
expect(components.length).toBeGreaterThan(0);
const offenders = components.filter((file) =>
readFileSync(join(timelineDir, file), 'utf8').includes('{@html')
);
expect(offenders).toEqual([]);
});
});