doc-views
Comprehensive guide for the view system in magenta.nvim, including template literal syntax, component composition, interactive bindings, and TUI-specific rendering patterns
Install
mkdir -p .claude/skills/doc-views && curl -L -o skill.zip "https://mcp.directory/api/skills/download/3169" && unzip -o skill.zip -d .claude/skills/doc-views && rm skill.zipInstalls to .claude/skills/doc-views
About this skill
View System in magenta.nvim
THIS IS NOT REACT. DO NOT USE REACT VIEWS OR DOM. This uses a templating library for a TUI running inside a neovim buffer.
Views in magenta.nvim are built using a declarative templating approach with the d template literal tag and view functions that render controller state to neovim buffers.
Core Concepts
The view system is based on several key principles:
- Declarative rendering: Views describe what should be displayed, not how to update the buffer
- Template composition: Small view functions combine to build complex UIs
- Interactive bindings: Attach keybindings to specific regions of rendered text
- Automatic updates: Views re-render on state changes triggered by dispatched messages
Template Literal Syntax (d)
The d tag function is the foundation of the view system:
// Basic text rendering
d`This is some text`;
// Dynamic content interpolation
d`User: ${username}`;
// Conditional rendering
d`${isLoading ? d`Loading...` : d`Content loaded!`}`;
// Rendering lists
d`${items.map((item) => d`- ${item.name}\n`)}`;
// Component composition
d`Header: ${headerView({ title })}\nBody: ${bodyView({ content })}`;
Interpolation Rules
- String values are inserted as-is
- Other
dtemplates can be nested - Arrays of
dtemplates are joined together - Undefined/null values render as empty strings
- Numbers are converted to strings
Component Composition
Break down complex views into smaller, reusable functions:
function headerView(title: string) {
return d`
===================
${title}
===================
`;
}
function itemView(item: Item) {
return d`
- ${item.name}: ${item.description}
Status: ${item.status}
`;
}
function listView(items: Item[]) {
return d`
${headerView("My Items")}
${items.map((item) => itemView(item))}
`;
}
Adding Interactivity with withBindings
Attach keybindings to sections of text using withBindings:
withBindings(d`Press Enter to continue`, {
"<CR>": () => dispatch({ type: "continue" }),
q: () => dispatch({ type: "quit" }),
});
Multiple Bindings
You can attach multiple keybindings to the same text region:
withBindings(d`[Submit]`, {
"<CR>": () => dispatch({ type: "submit" }),
"<Space>": () => dispatch({ type: "submit" }),
s: () => dispatch({ type: "submit" }),
});
Bindings on Lists
Each item in a list can have its own bindings:
d`
${items.map((item, index) =>
withBindings(d`[${index + 1}] ${item.name}\n`, {
"<CR>": () => dispatch({ type: "select-item", index }),
d: () => dispatch({ type: "delete-item", index }),
}),
)}
`;
Controller View Methods
Controllers implement a view() method that returns their rendered state:
class MyController {
state: {
count: number;
items: string[];
};
view() {
return d`
Counter: ${this.state.count}
${withBindings(d`[Increment]`, {
"<CR>": () => this.myDispatch({ type: "increment" }),
})}
Items:
${this.state.items.map((item) => d`- ${item}\n`)}
`;
}
}
Rendering Cycle
- User action triggers a keybinding or command
- Binding dispatches a message
- Message flows through the dispatch system to the appropriate controller
- Controller updates its state
- View is re-rendered based on new state
- Buffer content is updated with new view
TUI-Specific Considerations
Text Alignment and Spacing
Unlike web UIs, TUI rendering requires careful attention to spacing:
// Good: Explicit newlines for vertical spacing
d`
Line 1
Line 2
Line 4 (with gap above)
`;
// Bad: Implicit spacing assumptions
d`Line 1${"\n"}Line 2`; // Hard to read
Buffer Width
Be aware that text may wrap based on terminal/buffer width:
// Consider line length when formatting
d`
This is a very long line that might wrap in narrow terminals
Consider breaking long text into multiple lines
`;
Visual Separators
Use ASCII art for visual structure:
d`
===================
Section Header
===================
Content goes here
-------------------
Footer
`;
Common Patterns
Loading States
view() {
if (this.state.loading) {
return d`Loading...`;
}
return d`
${this.state.data}
${withBindings(d`[Refresh]`, {
"<CR>": () => this.myDispatch({ type: "refresh" }),
})}
`;
}
Error Display
view() {
if (this.state.error) {
return d`
ERROR: ${this.state.error.message}
${withBindings(d`[Retry]`, {
"<CR>": () => this.myDispatch({ type: "retry" }),
})}
`;
}
// Normal view...
}
Conditional Sections
view() {
return d`
Main content
${this.state.showDetails ? d`
Details:
${this.state.details}
` : d``}
${withBindings(d`[${this.state.showDetails ? "Hide" : "Show"} Details]`, {
"<CR>": () => this.myDispatch({ type: "toggle-details" }),
})}
`;
}
Lists with Actions
view() {
return d`
Tasks:
${this.state.tasks.map((task, idx) => d`
${withBindings(d`[${task.done ? "✓" : " "}] ${task.name}`, {
"<CR>": () => this.myDispatch({ type: "toggle-task", index: idx }),
d: () => this.myDispatch({ type: "delete-task", index: idx }),
})}
`)}
${withBindings(d`[Add Task]`, {
"<CR>": () => this.myDispatch({ type: "add-task" }),
})}
`;
}
Multi-Column Layouts
Use spacing to create columns:
function formatRow(name: string, value: string) {
const nameWidth = 20;
const paddedName = name.padEnd(nameWidth);
return d`${paddedName} ${value}`;
}
view() {
return d`
${formatRow("Name:", this.state.name)}
${formatRow("Status:", this.state.status)}
${formatRow("Created:", this.state.created)}
`;
}
Best Practices
Keep Views Pure
Views should be pure functions of state - no side effects:
// Good: Pure view function
view() {
return d`Count: ${this.state.count}`;
}
// Bad: Side effects in view
view() {
this.logCount(); // Don't do this!
return d`Count: ${this.state.count}`;
}
Extract Complex Logic
Don't put complex logic in templates:
// Good: Extract to helper method
private formatItem(item: Item): string {
return `${item.name} (${item.status})`;
}
view() {
return d`${this.state.items.map((item) => d`${this.formatItem(item)}\n`)}`;
}
// Bad: Complex logic in template
view() {
return d`${this.state.items.map((item) => d`${item.name} (${item.done ? "✓" : item.pending ? "..." : "✗"})\n`)}`;
}
Use Descriptive Binding Labels
Make interactive elements obvious:
// Good: Clear interactive elements
withBindings(d`[ Submit ]`, { "<CR>": handler });
withBindings(d`Press Enter to continue`, { "<CR>": handler });
// Bad: Unclear what's interactive
withBindings(d`Submit`, { "<CR>": handler });
withBindings(d`>`, { "<CR>": handler });
Handle Empty States
Always consider what happens with empty data:
view() {
if (this.state.items.length === 0) {
return d`
No items found.
${withBindings(d`[Add Item]`, {
"<CR>": () => this.myDispatch({ type: "add" }),
})}
`;
}
return d`${this.state.items.map(/* ... */)}`;
}
Avoid Deep Nesting
Keep template nesting shallow for readability:
// Good: Flat structure with helper functions
view() {
return d`
${this.renderHeader()}
${this.renderContent()}
${this.renderFooter()}
`;
}
// Bad: Deep nesting
view() {
return d`
${this.state.show ? d`
${this.state.loading ? d`
Loading...
` : d`
${this.state.items.map((item) => d`
${item.visible ? d`${item.name}` : d``}
`)}
`}
` : d``}
`;
}
Debugging Views
Check Rendered Output
The view is rendered to a neovim buffer - you can inspect the actual buffer content to debug rendering issues:
// In tests
const bufferContent = await driver.getDisplayBuffer();
console.log(bufferContent);
Validate Bindings
Ensure bindings are attached to the correct regions:
// In tests
const pos = await driver.assertDisplayBufferContains("[Submit]");
await driver.triggerDisplayBufferKey(pos, "<CR>");
Log State
Add temporary logging to see state during rendering:
view() {
this.context.nvim.logger.debug("Rendering with state:", this.state);
return d`...`;
}
Performance Considerations
Minimize Re-renders
Only dispatch messages when state actually changes:
// Good: Check before updating
myUpdate(msg: Msg) {
if (msg.type === "set-filter") {
if (this.state.filter !== msg.filter) {
this.state.filter = msg.filter;
// View will re-render
}
}
}
// Bad: Always update
myUpdate(msg: Msg) {
if (msg.type === "set-filter") {
this.state.filter = msg.filter; // Re-renders even if same value
}
}
Avoid Expensive Computations in Views
Compute derived data in update methods, not in views:
// Good: Pre-compute in update
myUpdate(msg: Msg) {
this.state.items = msg.items;
this.state.filteredItems = this.state.items.filter(/* ... */);
}
view() {
return d`${this.state.filteredItems.map(/* ... */)}`;
}
// Bad: Compute in view
view() {
const filtered = this.state.items.filter(/* expensive filter */);
return d`${filtered.map(/* ... */)}`;
}
Common Pitfalls
Don't Mix String Concatenation with d
// Bad: Mixing strings
d`Hello ` + userName; // Wrong!
// Good: Use interpolation
d`Hello ${userName}`;
Don't Forget Newlines in Lists
// Bad: Items will run together
d`${items.map((item) => d`${item.name}`)}`;
// Good: Explicit newlines
d`${items.map((item) => d`${item.name}\n`)}`;
Don't Return Plain Strings
// Bad: Plain string
view() {
return "Hello"; // Won't work!
}
// Good: Use d template
view() {
return d`Hello`;
}
Don't Mutate State in View
// Bad: Side effects
view() {
this.state.viewCount++; // Never do this!
return d`Viewed ${this.state.viewCount} times`;
}
// Good: Pure view
view() {
return d`Viewed ${this.state.viewCount} times`;
}
More by dlants
View all →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
rust-coding-skill
UtakataKyosui
Guides Claude in writing idiomatic, efficient, well-structured Rust code using proper data modeling, traits, impl organization, macros, and build-speed best practices.
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.