'
+```
+
+Key architectural points:
+
+- JSX elements are **always strings or Promises** (never React-like objects)
+- No virtual DOM - direct string generation
+- Async components propagate up the tree (if child is async, parent becomes async)
+
+### Monorepo Structure
+
+- **Workspace-based**: Uses pnpm workspaces with shared catalog for dependencies
+- **Independent versioning**: Each package has its own version via changesets
+- **Shared configuration**: Root-level TypeScript and Prettier configs
+
+## Key Design Patterns
+
+### 1. String Building Pattern
+
+Core pattern: Efficient string concatenation without intermediate objects
+
+```javascript
+// From index.js
+function createElement(tag, attrs, ...children) {
+ return `<${tag}${attributesToString(attrs)}>${contentsToString(children)}${tag}>`;
+}
+```
+
+### 2. Async/Await Pattern
+
+Async components are seamlessly integrated:
+
+- Sync components return `string`
+- Async components return `Promise`
+- Type system tracks async propagation via `JSX.Element` type
+
+### 3. Security by Default Pattern
+
+- **Attributes**: Auto-escaped by default
+- **Children**: Must explicitly use `safe` attribute or `Html.escapeHtml()`
+- **TypeScript Plugin**: Catches XSS at compile time
+
+### 4. Suspense Pattern
+
+Streaming HTML with fallback content:
+
+```tsx
+} catch={(err) => }>
+
+
+```
+
+Uses request IDs (rid) for concurrent request safety.
+
+### 5. Error Boundary Pattern
+
+Catch errors in async component trees:
+
+```tsx
+}>
+
+
+```
+
+## Module System
+
+### Exports Pattern
+
+Each package uses explicit exports in package.json:
+
+```json
+{
+ "exports": {
+ ".": "./dist/index.js",
+ "./jsx-runtime": "./dist/jsx-runtime.js",
+ "./suspense": "./dist/suspense.js"
+ // etc.
+ }
+}
+```
+
+### CommonJS with ESM Compatibility
+
+- Main output: CommonJS
+- `esModuleInterop` enabled for compatibility
+- No ESM build currently (could be added)
+
+## Type System Patterns
+
+### 1. Namespace-based Types
+
+Uses `JSX` namespace for type definitions:
+
+```typescript
+declare namespace JSX {
+ interface IntrinsicElements {
+ div: HtmlTag & {
+ /* div-specific */
+ };
+ }
+}
+```
+
+### 2. Type Extensions
+
+Users can extend types globally:
+
+```typescript
+declare global {
+ namespace JSX {
+ interface HtmlTag {
+ 'hx-get'?: string; // HTMX support
+ }
+ }
+}
+```
+
+### 3. Conditional Types
+
+`JSX.Element` is conditionally `string | Promise` based on usage.
+
+## Testing Patterns
+
+### 1. Vitest Test Runner
+
+Uses Vitest with coverage and type checking:
+
+```typescript
+import { describe, it, expect } from 'vitest';
+
+describe('component', () => {
+ it('renders correctly', () => {
+ expect(
hello
).toBe('
hello
');
+ });
+});
+```
+
+### 2. JSDOM for DOM Testing
+
+When DOM testing is needed:
+
+```typescript
+import { JSDOM } from 'jsdom';
+const dom = new JSDOM(htmlString);
+```
+
+### 3. Vitest Type Testing
+
+For TypeScript type definitions (using vitest --typecheck):
+
+```typescript
+// Uses vitest's built-in type testing capabilities
+import { expectTypeOf } from 'vitest';
+expectTypeOf(
foo
).toEqualTypeOf();
+```
+
+## Performance Patterns
+
+### 1. Minimal Allocations
+
+- Avoid creating intermediate objects
+- Direct string concatenation where possible
+- Avoid regex when string methods suffice
+
+### 2. Void Element Optimization
+
+Special handling for self-closing tags:
+
+```javascript
+if (isVoidElement(tag)) {
+ return `<${tag}${attributesToString(attrs)}>`;
+}
+```
+
+### 3. Attribute String Building
+
+Efficient attribute serialization with kebab-case conversion:
+
+```javascript
+function attributesToString(attrs) {
+ // Optimized string building
+}
+```
+
+## Guidelines and Best Practices
+
+### Do's:
+
+- Use `safe` attribute for all user input
+- Prefer composition over prop drilling
+- Keep components pure when possible
+- Use TypeScript strict mode
+- Write tests for new features
+- Consider performance for core HTML generation
+
+### Don'ts:
+
+- Don't use React-specific patterns (hooks, context, etc.)
+- Don't create circular dependencies between packages
+- Don't bypass XSS safety features
+- Don't use `any` type without strong justification
+- Don't mix CommonJS and ESM syntax
+
+### Async Component Guidelines:
+
+- Use request IDs (rid) for Suspense components
+- Error boundaries for async error handling
+- Avoid AsyncLocalStorage (performance penalty)
+- Document when a component is async
+
+### Security Guidelines:
+
+- **ALWAYS** escape user input
+- Use `@kitajs/ts-html-plugin` to catch XSS issues
+- Review all changes to escaping logic carefully
+- Test with malicious input samples
diff --git a/.serena/memories/code_style_and_conventions.md b/.serena/memories/code_style_and_conventions.md
new file mode 100644
index 000000000..15e186620
--- /dev/null
+++ b/.serena/memories/code_style_and_conventions.md
@@ -0,0 +1,84 @@
+# Code Style and Conventions
+
+## TypeScript Configuration
+
+The project uses **strict TypeScript settings** with the following key configurations:
+
+### JSX Settings
+
+- `jsx`: "react-jsx"
+- `jsxImportSource`: "@kitajs/html"
+- `plugins`: [{ "name": "@kitajs/ts-html-plugin" }]
+
+### Module Settings
+
+- `module`: "CommonJS"
+- `moduleResolution`: "node"
+- `target`: "ESNext"
+- `esModuleInterop`: true
+
+### Strict Mode Settings (all enabled)
+
+- `strict`: true
+- `noImplicitAny`: true
+- `strictNullChecks`: true
+- `strictFunctionTypes`: true
+- `strictBindCallApply`: true
+- `strictPropertyInitialization`: true
+- `noImplicitThis`: true
+- `useUnknownInCatchVariables`: true
+- `alwaysStrict`: true
+- `noUnusedLocals`: true
+- `noUnusedParameters`: true
+- `noImplicitReturns`: true
+- `noFallthroughCasesInSwitch`: true
+- `noUncheckedIndexedAccess`: true
+- `noImplicitOverride`: true
+
+### Build Settings
+
+- Source maps and declaration maps enabled
+- Output directory: `dist`
+- Incremental compilation enabled
+
+## Formatting
+
+- **Tool**: Prettier
+- **Configuration**: Uses @arthurfiorette/prettier-config
+- **Plugins**:
+ - prettier-plugin-jsdoc
+ - prettier-plugin-organize-imports
+ - prettier-plugin-packagejson
+- **Pre-commit hook**: Automatically formats staged files before commit
+
+## Naming Conventions
+
+Based on the codebase:
+
+- Functions: camelCase (e.g., `createElement`, `attributesToString`)
+- Constants: UPPER_SNAKE_CASE (e.g., `CAMEL_REGEX`, `ESCAPED_REGEX`)
+- Variables: camelCase (e.g., `escapeHtml`)
+- Files: kebab-case for test files (e.g., `simple-html.test.tsx`)
+- Packages: scoped with @kitajs/ prefix
+
+## JSX Usage
+
+- No need to import React or Html namespace when using react-jsx transform
+- Components can be sync (return string) or async (return Promise)
+- Always use `safe` attribute or `Html.escapeHtml()` for user input to prevent XSS
+- Attributes are automatically escaped by default
+- Children content is NOT escaped by default (requires `safe` attribute)
+
+## File Organization
+
+- Source files in `src/` directory
+- Tests in `test/` directory with `.test.tsx` or `.test.ts` extension
+- Type definitions generated alongside compiled files in `dist/`
+- Build output in `dist/` directory
+- Additional type definition files (htmx.d.ts, alpine.d.ts, etc.) in package root
+
+## Documentation
+
+- Use JSDoc comments for public APIs
+- TypeScript types serve as primary documentation
+- README.md in each package with comprehensive examples
diff --git a/.serena/memories/darwin_system_commands.md b/.serena/memories/darwin_system_commands.md
new file mode 100644
index 000000000..a2ee726ef
--- /dev/null
+++ b/.serena/memories/darwin_system_commands.md
@@ -0,0 +1,202 @@
+# macOS (Darwin) System Commands
+
+The project is running on macOS (Darwin 25.2.0). Here are important system-specific
+considerations:
+
+## Standard Unix Commands Available
+
+Most standard Unix commands work on macOS:
+
+- `ls`, `cd`, `pwd`, `mkdir`, `rm`, `cp`, `mv`
+- `cat`, `less`, `head`, `tail`
+- `grep`, `find`, `sed`, `awk`
+- `chmod`, `chown`
+- `ps`, `kill`, `top`
+
+## macOS-Specific Considerations
+
+### Package Management
+
+This project uses **pnpm** for Node.js packages, enforced by preinstall hook.
+
+### File System
+
+- **Case-insensitive** by default (but case-preserving)
+- Be careful with file naming to avoid cross-platform issues
+- Use forward slashes (/) in paths, not backslashes
+
+### Command Differences from Linux
+
+Some commands have different options or behavior:
+
+- `sed -i` requires an extension argument: `sed -i '' 's/foo/bar/g'`
+- `readlink` doesn't have `-f` flag (use `greadlink` from coreutils if needed)
+- `stat` has different syntax than GNU stat
+- `xargs` may have different default behavior
+
+### Git
+
+Standard git commands work as expected:
+
+```bash
+git status
+git add .
+git commit -m "message"
+git push
+git pull
+git branch
+git checkout
+git diff
+```
+
+### Node.js & pnpm
+
+```bash
+# Node version
+node --version # Should be >= 20.13
+
+# pnpm commands
+pnpm install
+pnpm test
+pnpm build
+pnpm format
+```
+
+### Common Development Tasks
+
+#### File Search
+
+```bash
+# Find files by name
+find . -name "*.tsx"
+
+# Find files excluding node_modules
+find . -name "*.tsx" -not -path "*/node_modules/*"
+
+# Using grep for content search
+grep -r "searchTerm" packages/
+```
+
+#### Process Management
+
+```bash
+# List processes
+ps aux | grep node
+
+# Kill process by PID
+kill
+
+# Kill process by name
+pkill -f "process-name"
+```
+
+#### File Operations
+
+```bash
+# View file contents
+cat file.txt
+less file.txt
+
+# View end of log file
+tail -f logfile.txt
+
+# Count lines
+wc -l file.txt
+
+# Find and replace (macOS-specific)
+sed -i '' 's/old/new/g' file.txt
+```
+
+#### Permissions
+
+```bash
+# Make script executable
+chmod +x script.sh
+
+# Fix permission issues
+chmod 644 file.txt # rw-r--r--
+chmod 755 file.sh # rwxr-xr-x
+```
+
+### Environment
+
+#### Shell
+
+Default shell on macOS is zsh (as of Catalina+):
+
+- Shell scripts should use `#!/usr/bin/env bash` or `#!/bin/sh`
+- Environment variables work standard way: `export VAR=value`
+
+#### Paths
+
+- Home directory: `~` or `$HOME`
+- Current directory: `.`
+- Parent directory: `..`
+- Absolute paths start with `/`
+
+### Performance Monitoring
+
+```bash
+# CPU and memory usage
+top
+
+# Better alternative (if installed)
+htop
+
+# Disk usage
+du -sh *
+
+# Disk free space
+df -h
+```
+
+### Networking (if needed)
+
+```bash
+# Check port usage
+lsof -i :3000
+
+# Check network connections
+netstat -an | grep LISTEN
+```
+
+## Project-Specific Commands
+
+### Most Used Git Operations
+
+```bash
+# Check status
+git status
+
+# View changes
+git diff
+
+# Add all changes
+git add .
+
+# Commit (pre-commit hook will run Prettier automatically)
+git commit -m "feat: add new feature"
+
+# Push
+git push
+
+# Pull with rebase
+git pull --rebase
+```
+
+### Testing Individual Files
+
+```bash
+# Run specific test file (after building)
+node --test dist/test/specific-test.test.js
+```
+
+### Debugging
+
+```bash
+# Run with inspector
+node --inspect dist/test/test-file.js
+
+# Run with more verbose output
+NODE_OPTIONS="--trace-warnings" pnpm test
+```
diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md
new file mode 100644
index 000000000..e5172784e
--- /dev/null
+++ b/.serena/memories/project_overview.md
@@ -0,0 +1,55 @@
+# KitaJS HTML - Project Overview
+
+## Purpose
+
+KitaJS HTML is a monorepo containing a super fast JSX runtime library that generates HTML
+strings. It's designed to work with any Node.js framework (Express, Fastify, Hono,
+AdonisJS, Bun, etc.) and integrates well with HTMX, Alpine.js, and Hotwire Turbo.
+
+Key features:
+
+- JSX-based HTML generation that outputs strings
+- Type-safe HTML templates using TypeScript
+- Built-in XSS protection with TypeScript plugin
+- Support for async components with Suspense
+- Error boundaries for async error handling
+- Performance-focused (benchmarks show 7-41x faster than alternatives)
+
+## Repository Structure
+
+This is a pnpm monorepo with the following structure:
+
+### Main Packages (packages/)
+
+- **@kitajs/html** - Core JSX runtime for HTML generation
+- **@kitajs/ts-html-plugin** - TypeScript LSP plugin for XSS detection and validation
+- **@kitajs/fastify-html-plugin** - Fastify integration plugin
+
+### Additional Directories
+
+- **benchmarks/** - Performance benchmarks comparing with React, Typed Html, etc.
+- **examples/** - Example code demonstrating usage
+- **.husky/** - Git hooks configuration
+- **.github/** - GitHub workflows and CI/CD
+- **.changeset/** - Changesets for version management
+
+## Tech Stack
+
+- **Language**: TypeScript 5.9+
+- **Runtime**: Node.js >= 20.13
+- **Package Manager**: pnpm >= 10 (required via preinstall hook)
+- **JSX Transform**: react-jsx with jsxImportSource: @kitajs/html
+- **Module System**: CommonJS
+- **Build Tool**: tsgo (@typescript/native-preview)
+- **Testing**: Vitest with @vitest/coverage-v8
+- **Formatting**: Prettier with @arthurfiorette/prettier-config
+- **Git Hooks**: Husky
+- **Versioning**: Changesets with GitHub changelog integration
+
+## Key Dependencies
+
+- csstype (for CSS types)
+- fastify-plugin (for Fastify integration)
+- TypeScript, tslib (build tools)
+- JSDOM (for DOM testing)
+- Vitest with v8 coverage
diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md
new file mode 100644
index 000000000..432a06086
--- /dev/null
+++ b/.serena/memories/suggested_commands.md
@@ -0,0 +1,141 @@
+# Suggested Development Commands
+
+## Package Manager
+
+**Always use pnpm** - The project enforces this via preinstall hook. Requires pnpm >= 10.
+
+## Main Development Commands
+
+### Building
+
+```bash
+# Build all packages
+pnpm build
+
+# Build specific package (from root)
+pnpm --filter "@kitajs/html" build
+
+# Build uses tsgo (native TypeScript compiler preview)
+# Each package has: pnpm build -> tsgo -p tsconfig.build.json
+```
+
+### Testing
+
+```bash
+# Run all tests in all packages
+pnpm test
+
+# Run tests in a specific package
+pnpm --filter "@kitajs/html" test
+cd packages/html && pnpm test
+
+# Test command runs Vitest with coverage and type checking:
+# vitest --coverage --typecheck --run
+```
+
+### Formatting
+
+```bash
+# Format all files
+pnpm format
+
+# Format specific files (Prettier)
+prettier --write
+```
+
+### Benchmarking
+
+```bash
+# Run performance benchmarks
+pnpm bench
+
+# This builds benchmark packages and runs the benchmark runner
+```
+
+### Versioning & Publishing
+
+```bash
+# Create a changeset (for version bumps)
+pnpm changeset
+
+# Version packages (CI command)
+pnpm ci-version
+
+# Publish packages (CI command)
+pnpm ci-publish
+```
+
+### Git Hooks
+
+Git hooks are managed by Husky:
+
+- **pre-commit**: Automatically formats staged files with Prettier
+- Setup: `pnpm prepare` (runs `husky` command)
+
+## Per-Package Commands
+
+### @kitajs/html
+
+```bash
+cd packages/html
+pnpm build # Build with tsgo
+pnpm test # Run vitest with coverage and typecheck
+```
+
+### @kitajs/ts-html-plugin
+
+```bash
+cd packages/ts-html-plugin
+pnpm build # Build with tsgo
+pnpm test # Run vitest with coverage and typecheck
+```
+
+### @kitajs/fastify-html-plugin
+
+```bash
+cd packages/fastify-html-plugin
+pnpm build # Build with tsgo
+pnpm test # Run vitest with coverage and typecheck
+```
+
+## Common Workflows
+
+### After making changes:
+
+1. Format code: `pnpm format` (or let pre-commit hook handle it)
+2. Build: `pnpm build`
+3. Run tests: `pnpm test`
+
+### Before committing:
+
+- Pre-commit hook automatically runs Prettier on staged files
+- No manual action needed
+
+### Testing a single package:
+
+```bash
+pnpm --filter "@kitajs/html" test
+```
+
+### Working with workspace:
+
+```bash
+# Install dependencies
+pnpm install
+
+# Run command in all packages
+pnpm -r
+
+# Run command in specific package
+pnpm --filter ""
+
+# Run commands in parallel
+pnpm -r --parallel
+```
+
+### Running examples:
+
+```bash
+npx tsx examples/fastify-htmx.tsx
+npx tsx examples/http-server.tsx
+```
diff --git a/.serena/memories/task_completion_checklist.md b/.serena/memories/task_completion_checklist.md
new file mode 100644
index 000000000..32e613a6d
--- /dev/null
+++ b/.serena/memories/task_completion_checklist.md
@@ -0,0 +1,93 @@
+# Task Completion Checklist
+
+When completing a coding task in this project, follow these steps:
+
+## 1. Code Quality
+
+- [ ] TypeScript compiles without errors (`tsc`)
+- [ ] Code follows strict TypeScript settings (no `any`, proper null checks)
+- [ ] No unused variables or parameters
+- [ ] All functions have proper return types
+
+## 2. Formatting
+
+- [ ] Code is formatted with Prettier
+ - Run: `pnpm format`
+ - Or let pre-commit hook handle it automatically
+
+## 3. XSS Safety (Critical for this project!)
+
+- [ ] All user input uses `safe` attribute or `Html.escapeHtml()`
+- [ ] No raw string concatenation with user input
+- [ ] TypeScript plugin catches potential XSS vulnerabilities
+- [ ] Consider running `xss-scan` if modifying JSX/HTML generation code
+
+## 4. Testing
+
+- [ ] Run tests: `pnpm test` (from root or package directory)
+- [ ] All tests pass with no failures
+- [ ] Code coverage is maintained or improved (Vitest v8 coverage)
+- [ ] For new features: Add appropriate tests in `test/` directory
+- [ ] Type tests pass: Vitest runs with `--typecheck` flag
+
+## 5. Build
+
+- [ ] Build succeeds: `pnpm build` (uses tsgo - native TypeScript compiler)
+- [ ] No build warnings or errors
+- [ ] Type definitions (.d.ts) are correctly generated in `dist/`
+
+## 6. Documentation
+
+- [ ] Update README.md if adding new features or changing APIs
+- [ ] Add JSDoc comments for public APIs
+- [ ] Update type definitions if needed
+- [ ] Consider updating examples if relevant
+
+## 7. Performance (if applicable)
+
+- [ ] Consider running benchmarks if changes affect core HTML generation
+ - Run: `pnpm bench`
+- [ ] No performance regressions
+
+## 8. Version Management (if releasing)
+
+- [ ] Create changeset if making user-facing changes
+ - Run: `pnpm changeset`
+- [ ] Follow semantic versioning
+
+## 9. Git
+
+- [ ] Commit messages are clear and descriptive
+- [ ] Pre-commit hook has run (Prettier formatting)
+- [ ] No unnecessary files committed
+- [ ] Changes are focused and atomic
+
+## Quick Check Before Committing
+
+From the root directory:
+
+```bash
+pnpm format # Format code
+pnpm build # Build all packages
+pnpm test # Run all tests
+```
+
+If all three succeed, the code is ready to commit!
+
+## Special Considerations
+
+### For Core HTML Package (@kitajs/html)
+
+- XSS safety is CRITICAL - always verify proper escaping
+- Performance matters - avoid unnecessary allocations
+- Type safety - ensure JSX types are correct
+
+### For TypeScript Plugin (@kitajs/ts-html-plugin)
+
+- Test with real TypeScript projects
+- Ensure error messages are clear and helpful
+
+### For Fastify Plugin (@kitajs/fastify-html-plugin)
+
+- Test integration with Fastify
+- Ensure type definitions work correctly (tsd tests)
diff --git a/.serena/project.yml b/.serena/project.yml
new file mode 100644
index 000000000..5f1bc809e
--- /dev/null
+++ b/.serena/project.yml
@@ -0,0 +1,87 @@
+# list of languages for which language servers are started; choose from:
+# al bash clojure cpp csharp csharp_omnisharp
+# dart elixir elm erlang fortran fsharp
+# go groovy haskell java julia kotlin
+# lua markdown nix pascal perl php
+# powershell python python_jedi r rego ruby
+# ruby_solargraph rust scala swift terraform toml
+# typescript typescript_vts yaml zig
+# Note:
+# - For C, use cpp
+# - For JavaScript, use typescript
+# - For Free Pascal / Lazarus, use pascal
+# Special requirements:
+# - csharp: Requires the presence of a .sln file in the project folder.
+# - pascal: Requires Free Pascal Compiler (fpc) and optionally Lazarus.
+# When using multiple languages, the first language server that supports a given file will be used for that file.
+# The first language is the default language and the respective language server will be used as a fallback.
+# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
+languages:
+ - typescript
+
+# the encoding used by text files in the project
+# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
+encoding: 'utf-8'
+
+# whether to use the project's gitignore file to ignore files
+# Added on 2025-04-07
+ignore_all_files_in_gitignore: true
+
+# list of additional paths to ignore
+# same syntax as gitignore, so you can use * and **
+# Was previously called `ignored_dirs`, please update your config if you are using that.
+# Added (renamed) on 2025-04-07
+ignored_paths: []
+
+# whether the project is in read-only mode
+# If set to true, all editing tools will be disabled and attempts to use them will result in an error
+# Added on 2025-04-18
+read_only: false
+
+# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
+# Below is the complete list of tools for convenience.
+# To make sure you have the latest list of tools, and to view their descriptions,
+# execute `uv run scripts/print_tool_overview.py`.
+#
+# * `activate_project`: Activates a project by name.
+# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
+# * `create_text_file`: Creates/overwrites a file in the project directory.
+# * `delete_lines`: Deletes a range of lines within a file.
+# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
+# * `execute_shell_command`: Executes a shell command.
+# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
+# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
+# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
+# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
+# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
+# * `initial_instructions`: Gets the initial instructions for the current project.
+# Should only be used in settings where the system prompt cannot be set,
+# e.g. in clients you have no control over, like Claude Desktop.
+# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
+# * `insert_at_line`: Inserts content at a given line in a file.
+# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
+# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
+# * `list_memories`: Lists memories in Serena's project-specific memory store.
+# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
+# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
+# * `read_file`: Reads a file within the project directory.
+# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
+# * `remove_project`: Removes a project from the Serena configuration.
+# * `replace_lines`: Replaces a range of lines within a file with new content.
+# * `replace_symbol_body`: Replaces the full definition of a symbol.
+# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
+# * `search_for_pattern`: Performs a search for a pattern in the project.
+# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
+# * `switch_modes`: Activates modes by providing a list of their names
+# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
+# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
+# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
+# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
+excluded_tools: []
+
+# initial prompt for the project. It will always be given to the LLM upon activating the project
+# (contrary to the memories, which are loaded on demand).
+initial_prompt: ''
+
+project_name: 'html'
+included_optional_tools: []
diff --git a/.vscode/settings.json b/.vscode/settings.json
index edc22acec..9a44a6ada 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,9 @@
{
- "typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
- "cSpell.words": ["KITA"]
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "typescript.tsserver.maxTsServerMemory": 4096,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ }
}
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..1b4643f79
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,327 @@
+# KitaJS HTML Monorepo - Developer Guide
+
+## Overview
+
+KitaJS HTML is a monorepo containing a super-fast JSX runtime that generates HTML strings.
+Unlike React, which builds a virtual DOM, this library directly produces HTML strings,
+making it ideal for server-side rendering, static site generation, and HTMX-style
+applications.
+
+## Repository Structure
+
+```
+kitajs/html/
+├── packages/
+│ ├── html/ # Core JSX runtime (@kitajs/html)
+│ ├── ts-html-plugin/ # XSS detection TypeScript plugin (@kitajs/ts-html-plugin)
+│ └── fastify-html-plugin/# Fastify integration (@kitajs/fastify-html-plugin)
+├── benchmarks/ # Performance benchmarks
+└── examples/ # Usage examples
+```
+
+## Package Dependencies
+
+```
+@kitajs/html (core)
+ ↑
+ ├── @kitajs/ts-html-plugin (peer dependency)
+ │
+ └── @kitajs/fastify-html-plugin (peer dependency)
+```
+
+## Quick Start
+
+### Development Commands
+
+```bash
+# Install dependencies (pnpm required)
+pnpm install
+
+# Build all packages
+pnpm build
+
+# Run all tests
+pnpm test
+
+# Format code
+pnpm format
+
+# Run benchmarks
+pnpm bench
+```
+
+### Per-Package Commands
+
+```bash
+# Build specific package
+pnpm --filter "@kitajs/html" build
+
+# Test specific package
+pnpm --filter "@kitajs/ts-html-plugin" test
+
+# Run commands from package directory
+cd packages/html && pnpm test
+```
+
+## Architecture Overview
+
+### Core Concept: JSX → String
+
+```tsx
+// Input (JSX)
+
{name}
;
+
+// TypeScript transforms to
+jsx('div', { class: 'hello', children: name });
+
+// Output (string)
+('
Arthur
');
+```
+
+### Key Architectural Decisions
+
+1. **No Virtual DOM**: Direct string concatenation for maximum performance
+2. **Type as String**: `JSX.Element = string | Promise`
+3. **Async Propagation**: Promise children make parent promises
+4. **XSS by Default**: Children are NOT escaped unless `safe` attribute is used
+5. **Compile-Time Safety**: TypeScript plugin catches XSS at development time
+
+### Data Flow
+
+```
+User Code (TSX)
+ │
+ ▼
+TypeScript Compiler
+ │ (jsx: "react-jsx", jsxImportSource: "@kitajs/html")
+ ▼
+jsx-runtime.ts (jsx/jsxs functions)
+ │
+ ▼
+index.ts (createElement, attributesToString, contentsToString)
+ │
+ ▼
+HTML String (or Promise for async)
+```
+
+### XSS Protection Flow
+
+```
+User writes JSX
+ │
+ ▼
+ts-html-plugin (LSP) ─────► Warnings/Errors in Editor
+ │
+ ▼
+xss-scan (CLI) ────────────► CI/CD Pipeline Check
+ │
+ ▼
+Runtime (safe attribute) ──► Escapes at render time
+```
+
+## Package Summaries
+
+### @kitajs/html
+
+The core JSX runtime. Key files:
+
+- `src/index.ts`: Escaping, attribute handling, element creation
+- `src/jsx-runtime.ts`: Modern JSX transform (`jsx`, `jsxs`, `Fragment`)
+- `src/suspense.ts`: Streaming HTML with async components
+- `src/error-boundary.ts`: Error handling for async trees
+
+**See:** [`packages/html/CLAUDE.md`](packages/html/CLAUDE.md)
+
+### @kitajs/ts-html-plugin
+
+TypeScript plugin for XSS detection. Key files:
+
+- `src/index.ts`: Language Service Plugin entry
+- `src/cli.ts`: `xss-scan` CLI tool
+- `src/util.ts`: Core detection algorithms
+- `src/errors.ts`: Error codes (K601-K604)
+
+**See:** [`packages/ts-html-plugin/CLAUDE.md`](packages/ts-html-plugin/CLAUDE.md)
+
+### @kitajs/fastify-html-plugin
+
+Fastify integration. Key file:
+
+- `src/index.ts`: Plugin registration, `reply.html()`, Suspense streaming
+
+**See:**
+[`packages/fastify-html-plugin/CLAUDE.md`](packages/fastify-html-plugin/CLAUDE.md)
+
+## Tech Stack
+
+| Tool | Purpose |
+| ------------------- | ------------------------------------ |
+| **pnpm** | Package manager (required) |
+| **TypeScript 5.9+** | Language |
+| **tsgo** | TypeScript compiler (native preview) |
+| **Vitest** | Test runner |
+| **c8/v8** | Code coverage |
+| **Prettier** | Code formatting |
+| **Husky** | Git hooks |
+| **Changesets** | Version management |
+
+## Configuration
+
+### TypeScript (tsconfig.json)
+
+```json
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "@kitajs/html",
+ "plugins": [{ "name": "@kitajs/ts-html-plugin" }],
+ "strict": true,
+ "module": "CommonJS",
+ "target": "ESNext"
+ }
+}
+```
+
+### VSCode Settings
+
+```json
+{
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "typescript.enablePromptUseWorkspaceTsdk": true
+}
+```
+
+## Performance Patterns
+
+The codebase uses several optimization patterns:
+
+1. **Check Before Convert**: Regex test before expensive operations
+
+ ```typescript
+ if (!CAMEL_REGEX.test(camel)) return camel;
+ ```
+
+2. **Loop vs Regex**: Character loops faster than regex for replacements
+
+ ```typescript
+ for (; end < length; end++) {
+ switch (value[end]) { ... }
+ }
+ ```
+
+3. **Escape Once**: Escape entire result string, not individual pieces
+
+4. **Void Element Ordering**: Most common tags first in checks
+
+5. **Bun Detection**: Use native `Bun.escapeHTML` when available
+
+## Security Model
+
+### XSS Prevention Layers
+
+1. **Compile-Time**: `@kitajs/ts-html-plugin` catches unsafe usage
+2. **CI/CD**: `xss-scan` CLI fails builds on XSS issues
+3. **Runtime**: `safe` attribute escapes content
+
+### Safe Content Types
+
+- Numbers, booleans, bigints
+- String literals
+- `JSX.Element` (already rendered)
+- `Html.Children` type
+- Variables prefixed with `safe`
+- `Html.escapeHtml()` calls
+
+### Unsafe Content Types
+
+- `string` (dynamic)
+- `any` type
+- Objects with `toString()`
+- Variables prefixed with `unsafe`
+
+## Common Patterns
+
+### Component Definition
+
+```tsx
+import type { PropsWithChildren } from '@kitajs/html';
+
+function Card({ title, children }: PropsWithChildren<{ title: string }>) {
+ return (
+
+
{title}
+ {children}
+
+ );
+}
+```
+
+### Async Component
+
+```tsx
+async function UserProfile({ id }: { id: string }) {
+ const user = await db.getUser(id);
+ return
{user.name}
;
+}
+```
+
+### Suspense Usage
+
+```tsx
+function Page({ rid }: { rid: number }) {
+ return (
+ } catch={(e) => }>
+
+
+ );
+}
+
+// With Fastify
+app.get('/', (req, reply) => reply.html());
+```
+
+### Conditional Classes
+
+```tsx
+
+```
+
+## Testing Guidelines
+
+1. **XSS Safety**: Always test with malicious input samples
+2. **Async Handling**: Test both sync and async component paths
+3. **Type Coverage**: Use `vitest --typecheck` for type tests
+4. **Performance**: Run benchmarks for core changes
+
+## Contribution Workflow
+
+1. Fork and clone the repository
+2. Install dependencies: `pnpm install`
+3. Make changes
+4. Format: `pnpm format`
+5. Build: `pnpm build`
+6. Test: `pnpm test`
+7. Create changeset: `pnpm changeset`
+8. Submit PR
+
+## Examples
+
+The `examples/` directory contains working examples:
+
+- `fastify-htmx.tsx`: Fastify + HTMX integration with Suspense
+- `http-server.tsx`: Plain Node.js HTTP server with streaming
+
+Run examples:
+
+```bash
+npx tsx examples/fastify-htmx.tsx
+npx tsx examples/http-server.tsx
+```
+
+## Common Gotchas
+
+1. **Children NOT escaped by default** - Always use `safe` for user input
+2. **`JSX.Element` is `string | Promise`** - Handle both cases
+3. **Suspense needs `rid`** - Use request ID for concurrent safety
+4. **Components need `Html.escapeHtml()`** - `safe` only works on native elements
+5. **pnpm required** - npm/yarn will fail on install
diff --git a/benchmarks/honojsx/index.tsx b/benchmarks/honojsx/index.tsx
index cee3faf27..13b947a63 100644
--- a/benchmarks/honojsx/index.tsx
+++ b/benchmarks/honojsx/index.tsx
@@ -11,7 +11,7 @@ function Purchase({ name, price, quantity }) {
function Layout({ children, head }) {
return (
- {head}
+ {head}
{children}
);
@@ -19,14 +19,12 @@ function Layout({ children, head }) {
function Head({ title }) {
return (
-
+
+
{title}
-
-
-
@@ -35,9 +33,11 @@ function Head({ title }) {
-
-
-
,
- /Objects are not valid as a KitaJSX child/
- );
+ () =>
{{}}
+ ).toThrow(/Objects are not valid as a KitaJSX child/);
- assert.throws(
+ expect(
//prettier-ignore
//@ts-expect-error - should warn about invalid child
- () => (
{{}} {{}}
),
- /Objects are not valid as a KitaJSX child/
- );
+ () => (
{{}} {{}}
)
+ ).toThrow(/Objects are not valid as a KitaJSX child/);
});
test('Events', () => {
- assert.equal(
- '',
+ expect(
+ ).toMatchInlineSnapshot(
+ `""`
);
- assert.equal(
- '',
+ expect(
- );
+ ).toMatchInlineSnapshot(`""`);
- assert.equal(
- '',
-
+ expect().toMatchInlineSnapshot(
+ `""`
);
});
});
diff --git a/packages/html/test/react.test.tsx b/packages/html/test/react.test.tsx
index 541a0abb6..17af05260 100644
--- a/packages/html/test/react.test.tsx
+++ b/packages/html/test/react.test.tsx
@@ -1,6 +1,5 @@
-import assert from 'node:assert';
-import test, { describe } from 'node:test';
-import Html from '../index';
+import { describe, expect, test } from 'vitest';
+import * as Html from '../src/index.js';
const Header: Html.Component = ({ children, ...attributes }) => (
- );
-
- // In case any .push() is called after the stream is closed,
- // The error below would be thrown:
- // Error [ERR_STREAM_PUSH_AFTER_EOF]: stream.push() after EOF
-
- assert.equal(await text(rendered), '');
- });
-
- it('does not allows to use the same rid', async () => {
- let i = 1;
-
- function render(r: number | string) {
- return (
- {i++}}>
- {Promise.resolve(
{i++}
)}
-
- );
- }
-
- const stream = renderToStream(render, 1);
-
- await assert.rejects(
- text(renderToStream(render, 1)),
- /Error: The provided Request Id is already in use: 1./
- );
-
- const html = await text(stream);
-
- assert.equal(
- html,
- <>
-
"`);
});
test('custom void tag', () => {
- assert.equal(, '');
+ expect().toMatchInlineSnapshot(`""`);
- assert.equal(, '');
+ expect().toMatchInlineSnapshot(`""`);
- assert.equal(
+ expect(
1
- ,
- '1'
- );
+
+ ).toMatchInlineSnapshot(`"1"`);
- assert.equal(
+ expect(
{' '}
- ,
- ' '
- );
+
+ ).toMatchInlineSnapshot(`" "`);
- assert.equal(, '');
+ expect().toMatchInlineSnapshot(
+ `""`
+ );
});
});
diff --git a/packages/html/test/util.test.ts b/packages/html/test/util.test.ts
index 9c668147d..cd31d12c7 100644
--- a/packages/html/test/util.test.ts
+++ b/packages/html/test/util.test.ts
@@ -1,10 +1,9 @@
-import assert from 'node:assert';
-import { describe, test } from 'node:test';
-import Html from '../index';
+import { describe, expect, test } from 'vitest';
+import { Html } from '../src/index.js';
describe('Util', () => {
test('Undefined contents', async () => {
- assert.equal(
+ expect(
await Html.contentsToString([
undefined,
Promise.resolve(undefined),
@@ -13,9 +12,8 @@ describe('Util', () => {
Promise.resolve(null),
[null, Promise.resolve(null)],
[[[[[[[]]]]]]]
- ]),
- ''
- );
+ ])
+ ).toBe('');
for (const i of [
undefined,
@@ -26,19 +24,18 @@ describe('Util', () => {
[null, Promise.resolve(null)],
[[[[[[[]]]]]]]
]) {
- assert.equal(await Html.contentToString(i), '');
+ expect(await Html.contentToString(i)).toBe('');
}
- assert.equal(await Html.contentsToString([]), '');
+ expect(await Html.contentsToString([])).toBe('');
});
test('Deep scaping', async () => {
- assert.equal(
- await Html.contentsToString(['<>', Promise.resolve('<>')], true),
+ expect(await Html.contentsToString(['<>', Promise.resolve('<>')], true)).toBe(
'<><>'
);
- assert.equal(
+ expect(
await Html.contentsToString(
[
undefined,
@@ -50,24 +47,22 @@ describe('Util', () => {
[[[[[[['<>']]]]]]]
],
true
- ),
- '<>'
- );
+ )
+ ).toBe('<>');
});
test('String contents', async () => {
- assert.equal(
+ expect(
await Html.contentsToString([
'a',
Promise.resolve('b'),
['c', Promise.resolve('d')]
- ]),
- 'abcd'
- );
+ ])
+ ).toBe('abcd');
});
test('Only string contents', async () => {
- assert.equal(await Html.contentsToString(['a', 'b', ['c', 'd']]), 'abcd');
+ expect(await Html.contentsToString(['a', 'b', ['c', 'd']])).toBe('abcd');
});
test('Promises', async () => {
@@ -77,11 +72,11 @@ describe('Util', () => {
Promise.resolve(['c', Promise.resolve('d')])
]);
- assert.ok(result instanceof Promise);
- assert.equal(await result, 'abcd');
+ expect(result instanceof Promise).toBeTruthy();
+ expect(await result).toBe('abcd');
});
test('h() function', async () => {
- assert.equal(Html.h, Html.createElement);
+ expect(Html.h).toBe(Html.createElement);
});
});
diff --git a/packages/html/tsconfig.build.json b/packages/html/tsconfig.build.json
new file mode 100644
index 000000000..ed84364ea
--- /dev/null
+++ b/packages/html/tsconfig.build.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "noEmit": false,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "tsBuildInfoFile": "dist/.tsbuildinfo"
+ },
+ "include": ["src"]
+}
diff --git a/packages/html/tsconfig.json b/packages/html/tsconfig.json
index 3833b5846..b5297ad81 100644
--- a/packages/html/tsconfig.json
+++ b/packages/html/tsconfig.json
@@ -1,38 +1,11 @@
{
+ "extends": "../../tsconfig.json",
"compilerOptions": {
- "target": "ESNext",
"jsx": "react-jsx",
- "jsxImportSource": "..",
- "plugins": [{ "name": "@kitajs/ts-html-plugin" }],
- "module": "CommonJS",
- "moduleResolution": "node",
- "incremental": true,
- "allowJs": true,
- "checkJs": true,
- "esModuleInterop": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noImplicitAny": true,
- "strictNullChecks": true,
- "declaration": true,
- "importHelpers": true,
- "declarationMap": true,
- "sourceMap": true,
- "strictFunctionTypes": true,
- "strictBindCallApply": true,
- "strictPropertyInitialization": true,
- "noImplicitThis": true,
- "useUnknownInCatchVariables": true,
- "alwaysStrict": true,
- "outDir": "dist",
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedIndexedAccess": true,
- "noImplicitOverride": true,
- "skipDefaultLibCheck": true
+ "jsxImportSource": "self-html",
+ "outDir": "./dist",
+ "skipLibCheck": false
},
- "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.d.ts", "node_modules/csstype"],
- "exclude": ["node_modules", "dist", "coverage", "benchmarks"]
+ "include": ["src", "test"],
+ "exclude": ["dist"]
}
diff --git a/packages/ts-html-plugin/CLAUDE.md b/packages/ts-html-plugin/CLAUDE.md
new file mode 100644
index 000000000..12e7dfb1f
--- /dev/null
+++ b/packages/ts-html-plugin/CLAUDE.md
@@ -0,0 +1,287 @@
+# @kitajs/ts-html-plugin - Developer Guide
+
+## Overview
+
+`@kitajs/ts-html-plugin` is a TypeScript Language Service Plugin and CLI tool that detects
+XSS vulnerabilities in JSX code at compile time. It integrates with your editor's
+TypeScript service to show real-time warnings and errors for potentially unsafe HTML
+content.
+
+## Architecture
+
+### Dual Mode Operation
+
+The plugin operates in two modes:
+
+1. **LSP Mode**: As a TypeScript Language Service Plugin, providing real-time diagnostics
+ in editors
+2. **CLI Mode**: As a standalone `xss-scan` command for CI/CD pipelines
+
+### File Structure
+
+```
+src/
+├── index.ts # TypeScript Language Service Plugin entry point
+├── cli.ts # CLI tool implementation (xss-scan)
+├── util.ts # Core XSS detection logic and helpers
+└── errors.ts # Error code definitions (K601-K604)
+```
+
+### Key Components
+
+#### `index.ts` - Language Service Plugin
+
+The plugin entry point that hooks into TypeScript's language service:
+
+```typescript
+module.exports = function (modules: { typescript: typeof TS }) {
+ return {
+ create(info: server.PluginCreateInfo) {
+ const proxy = proxyObject(info.languageService);
+
+ proxy.getSemanticDiagnostics = function (filename) {
+ const diagnostics = info.languageService.getSemanticDiagnostics(filename);
+
+ // Only process .tsx/.jsx files
+ if (!filename.endsWith('.tsx') && !filename.endsWith('.jsx')) {
+ return diagnostics;
+ }
+
+ // Walk the AST and add XSS diagnostics
+ ts.forEachChild(source, (node) => {
+ recursiveDiagnoseJsxElements(ts, node, typeChecker, diagnostics);
+ });
+
+ return diagnostics;
+ };
+
+ return proxy;
+ }
+ };
+};
+```
+
+#### `cli.ts` - XSS Scanner CLI
+
+Standalone command-line tool for scanning projects:
+
+- Parses `tsconfig.json` to get compiler options and file list
+- Creates a TypeScript program
+- Runs the same XSS detection logic as the LSP
+- Outputs colored diagnostics
+- Exit codes: 0 (clean), 1 (errors), 2 (warnings only)
+
+#### `util.ts` - Detection Logic
+
+Core XSS detection algorithms:
+
+**`recursiveDiagnoseJsxElements`**: Entry point for JSX tree traversal
+
+- Walks the AST depth-first
+- Calls `diagnoseJsxElement` for each JSX node
+- Deduplicates diagnostics by position
+
+**`diagnoseJsxElement`**: Analyzes a single JSX element
+
+- Checks for `script` tags (always allowed)
+- Handles `safe` attribute logic (K602 double escape, K604 unused safe)
+- Iterates through JSX expressions in children
+
+**`diagnoseExpression`**: Analyzes expressions within JSX
+
+- Unwraps parentheses and handles nested JSX
+- Recurses through ternary/binary expressions
+- Checks type safety with `isSafeAttribute`
+
+**`isSafeAttribute`**: The core safety check
+
+- Returns `true` if the type is safe to render unescaped
+- Safe types: numbers, booleans, literals, `JSX.Element`, `Html.Children`
+- Unsafe types: `string`, `any`, objects with `toString()`
+- Special cases: `safe`-prefixed variables, `escapeHtml()` calls
+
+#### `errors.ts` - Error Definitions
+
+Four error codes with documentation links:
+
+| Code | Severity | Description |
+| ---- | -------- | ----------------------------------------------------- |
+| K601 | Error | XSS-prone content without `safe` attribute |
+| K602 | Error | Double escaping detected (safe + inner JSX) |
+| K603 | Error | XSS in component children (needs `Html.escapeHtml()`) |
+| K604 | Warning | Unnecessary `safe` attribute on safe content |
+
+## Detection Algorithm
+
+### What Makes Content "Safe"?
+
+```typescript
+function isSafeAttribute(ts, type, checker, node): boolean {
+ // 1. `children` prop from PropsWithChildren - always safe
+ if (node.name?.text === 'children') return true;
+
+ // 2. Variables initialized with JSX - safe
+ if (decl.initializer && isJsx(ts, decl.initializer)) return true;
+
+ // 3. `any` type - NEVER safe
+ if (type.flags & ts.TypeFlags.Any) return false;
+
+ // 4. JSX.Element alias - safe
+ if (type.aliasSymbol?.escapedName === 'Element') return true;
+
+ // 5. Html.Children alias - safe
+ if (type.aliasSymbol?.escapedName === 'Children') return true;
+
+ // 6. Union types - all members must be safe
+ if (type.isUnionOrIntersection()) {
+ return type.types.every((t) => isSafeAttribute(ts, t, checker, node));
+ }
+
+ // 7. Non-string primitives (number, boolean, etc.) - safe
+ if (!(type.flags & ts.TypeFlags.String)) return true;
+
+ // 8. Variables starting with "safe" - suppressed
+ if (text.startsWith('safe')) return true;
+
+ // 9. escapeHtml() calls - safe
+ if (text.match(/^(\w+\.)?(escapeHtml|e`|escape)/i)) return true;
+
+ return false;
+}
+```
+
+### Expression Handling
+
+Binary and ternary expressions are analyzed on both branches:
+
+```typescript
+// Both sides are checked, even if one never executes
+
{condition ? safeValue : unsafeValue}
+// ^^^^^^^^^^^ Error!
+
+// Boolean operators are ignored (result is boolean)
+
{a === b}
// OK
+```
+
+### Component vs Element Detection
+
+The plugin distinguishes between native elements and components:
+
+```typescript
+// Native element - use `safe` attribute
+
{ userInput } <
+ /div> / / K601 <
+ // Component - use Html.escapeHtml()
+ Component >
+ { userInput } <
+ /Component> / / K603;
+```
+
+Detection is based on tag name casing (uppercase = component).
+
+## Development
+
+### Building
+
+```bash
+pnpm build # Compiles TypeScript with tsgo
+```
+
+### Testing
+
+```bash
+pnpm test # Runs vitest with coverage and type checking
+```
+
+The tests use the `KITA_TS_HTML_PLUGIN_TESTING` environment variable to enable special
+handling for monorepo paths.
+
+### Running the CLI
+
+```bash
+# After building
+node dist/cli.js --help
+node dist/cli.js # Scan current project
+node dist/cli.js src/file.tsx # Scan specific files
+```
+
+## Integration
+
+### Editor Setup
+
+```json
+// tsconfig.json
+{
+ "compilerOptions": {
+ "plugins": [{ "name": "@kitajs/ts-html-plugin" }]
+ }
+}
+```
+
+```json
+// .vscode/settings.json
+{
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "typescript.enablePromptUseWorkspaceTsdk": true
+}
+```
+
+### CI/CD Integration
+
+```json
+// package.json
+{
+ "scripts": {
+ "test": "xss-scan && vitest"
+ }
+}
+```
+
+## Key Patterns
+
+### Proxy Pattern for Language Service
+
+The plugin wraps the original language service methods:
+
+```typescript
+export function proxyObject(obj: T): T {
+ const proxy: T = Object.create(null);
+ for (const k of Object.keys(obj) as Array) {
+ const x = obj[k]!;
+ proxy[k] = (...args) => x.apply(obj, args);
+ }
+ return proxy;
+}
+```
+
+### Diagnostic Creation
+
+```typescript
+function diagnostic(node, error, category): ts.Diagnostic {
+ return {
+ category: ts.DiagnosticCategory[category],
+ messageText: Errors[error].message,
+ code: Errors[error].code,
+ file: node.getSourceFile(),
+ length: node.getWidth(),
+ start: node.getStart()
+ };
+}
+```
+
+## Suppression Techniques
+
+Users can suppress warnings in several ways:
+
+1. **`safe` attribute**: `
{content}
`
+2. **`safe`-prefixed variable**: `const safeContent = content;`
+3. **Cast to 'safe'**: `{content as 'safe'}`
+4. **escapeHtml call**: `{Html.escapeHtml(content)}`
+
+## Common Gotchas
+
+1. **Script tags are exempt**: Content inside `