Editor System Guide
SPEAR uses a unified Edra editor foundation built on Tiptap with four specialized variants for different use cases. This guide explains when to use each editor and how to configure them.
Editor Architecture Overview
Section titled “Editor Architecture Overview”All editors share a common foundation:
- Tiptap - Headless editor framework providing the core editing engine
- ProseMirror - Document model and transaction system
- Common Extensions - StarterKit, Image, Link, Table, CodeBlockLowlight, TextAlign, Placeholder, Underline, Highlight
- Shared Styling - Editor CSS from
frontend/src/lib/components/edra/editor.css
Editor Decision Matrix
Section titled “Editor Decision Matrix”flowchart TD
Start[Need to edit content?] --> Collab{Need real-time<br/>collaboration?}
Collab -->|Yes| Base[Use BaseEdraEditor]
Collab -->|No| Type{What type of content?}
Type -->|Vulnerability findings| Findings[Use FindingsRichTextEditor]
Type -->|Validation notes,<br/>service descriptions| Validation[Use ValidationNotesEditor]
Type -->|SOW templates with<br/>conditional blocks| SOW[Use SOWRichTextEditor]
Type -->|General report sections| Base
Base --> Features1[Y.js collaboration<br/>Live cursors<br/>Extensible]
Findings --> Features2[Slash commands<br/>Tables<br/>Code blocks]
Validation --> Features3[Shortcode picker<br/>Plain-text paste<br/>EditorApi]
SOW --> Features4[Conditional blocks<br/>Scoping questions<br/>Shortcodes]
| Editor | Use Case | Collaboration | Key Features |
|---|---|---|---|
| BaseEdraEditor | Report sections, general content | Yes | Y.js, live cursors, extensible |
| FindingsRichTextEditor | Vulnerability findings | No | Slash commands, tables, code blocks |
| ValidationNotesEditor | Validation notes, descriptions | No | Shortcode picker, EditorApi |
| SOWRichTextEditor | SOW templates with conditionals | No | Conditional blocks, scoping questions |
BaseEdraEditor
Section titled “BaseEdraEditor”The primary editor for report sections with optional real-time collaboration support.
Purpose
Section titled “Purpose”- Report sections requiring collaborative editing
- General rich text content with team collaboration
- Extensible editing with custom extensions
Key Features
Section titled “Key Features”- Y.js-powered real-time collaboration
- Live cursors showing collaborator positions
- Connection status indicator (Connected/Connecting/Disconnected)
- Collaborator avatar display in toolbar
- Extensible via
additionalExtensionsprop
| Prop | Type | Default | Description |
|---|---|---|---|
collaboration | boolean | false | Enable real-time collaboration |
documentId | string | - | Unique document ID for collaboration sync |
content | string | '' | Initial HTML content |
onUpdate | (html: string) => void | - | Callback when content changes |
additionalExtensions | Extension[] | [] | Additional Tiptap extensions |
placeholder | string | - | Placeholder text when empty |
minHeight | string | '200px' | Minimum editor height |
Usage Example
Section titled “Usage Example”<script lang="ts"> import BaseEdraEditor from '$lib/components/edra/BaseEdraEditor.svelte';
let content = $state('<p>Initial content</p>'); const documentId = `section-${sectionId}`;</script>
<BaseEdraEditor collaboration={true} {documentId} {content} onUpdate={(html) => content = html} placeholder="Start typing..."/>Implementation Reference
Section titled “Implementation Reference”frontend/src/lib/components/edra/BaseEdraEditor.svelte
FindingsRichTextEditor
Section titled “FindingsRichTextEditor”Specialized editor for vulnerability findings, observations, and remediation recommendations.
Purpose
Section titled “Purpose”- Vulnerability finding descriptions
- Technical observations with code samples
- Remediation recommendations with structured content
Key Features
Section titled “Key Features”- Slash commands (
/) for quick formatting - Advanced table manipulation
- Code blocks with syntax highlighting (via Lowlight)
- Optimized for technical security content
| Prop | Type | Default | Description |
|---|---|---|---|
content | string | '' | Initial HTML content |
placeholder | string | - | Placeholder text when empty |
onUpdate | (html: string) => void | - | Callback when content changes |
minHeight | string | '150px' | Minimum editor height |
Usage Example
Section titled “Usage Example”<script lang="ts"> import FindingsRichTextEditor from '$lib/components/findings/FindingsRichTextEditor.svelte';
let description = $state('');</script>
<FindingsRichTextEditor content={description} placeholder="Describe the vulnerability..." onUpdate={(html) => description = html} minHeight="200px"/>Implementation Reference
Section titled “Implementation Reference”frontend/src/lib/components/findings/FindingsRichTextEditor.svelte
Where It’s Used
Section titled “Where It’s Used”frontend/src/lib/components/section-editor/FindingsSectionEditor.svelte- Vulnerability detail editors
- Finding description fields
ValidationNotesEditor
Section titled “ValidationNotesEditor”Lightweight editor for validation notes, service descriptions, and project descriptions with shortcode support.
Purpose
Section titled “Purpose”- Manual validation notes
- Service descriptions
- Project descriptions
- Any content requiring shortcode insertion
Key Features
Section titled “Key Features”- Shortcode picker integration
- Plain-text paste handling
- EditorApi for programmatic control
- Compact toolbar for focused editing
| Prop | Type | Default | Description |
|---|---|---|---|
content | string | '' | Initial HTML content |
onUpdate | (html: string) => void | - | Callback when content changes |
onEditorReady | (api: EditorApi) => void | - | Callback with editor API reference |
placeholder | string | - | Placeholder text |
EditorApi Interface
Section titled “EditorApi Interface”interface EditorApi { insertContent(html: string): void; // Insert content at cursor focus(): void; // Focus the editor getHTML(): string; // Get current content as HTML}Usage Example
Section titled “Usage Example”<script lang="ts"> import ValidationNotesEditor from '$lib/components/vulnerabilities/ValidationNotesEditor.svelte';
let notes = $state(''); let editorApi: EditorApi | null = null;
function insertShortcode(shortcode: string) { editorApi?.insertContent(`{{${shortcode}}}`); }</script>
<ValidationNotesEditor content={notes} onUpdate={(html) => notes = html} onEditorReady={(api) => editorApi = api} placeholder="Enter validation notes..."/>
<button onclick={() => insertShortcode('project.name')}> Insert Project Name</button>Implementation Reference
Section titled “Implementation Reference”frontend/src/lib/components/vulnerabilities/ValidationNotesEditor.svelte
Where It’s Used
Section titled “Where It’s Used”frontend/src/lib/components/vulnerabilities/ManualValidationForm.svelte- Service description editors
- Project detail forms
SOWRichTextEditor
Section titled “SOWRichTextEditor”Specialized editor for SOW (Statement of Work) templates with conditional content blocks.
Purpose
Section titled “Purpose”- SOW template sections with conditional logic
- Content that varies based on scoping question responses
- Rules of Engagement (ROE) template editing
Key Features
Section titled “Key Features”- Conditional block builder
- Scoping question integration
- Visual node views for conditional blocks
- Shortcode support for dynamic content
| Prop | Type | Default | Description |
|---|---|---|---|
content | string | '' | Initial HTML content |
scopingQuestions | ScopingQuestion[] | [] | Available scoping questions for conditions |
onUpdate | (html: string) => void | - | Callback when content changes |
onEditorReady | (api: EditorApi) => void | - | Callback with editor API reference |
Conditional Block Structure
Section titled “Conditional Block Structure”Conditional blocks wrap content that should only appear when specific scoping conditions are met:
<conditional-block data-question-id="q123" data-operator="equals" data-value="true"> <p>This content only appears when the condition is met.</p></conditional-block>Usage Example
Section titled “Usage Example”<script lang="ts"> import SOWRichTextEditor from '$lib/components/services/SOWRichTextEditor.svelte';
let templateContent = $state(''); const scopingQuestions = [ { id: 'q1', text: 'Include penetration testing?', type: 'boolean' }, { id: 'q2', text: 'Target environment', type: 'select', options: ['production', 'staging'] } ];</script>
<SOWRichTextEditor content={templateContent} {scopingQuestions} onUpdate={(html) => templateContent = html}/>Implementation Reference
Section titled “Implementation Reference”frontend/src/lib/components/services/SOWRichTextEditor.svelte
Where It’s Used
Section titled “Where It’s Used”frontend/src/lib/components/sow/SowTemplateSectionEditor.svelte- SOW template management
- ROE template editing
Common Extensions
Section titled “Common Extensions”All editors include these core extensions:
| Extension | Purpose |
|---|---|
| StarterKit | Basic formatting (bold, italic, headings, lists) |
| Image | Image insertion and display |
| Link | Hyperlink support |
| Table | Table creation and editing |
| CodeBlockLowlight | Syntax-highlighted code blocks |
| TextAlign | Text alignment (left, center, right, justify) |
| Placeholder | Placeholder text when empty |
| Underline | Underline text formatting |
| Highlight | Text highlighting |
Best Practices
Section titled “Best Practices”Choose the Right Editor
Section titled “Choose the Right Editor”- Default to BaseEdraEditor for general report sections
- Use FindingsRichTextEditor only for vulnerability-specific content
- Use ValidationNotesEditor when shortcode insertion is needed
- Use SOWRichTextEditor only for SOW/ROE templates
Test Dark Mode
Section titled “Test Dark Mode”All editors support dark mode. Test your content in both light and dark modes to ensure readability.
Handle Content Updates
Section titled “Handle Content Updates”Use debouncing for onUpdate callbacks to avoid excessive saves:
import { debounce } from '$lib/utils/debounce';
const debouncedSave = debounce((html: string) => { saveContent(html);}, 500);Avoid Custom Implementations
Section titled “Avoid Custom Implementations”Don’t create custom editor implementations. If you need additional functionality, extend an existing editor using additionalExtensions.
Troubleshooting
Section titled “Troubleshooting”Content Not Updating
Section titled “Content Not Updating”- Verify
onUpdatecallback is connected - Check that content is being passed correctly
- Ensure you’re not overwriting content during save
Shortcodes Not Rendering
Section titled “Shortcodes Not Rendering”- Verify shortcode syntax:
{{shortcode.path}} - Check that shortcode extension is included
- Ensure shortcode values are resolved on the backend
Collaboration Not Working
Section titled “Collaboration Not Working”- Verify
collaboration={true}is set - Check
documentIdis unique and consistent - Ensure WebSocket connection is established
- Check browser console for connection errors
Editor Not Focusing
Section titled “Editor Not Focusing”- Use the EditorApi’s
focus()method - Check for z-index conflicts with overlays
- Verify editor container is visible
Technical References
Section titled “Technical References”| Resource | Location |
|---|---|
| BaseEdraEditor | frontend/src/lib/components/edra/BaseEdraEditor.svelte |
| FindingsRichTextEditor | frontend/src/lib/components/findings/FindingsRichTextEditor.svelte |
| ValidationNotesEditor | frontend/src/lib/components/vulnerabilities/ValidationNotesEditor.svelte |
| SOWRichTextEditor | frontend/src/lib/components/services/SOWRichTextEditor.svelte |
| Editor CSS | frontend/src/lib/components/edra/editor.css |
| Architecture Guide | EDITOR_ARCHITECTURE.md |
| Internal Guide | frontend/src/lib/components/edra/EDITOR_GUIDE.md |