Reactive web component framework guidelines for the backlog viewer. This skill should be used when writing, reviewing, migrating, or refactoring components in `packages/viewer/components/` that use `@nisli/core`. Triggers on tasks involving component creation, signal-based state, template rendering, dependency injection, emitter events, or query-based data loading.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add gkoreli/backlog-mcp --skill backlog-ui-framework --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Backlog Ui Framework?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/gkoreli-backlog-ui-framework)More formats (shields.io, HTML) on the badges page.
---
name: backlog-ui-framework
description: Reactive web component framework guidelines for the backlog viewer. This skill should be used when writing, reviewing, migrating, or refactoring components in `packages/viewer/components/` that use `@nisli/core`. Triggers on tasks involving component creation, signal-based state, template rendering, dependency injection, emitter events, or query-based data loading.
license: MIT
metadata:
author: backlog-team
version: "1.0.0"
---
# Backlog UI Framework — Agent Skill
Comprehensive guide for building reactive web components using `@nisli/core`. Contains rules across 9 categories covering component authoring, reactivity, templates, dependency injection, events, data loading, error handling, migration, and testing.
## When to Apply
Reference these guidelines when:
- Creating new web components in `viewer/components/`
- Migrating existing `HTMLElement`-based components to the reactive framework
- Writing or reviewing signal-based reactive state
- Implementing `html` tagged templates with bindings and `@event` handlers
- Using `inject()` / `provide()` for dependency injection
- Setting up `query()` for declarative data loading
- Wiring typed `Emitter` events between components
- Reviewing code for memory leaks, XSS, or lifecycle issues
- Writing tests for framework components
## Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|----------|----------|--------|--------|
| 1 | Component Authoring | CRITICAL | `comp-` |
| 2 | Signals & Reactivity | CRITICAL | `signal-` |
| 3 | Template Engine | HIGH | `tmpl-` |
| 4 | Dependency Injection | HIGH | `di-` |
| 5 | Typed Emitters | MEDIUM-HIGH | `emitter-` |
| 6 | Declarative Data Loading | MEDIUM-HIGH | `query-` |
| 7 | Error Handling & Resilience | MEDIUM | `error-` |
| 8 | Migration & Interop | MEDIUM | `migration-` |
| 9 | Testing | LOW-MEDIUM | `test-` |
## Quick Reference
### 1. Component Authoring (CRITICAL)
- `comp-setup-sync` - Setup function MUST be synchronous; capture services sync, use them async
- `comp-props-signals` - All props are `Signal<T>` in setup; factories accept `T | Signal<T>` inputs
- `comp-factory-composition` - ALL custom elements MUST use factory composition; HTML tag syntax is ONLY for native elements (div, span, button)
- `comp-html-for-vanilla` - HTML tag syntax (`<tag>`) is ONLY for native HTML elements; never for custom elements
- `comp-no-this` - No `this` in components; use pure functions with props and host
- `comp-no-innerhtml` - Never use `innerHTML`; use `html` tagged templates for targeted DOM patching
- `comp-host-escape-hatch` - `host` is the second param; use it only for imperative DOM access
- `comp-no-class-authoring` - Never extend HTMLElement directly for new components; use `component()`
- `comp-host-attrs` - Use second factory arg `{ class: '...' }` for host-level CSS classes (ADR 0009)
- `comp-prop-input` - Factory props accept `T | Signal<T>` — plain values are auto-wrapped (ADR 0009)
### 2. Signals & Reactivity (CRITICAL)
- `signal-value-read` - Always use `.value` in JS code; signals are implicit in `html` templates
- `signal-immutable-writes` - Mutating objects doesn't trigger updates; assign a new reference
- `signal-computed-derived` - Use `computed()` for derived state, not manual sync in effects
- `signal-effect-side-effects` - Effects are for side effects only (DOM, network, localStorage)
- `signal-coalesced-writes` - Multiple synchronous writes coalesce automatically; use `flush()` only when synchronous effects are needed
- `signal-no-async-in-setup-context` - `inject()`, `effect()`, `emitter.on()` must be called synchronously in setup
- `signal-untrack` - Use `untrack()` to read signals without tracking them as dependencies (ADR 0009)
- `signal-conditional-deps` - Dependencies are re-tracked on every run; conditional reads track correctly
- `signal-equality-object-is` - Signal equality uses `Object.is()`, not `===`
### 3. Template Engine (HIGH)
- `tmpl-implicit-signals` - Write `${count}` not `${count.value}` in templates
- `tmpl-event-colocated` - Use `@click=${handler}` on the element, not detached listeners
- `tmpl-event-modifiers` - Use `.stop`, `.prevent`, `.once`, `.enter`, `.escape` modifiers
- `tmpl-class-directive` - Use `class:name=${signal}` for conditional classes, not ternary soup
- `tmpl-class-attribute-safe` - Reactive class attributes use classList, safe alongside class:name directives (ADR 0007)
- `tmpl-computed-views` - Use `computed()` for multi-branch conditional rendering
- `tmpl-when-simple` - Use `when()` only for simple single-branch toggles
- `tmpl-xss-safe` - Text bindings use `textNode.data`; never parse user input as HTML
- `tmpl-comment-markers` - Framework uses `<!--bk-N-->` markers; avoid this pattern in content
### 4. Dependency Injection (HIGH)
- `di-class-as-token` - Use the class itself as the injection token; no `createToken()` for services
- `di-auto-singleton` - `inject(Class)` auto-creates a singleton; no registration needed
- `di-provide-for-overrides` - `provide()` is for testing and subtree overrides only
- `di-sync-only` - `inject()` must be called synchronously during setup
- `di-bootstrap-eager` - Bootstrap services (SSE, etc.) must be eagerly created in `main.ts`
- `di-no-failed-cache` - Failed construction is never cached; next `inject()` retries
### 5. Typed Emitters (MEDIUM-HIGH)
- `emitter-typed-events` - Extend `Emitter<T>` with a typed event map; no `CustomEvent` strings
- `emitter-inject-singleton` - Inject emitters via DI; they are auto-singleton services
- `emitter-auto-dispose` - `on()` inside component context auto-disposes on disconnect
- `emitter-to-signal` - Use `toSignal()` to bridge events into the reactive system
- `emitter-copy-on-emit` - `emit()` iterates a copy; safe to unsubscribe during callback
### 6. Declarative Data Loading (MEDIUM-HIGH)
- `query-key-function` - First arg is a key function returning an array; signals inside are tracked
- `query-cache-key` - Same cache key = same cached result; design keys for proper deduplication
- `query-generation-guard` - Stale responses are discarded via generation counter; no race conditions
- `query-enabled-guard` - Use `enabled` option to conditionally skip fetches
- `query-invalidate-prefix` - `invalidate(['tasks'])` matches all keys starting with `['tasks']`
- `query-disposed-check` - All async writes check `!disposed` before updating signals
### 7. Error Handling & Resilience (MEDIUM)
- `error-setup-boundary` - Setup errors render a fallback; sibling components unaffected
- `error-effect-survives` - Effect errors are logged, not thrown; the effect stays alive
- `error-effect-loop-guard` - Effects that re-run >100 times in 2s are auto-disposed (ADR 0009)
- `error-handler-wrapped` - `@event` handlers are try/caught; broken handlers don't crash the UI
- `error-cleanup-swallowed` - Cleanup/disposer errors are swallowed; disposal always completes
- `error-circular-detection` - Both computed and DI have circular dependency detection
### 8. Migration & Interop (MEDIUM)
- `migration-same-tag` - Keep the same custom element tag name after migration
- `migration-same-events` - Dispatch document CustomEvents until all listeners migrate
- `migration-same-api` - Maintain public method signatures via `(host as any).method = ...`
- `migration-hack-tags` - Tag every backward-compat hack: `HACK:EXPOSE`, `HACK:DOC_EVENT`, `HACK:REF`
- `migration-auto-resolve` - Template auto-resolves `_setProp` vs `setAttribute`; `class` uses classList (ADR 0007)
### 9. Testing (LOW-MEDIUM)
- `test-flush-effects` - Call `flushEffects()` after signal changes to run pending effects
- `test-cascading-flush` - Cascading effects need multiple `flushEffects()` calls
- `test-provide-mock` - Use `provide(Class, () => mock)` before `inject()` in tests
- `test-reset-injector` - Call `resetInjector()` between tests to clear singleton cache
- `test-query-client-isolated` - Provide a fresh `QueryClient` in tests for cache isolation
## How to Use
Read the full compiled document for detailed explanations and code examples: `AGENTS.md`
Each section contains:
- The invariant or rule explained
- Why it matters (the bug it prevents)
- Incorrect code example with explanation
- Correct code example with explanation
- References to framework source files
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!