managarten/packages/shared-help-ui
Till JS 42dd7d2a7a 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>
2026-03-24 22:38:46 +01:00
..
src fix(shared-help): harden help system with XSS protection, i18n, type safety, and reference implementation 2026-03-24 22:38:46 +01:00
package.json feat: major update with network graphs, themes, todo extensions, and more 2025-12-10 02:37:46 +01:00
README.md fix(shared-help): harden help system with XSS protection, i18n, type safety, and reference implementation 2026-03-24 22:38:46 +01:00
tsconfig.json feat: major update with network graphs, themes, todo extensions, and more 2025-12-10 02:37:46 +01:00

@manacore/shared-help-ui

Shared Svelte 5 help page components for Manacore web apps.

Quick Start

1. Add dependencies to your web app

{
  "dependencies": {
    "@manacore/shared-help-ui": "workspace:*",
    "@manacore/shared-help-content": "workspace:*",
    "@manacore/shared-help-types": "workspace:*"
  }
}

2. Create help content

Create src/lib/content/help/index.ts:

import type { HelpContent } from '@manacore/shared-help-types';

export function getAppHelpContent(locale: string): HelpContent {
  const isDE = locale === 'de';
  return {
    faq: [
      {
        id: 'faq-example',
        question: isDE ? 'Wie funktioniert X?' : 'How does X work?',
        answer: isDE ? '<p>So funktioniert X...</p>' : '<p>X works like this...</p>',
        category: 'features',
        order: 1,
        language: isDE ? 'de' : 'en',
        tags: ['example'],
      },
    ],
    features: [],
    shortcuts: [
      {
        id: 'shortcuts-general',
        category: 'general',
        language: isDE ? 'de' : 'en',
        order: 1,
        shortcuts: [
          { shortcut: 'Cmd/Ctrl + K', action: isDE ? 'Suche' : 'Search' },
        ],
      },
    ],
    gettingStarted: [],
    changelog: [],
    contact: {
      id: 'contact',
      title: isDE ? 'Support' : 'Support',
      content: '',
      language: isDE ? 'de' : 'en',
      order: 1,
      supportEmail: 'support@mana.how',
      responseTime: isDE ? 'Innerhalb von 24 Stunden' : 'Within 24 hours',
    },
  };
}

3. Create the help page route

Create src/routes/(app)/help/+page.svelte:

<script lang="ts">
  import { locale } from 'svelte-i18n';
  import { HelpPage } from '@manacore/shared-help-ui';
  import type { HelpPageTranslations } from '@manacore/shared-help-ui';
  import { getAppHelpContent } from '$lib/content/help/index.js';

  const content = $derived(getAppHelpContent($locale ?? 'en'));
  const translations: HelpPageTranslations = $derived(/* see translations template below */);
</script>

<HelpPage
  {content}
  appName="MyApp"
  appId="myapp"
  {translations}
  showBackButton
  onBack={() => goto('/')}
  showGettingStarted={false}
  showChangelog={false}
/>

HelpPage Props

Prop Type Default Description
content HelpContent required Help content data
appName string required Display name of the app
appId string required App identifier
translations HelpPageTranslations required UI translations
searchEnabled boolean true Show search bar
showFAQ boolean true Show FAQ section
showFeatures boolean true Show Features section
showShortcuts boolean true Show Shortcuts section
showGettingStarted boolean true Show Getting Started section
showChangelog boolean true Show Changelog section
showContact boolean true Show Contact section
defaultSection HelpSection 'faq' Initially active section
showBackButton boolean false Show back navigation
onBack () => void - Back button callback

Sections with empty content are automatically hidden.

Translations Template

const translations: HelpPageTranslations = {
  title: 'Help & Support',
  subtitle: 'Find answers and learn how to use the app',
  searchPlaceholder: 'Search help...',
  sections: {
    faq: 'FAQ',
    features: 'Features',
    shortcuts: 'Shortcuts',
    gettingStarted: 'Getting Started',
    changelog: 'Changelog',
    contact: 'Contact',
  },
  search: {
    noResults: 'No results for "{query}"',
    resultsCount: '{count} results',
    searching: 'Searching...',
  },
  faq: {
    noItems: 'No FAQs available.',
    allCategories: 'All',
    categories: {
      general: 'General',
      account: 'Account',
      billing: 'Billing',
      features: 'Features',
      technical: 'Technical',
      privacy: 'Privacy',
    },
  },
  features: {
    noItems: 'No features available.',
    comingSoon: 'Coming Soon',
    learnMore: 'Learn More',
  },
  shortcuts: {
    noItems: 'No shortcuts available.',
    columns: {
      shortcut: 'Shortcut',
      action: 'Action',
      description: 'Description',
    },
  },
  gettingStarted: {
    noItems: 'No guides available.',
    estimatedTime: 'Estimated time',
    difficulty: {
      beginner: 'Beginner',
      intermediate: 'Intermediate',
      advanced: 'Advanced',
    },
  },
  changelog: {
    noItems: 'No changelog available.',
    showAll: 'Show all releases',
    types: { major: 'Major', minor: 'Minor', patch: 'Patch', beta: 'Beta' },
    labels: {
      features: 'New Features',
      improvements: 'Improvements',
      bugFixes: 'Bug Fixes',
    },
  },
  contact: {
    noInfo: 'No contact info available.',
    email: 'Send email',
    responseTime: 'Response time',
  },
  common: {
    back: 'Back',
    showMore: 'Show more',
    showLess: 'Show less',
  },
};

Content Types

FAQ

{
  id: string;           // Unique ID
  question: string;     // The question
  answer: string;       // HTML answer (auto-sanitized)
  category: 'general' | 'account' | 'billing' | 'features' | 'technical' | 'privacy';
  order: number;
  language: 'en' | 'de' | 'fr' | 'it' | 'es';
  featured?: boolean;
  tags?: string[];
}

Shortcuts

{
  id: string;
  category: 'navigation' | 'editing' | 'general' | 'app-specific';
  language: string;
  order: number;
  shortcuts: Array<{
    shortcut: string;   // e.g. "Cmd/Ctrl + K"
    action: string;     // e.g. "Open search"
    description?: string;
  }>;
}

Contact

{
  id: string;
  title: string;
  content: string;          // HTML (auto-sanitized)
  language: string;
  order: number;
  supportEmail?: string;
  supportUrl?: string;
  discordUrl?: string;
  documentationUrl?: string;
  responseTime?: string;
}

Security

All HTML content is automatically sanitized via isomorphic-dompurify in the parser layer. Content passed through {@html} in components is safe against XSS.

Reference Implementation

See apps/contacts/apps/web/src/routes/(app)/help/+page.svelte for a complete working example.