fix(shared-help): harden help system with XSS protection, i18n, type safety, and reference implementation

- Add HTML sanitization via isomorphic-dompurify in parser layer to prevent XSS
- Replace all hardcoded English strings with translations (FAQSection, KeyboardShortcuts, ChangelogEntry/Section)
- Remove unsafe `as` type casting in loader.ts, use Zod-inferred generics instead
- Add error logging in content loader (replaces silent catch blocks)
- Fix HelpSearch blur handling (mousedown+preventDefault instead of setTimeout hack)
- Add ARIA attributes to HelpSearch for accessibility
- Derive FAQ categories from items instead of hardcoding all 6
- Fix null-safety in GettingStartedGuide.svelte
- Fix unused appId variable in HelpPage.svelte, add scroll-reset on tab switch
- Rebuild Contacts help page as reference implementation using shared HelpPage component
- Add README with quick-start guide, props docs, and translations template

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-24 22:38:46 +01:00
parent 8356ac634a
commit 42dd7d2a7a
18 changed files with 845 additions and 801 deletions

View file

@ -5,7 +5,8 @@
import matter from 'gray-matter';
import { marked } from 'marked';
import type { ZodSchema } from 'zod';
import type { ZodType, ZodTypeDef } from 'zod';
import { sanitizeHtml } from './sanitize.js';
export interface ParsedContent<T> {
frontmatter: T;
@ -23,7 +24,7 @@ export interface ParseOptions {
*/
export function parseMarkdown<T>(
rawContent: string,
schema?: ZodSchema<T>,
schema?: ZodType<T, ZodTypeDef, unknown>,
options: ParseOptions = { renderHtml: true }
): ParsedContent<T> {
const { data, content } = matter(rawContent);
@ -40,8 +41,8 @@ export function parseMarkdown<T>(
frontmatter = data as T;
}
// Render HTML if requested
const html = options.renderHtml ? (marked.parse(content) as string) : '';
// Render HTML if requested, then sanitize to prevent XSS
const html = options.renderHtml ? sanitizeHtml(marked.parse(content) as string) : '';
return {
frontmatter,
@ -55,7 +56,7 @@ export function parseMarkdown<T>(
*/
export function parseMarkdownFiles<T>(
files: { filename: string; content: string }[],
schema?: ZodSchema<T>,
schema?: ZodType<T, ZodTypeDef, unknown>,
options?: ParseOptions
): Array<ParsedContent<T> & { filename: string }> {
return files.map(({ filename, content }) => ({