High-level overview of development tasks for AI agents.
Goal: Simplify dashboard AI entries into a single unified entry across all platforms
Replaced platform-specific AI dashboard entries with a single unified entry. Previously, Windows showed "OpenCode (Preferred)" + "Agency CLI (Preview)" while macOS/Linux showed "Agency CLI (Preferred)" + "OpenCode", using an IIFE with vim.fn.has("win32") branching. This complexity is no longer needed since Agency CLI (sidekick) is now stable on all platforms including Windows.
What Changed:
-
Removed:
- Platform-specific IIFE branching logic (
vim.fn.has("win32") == 1 and ... or ...) O→ OpenCode dashboard entry (direct toggle viarequire('opencode').toggle())- Separate "Preferred"/"Preview" labeling per platform
- Platform-specific IIFE branching logic (
-
Added:
- Single unified entry:
A→ "AI Hub (Agent CLIs)" viarequire('sidekick.cli').select() - Works identically on Windows, macOS, and Linux
- Simplified keys table (flat array, no IIFE needed)
- Single unified entry:
OpenCode Remains Fully Accessible:
<leader>ot→ Direct OpenCode toggle (keymap unchanged)<leader>o*→ All OpenCode keymaps remain functional- Sidekick tool picker (
Aon dashboard) → Can launch OpenCode from tool list - OpenCode plugin configuration and sidekick tool integration unchanged
Why:
- Agency CLI (psmux backend) is now stable on Windows, eliminating the "Preview" distinction
- Single entry reduces dashboard clutter and decision fatigue
- Sidekick tool picker provides access to all AI tools (OpenCode, Claude, Copilot, etc.)
- Platform-specific branching was unnecessary complexity
Impact: Simplified dashboard with single AI entry point, consistent across all platforms
Branch: user/johnmutuma/windows-terminal-CTRL_V_binding
Files: 1 file modified (snacks.lua)
Line changes: +8, -27 lines
Goal: Fix Tab key not accepting Copilot suggestions in Neovim
Resolved critical issue where pressing Tab with Copilot ghost text visible was inserting a tab character instead of accepting the suggestion. Root cause was timing/buffer-attachment issues in Copilot's built-in keymap registration system.
Root Cause:
Copilot's automatic keymap system (suggestion.setup() → buf_attach() → set_keymap()) was unreliable:
suggestion.setup()and autocmds were being created successfully- However, keymaps were NOT registering on buffer attachment in live sessions
- Neovim's built-in
vim.snippet.jumpTab mapping (sid: -8) remained active InsertEnterevent timing exacerbated the registration failure
Diagnostic Evidence:
-- BEFORE fix:
:verbose map <Tab> → "vim.snippet.jump if active, otherwise <Tab>" (Neovim built-in)
require('copilot.suggestion').context → {} (empty, no buffers tracked)
-- AFTER fix:
:verbose map <Tab> → "Copilot: Accept suggestion or Tab" (custom keymap)
Tab reliably accepts suggestions when is_visible() returns trueSolution:
Bypassed Copilot's unreliable built-in keymap system entirely:
-
Disabled Built-in Keymaps (
copilot.lualines 11-17):keymap = { accept = false, accept_word = false, accept_line = false, next = false, prev = false, dismiss = false, }
-
Manual Keymap Registration (new file
keymaps/copilot.lua):- Tab: Accept suggestion with
is_visible()check, fallback to snippet jump or tab - Alt+]: Cycle to next suggestion
- Alt+[: Cycle to previous suggestion
- Ctrl+]: Dismiss suggestion
- Tab: Accept suggestion with
-
Improved Loading Timing:
- Changed event from
InsertEnter→VimEnter - Load keymaps via
common_utils.map()after plugin setup
- Changed event from
Tab Keymap Logic:
function()
local suggestion = require("copilot.suggestion")
if suggestion.is_visible() then
suggestion.accept()
return ""
else
return vim.snippet.active({ direction = 1 })
and "<Cmd>lua vim.snippet.jump(1)<CR>"
or "<Tab>"
end
endWhy Manual Registration Works:
- Copilot's
register_keymap_with_passthrough()relies on buffer attachment timing - Manual
vim.keymap.set()after plugin loads guarantees keymap exists - Follows project conventions (consistent with telescope, opencode, sidekick keymaps)
- Direct access to
suggestion.is_visible()API for reliable check
Technical Investigation:
Examined Copilot plugin source code to understand failure:
suggestion/init.lualine 124-139:set_keymap()functionkeymaps/init.lualine 77-126:register_keymap_with_passthrough()client/init.lualine 104-106: Buffer attach logic- Confirmed autocmds exist but keymaps don't register in practice
Keybindings:
Tab: Accept suggestion (or snippet jump/normal tab if no suggestion)Alt+]: Next suggestionAlt+[: Previous suggestionCtrl+]: Dismiss suggestion
Impact: Tab reliably accepts suggestions, users can cycle through alternatives before accepting
Branch: develop
Files: 2 files (copilot.lua modified, keymaps/copilot.lua created)
Line changes: +67, -3 lines
Commits: 4b5ac56 (initial fix), dbb51a2 (next/prev keymaps)
Goal: Add Agency CLI to Snacks dashboard with platform-specific ordering
Refactored Snacks dashboard to display both OpenCode and Agency CLI with platform-aware prioritization. Windows shows OpenCode as preferred (stable), macOS/Linux shows Agency CLI as preferred (since it can launch OpenCode via sidekick tool picker).
Platform-Specific Entries:
-
Windows Dashboard:
O→ OpenCode (Preferred) - Direct toggle, stable on WindowsA→ Agency CLI (Preview) - Opens sidekick tool picker, preview status
-
macOS/Linux Dashboard:
A→ Agency CLI (Preferred) - Opens sidekick tool picker (can launch OpenCode)O→ OpenCode - Direct toggle, secondary option
Implementation Details:
- Structure: Keys table split into three parts (
keys_before,ai_entries,keys_after) merged withvim.list_extend - Platform Detection:
vim.fn.has("win32") == 1determines which entry set to use - Icons: Both use
(AI/robot icon) for visual consistency - Actions:
- OpenCode:
:lua require('opencode').toggle()(direct toggle) - Agency CLI:
:lua require('sidekick.cli').select()(tool picker)
- OpenCode:
- IIFE Pattern: Dashboard keys use immediately-invoked function expression to build dynamic array
Why Agency CLI Primary on macOS/Linux:
- Sidekick tool picker provides access to ALL AI tools (OpenCode, Claude, Copilot, etc.)
- Can launch OpenCode from within sidekick if needed
- More flexible entry point for multi-tool workflows
- Works better with zellij backend (no colorscheme issues)
Why OpenCode Primary on Windows:
- More stable/mature on Windows platform
- Agency CLI marked as "Preview" due to psmux limitations (scrolling, newline, set-option issues)
- Direct OpenCode toggle provides immediate access to stable tool
Code Pattern:
keys = (function()
local keys_before = { -- f, n, t, F, g, r, G entries -- }
local ai_entries = vim.fn.has("win32") == 1 and {
{ icon = " ", key = "O", desc = "OpenCode (Preferred)", ... },
{ icon = " ", key = "A", desc = "Agency CLI (Preview)", ... },
} or {
{ icon = " ", key = "A", desc = "Agency CLI (Preferred)", ... },
{ icon = " ", key = "O", desc = "OpenCode", ... },
}
local keys_after = { -- l, c, s, m, q entries -- }
return vim.list_extend(vim.list_extend(keys_before, ai_entries), keys_after)
end)(),Impact: Users see preferred AI tool first, both tools accessible via single key, platform-appropriate recommendations
Branch: feat/sidekick-ctrl-p-fix
Files: 1 file modified (snacks.lua)
Line changes: +81, -51 (139 lines total, was 109)
Commit: 82f0ef1
Goal: Add complete sidekick.nvim plugin configuration with clean keymap organization
Implemented full sidekick.nvim configuration with platform-specific multiplexer backend (psmux on Windows, zellij on macOS/Linux), tool-specific overrides, and extracted all keymaps to dedicated customizations file following project conventions. Part of the broader OpenCode Ctrl+P fix initiative.
Configuration Components:
-
Multiplexer Backend (platform-specific):
- Windows: Uses tmux backend (provided by psmux installation)
- macOS/Linux: Uses zellij backend (preferred to avoid tmux colorscheme issues)
- Detection:
vim.fn.has("win32") == 1 and "tmux" or "zellij" - psmux: Native Windows terminal multiplexer in Rust, 100% tmux-compatible
- Installs
psmux,pmux, andtmuxbinaries (sidekick detectstmuxon PATH) - Full multiplexer session support on all platforms
-
Terminal Window Layout:
- Position: Right side of screen (
layout = "right") - Width: 45% of screen (
split.width = 0.45) - Height: Full height auto-calculated (
split.height = 0) - Matches OpenCode terminal dimensions for consistency
- Position: Right side of screen (
-
Tool-Specific Overrides:
- OpenCode: Alt+P for sidekick prompt (frees Ctrl+P for OpenCode's command list)
- Copilot: Ctrl+S for submit/send (custom keybinding for terminal interaction)
-
Keymap Organization:
- Extracted all 10 keybindings to
customizations/keymaps/sidekick.lua - Converted from lazy.nvim
keystable tonairovim.KeymapDef[]format - Loaded via
common.map()utility in pluginconfigfunction - Maintains consistency with other plugin keymaps (telescope, opencode, lazygit, etc.)
- Extracted all 10 keybindings to
Keybindings Included:
| Key | Mode | Action | Description |
|---|---|---|---|
<tab> |
n | nes_jump_or_apply() |
Next Edit Suggestion jump/apply |
<c-.> |
n,t,i,x | toggle() |
Toggle sidekick CLI |
<leader>aa |
n | toggle() |
Toggle sidekick CLI (mnemonic) |
<leader>as |
n | select() |
Select CLI tool |
<leader>ad |
n | close() |
Detach/close session |
<leader>at |
n,x | send({ msg = "{this}" }) |
Send current context |
<leader>af |
n | send({ msg = "{file}" }) |
Send entire file |
<leader>av |
x | send({ msg = "{selection}" }) |
Send visual selection |
<leader>ap |
n,x | prompt() |
Select prompt |
<leader>ac |
n | toggle({ name = "claude" }) |
Toggle Claude directly |
Implementation Pattern:
-- Plugin file: nvim/lua/nairovim/plugins/ai/sidekick.lua
config = function(_, opts)
require("sidekick").setup(opts)
-- Load keymaps from customizations
local mappings = require("nairovim.plugins.customizations.keymaps.sidekick").mappings
local common_utils = require("nairovim.utils.common")
common_utils.map(mappings)
endFiles Structure:
nvim/lua/nairovim/plugins/
├── ai/
│ └── sidekick.lua # Plugin configuration + tool overrides
└── customizations/
└── keymaps/
└── sidekick.lua # All keybindings (nairovim.KeymapDef[])
Impact: Clean separation of concerns, follows project conventions, maintainable keymap organization
Branch: feat/sidekick-ctrl-p-fix
Files: 2 new files (sidekick.lua, keymaps/sidekick.lua)
Line changes: +152 lines total
Commits: 6 commits (plugin config, lazy-lock update, zellij install, docs, psmux switch, psmux install)
Goal: Enable OpenCode's native Ctrl+P command list functionality when running in sidekick terminal
Applied official fix from upstream PR #176 to resolve keybinding conflict where sidekick's prompt picker was intercepting Ctrl+P before it could reach OpenCode's TUI. OpenCode v1.0.15+ uses Ctrl+P for its built-in command list menu.
Root Cause:
Sidekick's default terminal keybindings included prompt = { "<c-p>", "prompt", mode = "t" } which intercepted Ctrl+P in ALL terminal sessions. When OpenCode runs inside a sidekick terminal, this prevented OpenCode's native Ctrl+P functionality from working.
Why Setting prompt = false Didn't Work:
Initial attempts to set opts.cli.win.keys.prompt = false in user config were insufficient because:
- Sidekick's tool-specific defaults have higher precedence in the merge order
- The keybinding wasn't being created, but no alternative was configured
- Result: Ctrl+P did nothing (neither sidekick nor OpenCode handled it)
Solution Applied (PR #176):
Modified user config to override Ctrl+P for OpenCode tool specifically using tool-specific configuration:
-- File: nvim/lua/nairovim/plugins/ai/sidekick.lua
tools = {
opencode = {
-- OpenCode uses <c-p> for its own command list functionality
-- Override sidekick's default to use Alt+P instead
keys = { prompt = { "<a-p>", "prompt" } },
},
}This follows the same pattern as the crush tool (which also needs Ctrl+P for native functionality).
Changes Made:
-
Sidekick Config (
nvim/lua/nairovim/plugins/ai/sidekick.lualines 18-22):- Added tool-specific OpenCode override:
keys = { prompt = { "<a-p>", "prompt" } } - Frees Ctrl+P for OpenCode, moves sidekick prompt picker to Alt+P
- Added tool-specific OpenCode override:
-
Zellij Config (
~/.config/zellij/config.kdl):- Commented out line 21:
bind "Ctrl p" { SwitchToMode "normal"; }in pane mode - Commented out line 175:
bind "Ctrl p" { SwitchToMode "pane"; }in shared bindings - Reason: Zellij was intercepting Ctrl+P before it could reach OpenCode terminal
- User doesn't actively use Zellij pane mode, so removing this binding has no impact
- Backup created at
~/.config/zellij/config.kdl.backup
- Commented out line 21:
Result:
- ✅ Ctrl+P in OpenCode terminal → Opens OpenCode command list
- ✅ Alt+P in OpenCode terminal → Opens sidekick prompt/context picker
- ✅ Ctrl+P in other AI tools (Claude, Copilot) → Opens sidekick prompt picker (default behavior)
- ✅ Works with Zellij backend (no tmux colorscheme issues)
- ✅ Configuration will align with upstream when PR #176 merges
Testing:
# 1. Open OpenCode via sidekick
:lua require("sidekick.cli").toggle("opencode")
# 2. In terminal mode, press Ctrl+P
# Expected: OpenCode command list appears ✅
# 3. In terminal mode, press Alt+P
# Expected: Sidekick prompt picker appears ✅
# 4. Verify keybinding
:verbose map <c-p>
# In terminal mode: No sidekick mapping (passes through to OpenCode)Impact: OpenCode's native Ctrl+P command list now works correctly in sidekick terminals
Reference: GitHub Issue #175 | PR #176
Files: 2 files modified (sidekick.lua, zellij config.kdl)
Line changes: +5 lines (sidekick.lua), 2 lines commented (zellij config.kdl)
Goal: Fix Ctrl+R command history search not working in WSL with Vi mode enabled
Diagnosed and fixed FZF key binding issue in WSL where Ctrl+R command history search wasn't working due to plugin ordering bug and Vi mode overriding default keybindings. Works perfectly on macOS but failed silently in WSL.
Root Cause:
-
Plugin Ordering Bug:
- Line 81:
plugins=(git)- FZF plugin NOT included before Oh-My-Zsh loads - Line 83:
source $ZSH/oh-my-zsh.sh- Oh-My-Zsh loads without FZF - Lines 121-123:
plugins=(fzf)- Second plugins array defined AFTER Oh-My-Zsh loaded (has no effect)
- Line 81:
-
Version-Incompatible Manual Config:
- Lines 128-135: Manual FZF sourcing logic that fails silently on WSL
- Checks for
~/.fzf.zsh(doesn't exist in WSL) ❌ - Tries
fzf --zsh(requires v0.48.0+, WSL has v0.44.1 Debian package) ❌ - Result: No key bindings loaded at all
-
Vi Mode Override:
set -o vienables Vi mode which uses different keymap (viins/vicmd vs emacs)- FZF key-bindings must be sourced AFTER Vi mode is set
- Explicit
bindkey -M viinsneeded to ensure Ctrl+R works in Vi insert mode
Why macOS Works But WSL Doesn't:
- macOS: Has
~/.fzf.zshfrom Homebrew installation OR newer FZF version - WSL: No
~/.fzf.zsh, FZF 0.44.1 (Debian),fzf --zshnot supported
Solution:
-
Fixed Plugin Loading (Line 81):
# Before: plugins=(git) # After: plugins=(git fzf)
-
Commented Out Incorrect FZF_BASE (Line 113):
# export FZF_BASE="$HOME/.fzf" # Let Oh-My-Zsh auto-detect -
Removed Duplicate Plugins Array (Lines 121-123):
- Deleted second
plugins=(fzf)that had no effect
- Deleted second
-
Replaced Manual Config with Explicit Binding (Lines 125-132):
set -o vi if [[ -f /usr/share/doc/fzf/examples/key-bindings.zsh ]]; then source /usr/share/doc/fzf/examples/key-bindings.zsh # Explicitly bind after Vi mode bindkey -M viins '^R' fzf-history-widget bindkey -M vicmd '^R' fzf-history-widget fi
-
Kept Custom FZF Options (Lines 136-137):
export FZF_DEFAULT_OPS="--extended" export FZF_DEFAULT_COMMAND='rg --files --no-ignore-vcs --hidden'
FZF Installation Details (WSL):
- Installed:
/usr/bin/fzf(version 0.44.1 debian) - Key bindings:
/usr/share/doc/fzf/examples/key-bindings.zsh - Completion:
/usr/share/doc/fzf/examples/completion.zsh - Oh-My-Zsh plugin:
~/.oh-my-zsh/plugins/fzf/fzf.plugin.zsh
Testing Instructions:
- Open new terminal or run:
exec zsh - Run some commands to populate history:
ls,pwd,echo test - Press
Ctrl+R- should open FZF command history search - Type to filter history, press Enter to execute command
- Also test:
Ctrl+T(file search),Alt+C(directory navigation)
Verification Commands:
# Check if widget function is defined
typeset -f fzf-history-widget | head -3
# Check Vi insert mode binding
bindkey -M viins | grep "^\^R"
# Should show: "^R" fzf-history-widgetImpact: FZF command history search now works consistently across macOS and WSL with Vi mode enabled
Files: ~/.zshrc (modified, backup created)
Line changes: +9 lines added/modified, ~10 lines removed
Goal: Automatically terminate OpenCode server processes when Neovim exits
Implemented robust dual-phase cleanup system for OpenCode server processes. Handles both graceful shutdown on Neovim exit and automatic cleanup of orphaned processes from crashed sessions.
Implementation Evolution:
-
Initial Attempt (Commit
2cb01dc):- Used complex bash string with nested quotes and escaping
- Failed due to bash variable escaping issues (
\$pidevaluated incorrectly) - SIGTERM-only approach left some processes alive
-
Final Implementation (Commit
ac49a43):- Pure Lua implementation eliminates bash escaping complexity
- Two-phase cleanup strategy for comprehensive coverage
- Dual kill approach: SIGTERM → 500ms delay → SIGKILL
Dual Cleanup Strategy:
-
VimLeavePre Autocmd (Exit Cleanup):
- Finds all OpenCode processes for current
$NVIMsocket - Sends SIGTERM for graceful shutdown
- After 500ms, sends SIGKILL to force-kill stragglers
- Ensures clean exit even for stuck processes
- Finds all OpenCode processes for current
-
Startup Cleanup (Orphan Removal):
- Runs 2 seconds after Neovim starts (allows system to settle)
- Checks each OpenCode process's parent Neovim PID
- Kills processes whose parent Neovim is dead
- Prevents accumulation of orphaned processes from crashes
Process Identification:
- Uses
$NVIMenvironment variable (e.g.,/run/user/1000//nvim.88820.0) - Each Neovim instance has unique socket identifier
- OpenCode processes inherit
$NVIMfrom parent - Socket-based matching prevents cross-instance kills
Why Bash Approach Failed:
-- This failed due to $pid escaping issues:
local cmd = [[bash -c "for pid in $(pgrep ...); do kill -15 \$pid; done"]]
-- Problem: \$pid evaluated at wrong time, syntax errorsPure Lua Solution:
local pids = vim.fn.systemlist("pgrep -f 'opencode --port'")
for _, pid in ipairs(pids) do
vim.fn.system("kill -15 " .. pid)
vim.defer_fn(function() vim.fn.system("kill -9 " .. pid) end, 500)
endTesting Results:
- ✅ Orphaned process detection: Successfully identified parent PID from socket
- ✅ Dual kill strategy: SIGTERM followed by SIGKILL after 500ms
- ✅ Multi-instance safety: Preserves processes from other Neovim sessions
- ✅ Startup cleanup: Removes orphans 2 seconds after launch
Technical Insight:
Socket-based tracking ($NVIM) is superior to path-based (cwd) because:
- Path-based would kill ALL OpenCode sessions in a directory
- Socket-based only kills sessions from specific Neovim instance
- Example: Two Neovim instances in same directory → path-based kills both, socket-based differentiates
Impact: Comprehensive cleanup on exit + automatic orphan removal on startup
Commits: 2cb01dc (initial), 6873485 (docs), ac49a43 (fix)
Files: 1 file modified (opencode.lua)
Line changes: +54 lines (final implementation)
Goal: Simplify AI tooling by consolidating to OpenCode as primary AI assistant
Removed redundant AI assistant plugins (mcphub, avante, copilot-chat) to streamline configuration and reduce maintenance overhead. OpenCode.nvim now serves as the primary AI coding assistant alongside GitHub Copilot for inline completions.
Plugins Removed:
mcphub.nvim- Model Context Protocol hub (entiremcp/directory)avante.nvim- AI coding assistant with multi-provider supportCopilotChat.nvim- GitHub Copilot chat interface
Changes Made:
-
Plugin Deletions:
nvim/lua/nairovim/plugins/mcp/mcphub.lua- Deleted entire mcp directorynvim/lua/nairovim/plugins/ai/avante.lua- Deleted plugin confignvim/lua/nairovim/plugins/ai/copilot-chat.lua- Deleted plugin confignvim/lua/nairovim/plugins/customizations/highlights/avante.lua- Deleted highlightsnvim/lua/nairovim/plugins/customizations/keymaps/copilot-chat.lua- Deleted keymaps
-
Configuration Cleanup:
nvim/lua/nairovim/lazy.lua- Removed mcp plugin importnvim/lua/nairovim/plugins/lualine.lua- Removed MCP status component (58 lines), removed Avante/copilot-chat filetypesnvim/lua/nairovim/plugins/snacks.lua- Removed MCPHub dashboard action, removed commented Avante/CopilotChat actionsnvim/lua/nairovim/plugins/init.lua- Removed Avante/mcphub/copilot-chat from render-markdown filetypesnvim/lua/nairovim/plugins/ai/opencode.lua- Removed Avante/copilot-chat commentnvim/lua/nairovim/utils/workspace.lua- Removed CopilotChat configuration section
-
Documentation Updates:
docs/ARCHITECTURE.md- Updated AI plugins count (4→2), removed mcp directory from tree, removed plugin table entriesdocs/FAQ.md- Removed Avante provider switching questiondocs/TROUBLESHOOTING.md- Removed CopilotChat from build tools section
Rationale:
- OpenCode provides comprehensive AI assistance with Claude Sonnet 4.5
- Multiple overlapping AI tools created configuration complexity
- GitHub Copilot handles inline completions effectively
- Reduced plugin count improves startup time and maintainability
Impact: Simplified AI tooling stack, reduced configuration complexity, faster startup
Branch: feat/remove-mcphub-avante
Files: 14 files (7 deleted, 7 modified)
Line changes: ~400 lines removed
Goal: Fix clipboard synchronization between WSL2 and Windows host
Resolved clipboard integration issues where text copied in Neovim or OpenCode terminal wasn't reaching Windows clipboard (and vice versa). Implemented comprehensive solution with automatic provider detection and fallback mechanisms.
Root Cause:
- Neovim in WSL2 had no clipboard provider configured for Windows integration
- OpenCode terminal uses OSC 52 escape sequences that weren't bridging to Windows clipboard
- WSLg X11/Wayland clipboard is isolated from Windows host clipboard
- No automatic translation between Linux clipboard tools and Windows clipboard
Solution Components:
-
Neovim Clipboard Provider (
nvim/lua/nairovim/core/options.lua)- Auto-detects WSL environment (
vim.fn.has("wsl")) - Prefers win32yank (faster, bidirectional) when available
- Falls back to clip.exe + PowerShell for copy/paste
- Handles both
+and*registers - Automatic CRLF → LF conversion for Windows line endings
- Auto-detects WSL environment (
-
win32yank Installation
- Downloaded and installed to
~/.local/bin/win32yank.exe - Provides fast, reliable clipboard bridging
- Supports proper newline handling (
--crlf,--lfflags)
- Downloaded and installed to
-
Shell Integration (
.bashrcand.zshrc)- Added WSL-aware clipboard aliases:
pbcopy,pbpaste - OpenCode-specific helpers:
copy,paste,xclip,xsel - Exported
COPY_CMDandPASTE_CMDenvironment variables - Conditional loading based on WSL detection
- Added WSL-aware clipboard aliases:
-
Documentation (
docs/TROUBLESHOOTING.md)- New "WSL Clipboard Integration Issues" section
- Troubleshooting steps for common clipboard problems
- Installation verification procedures
- OpenCode terminal workarounds
- Updated Table of Contents
Verified Functionality:
- ✅ Neovim copy (
yy) → Windows paste (Ctrl+V) - ✅ Windows copy (Ctrl+C) → Neovim paste (
p) - ✅ Shell commands:
echo text | clip.exe - ✅ win32yank: bidirectional clipboard with proper line endings
- ✅ Automatic provider detection and fallback
Known Limitations:
- OpenCode terminal text selection (OSC 52) still requires manual clipboard commands
- Terminal selection "copied to clipboard" message doesn't bridge to Windows
- Workaround: Use
pbcopy/clip.exepiping instead of mouse selection
Impact: Full clipboard integration between WSL2 and Windows, enabling seamless workflow across environments
Branch: fix/wsl-clipboard-integration
Files: 4 files modified (options.lua, .bashrc, .zshrc, TROUBLESHOOTING.md)
Line changes: +145 lines total
Goal: Fix OpenCode global configuration loading on Windows
Discovered and fixed critical issue where OpenCode was not loading global configuration (including MCP servers) on Windows. OpenCode follows XDG Base Directory specification and looks for config at %USERPROFILE%\.config\opencode, but the install.ps1 script was incorrectly creating symlinks at %APPDATA%\opencode.
Root Cause:
- OpenCode uses XDG paths:
~/.config/opencode/opencode.json - install.ps1 was using Windows AppData:
%APPDATA%\opencode - Result: Global config (model, MCPs, agents) was not loading outside project directories
Solution:
- Updated install.ps1 to use correct XDG path:
%USERPROFILE%\.config\opencode - Set OPENCODE_CONFIG_DIR environment variable to config directory
- Migrated existing config from AppData to .config location
- Verified MCP servers load correctly in any directory
Verified MCP Loading:
- ✅ context7 (remote) - Connected
- ✅ time (local/uvx) - Connected
- ✅ chrome-devtools (local/npx) - Connected
- ✅ playwright (local/npx) - Connected
⚠️ github (remote) - Needs GITHUB_PERSONAL_ACCESS_TOKEN env var
Impact: OpenCode global configuration now loads correctly on Windows, enabling MCP servers system-wide
Files: install.ps1 (modified), existing config migrated
Line changes: ~5 lines modified in install.ps1
Goal: Create comprehensive guided learning experience for new users
Built complete 7-lesson interactive tutorial system (:Tutorial command) covering all essential NairoVIM features. Each lesson includes hands-on practice, mnemonic memory aids, and progressive skill building. Enhanced readability with strategic paragraph breaks, visual separators, and simplified command notation.
Tutorial Structure:
- Lesson 1: Basic Navigation (5min) - Movement, saving, modes
- Lesson 2: Efficient Editing (8min) - Text objects, operators, clipboard
- Lesson 3: Search & Navigation (8min) - File/string search, fuzzy finding
- Lesson 4: LSP Features (10min) - Code intelligence, definitions, diagnostics
- Lesson 5: AI Assistant (7min) - OpenCode integration, chat, explanations
- Lesson 6: Git Integration (7min) - Staging, diffing, blame, LazyGit
- Lesson 7: Advanced Tools (5min) - Splits, themes, dashboard, recap
Features:
- 41 mnemonic sections with memory aids for 60+ keybindings
- Consistent formatting with
---and━━━visual separators - Self-paced progression with space bar navigation
- Master pattern summary reinforcing learning retention
- Reset progress feature (
Rkey) with confirmation dialog for fresh starts - ~2,500 lines of instructional content across 50 minutes
Impact: Zero-to-productive onboarding path, eliminates learning curve friction
Branch: user/johnmutuma/tutorial-mvp
Files: 9 files (init.lua, 7 lessons, utils/tutorial.lua)
Commits: 11 commits (mnemonic enhancements + paragraph formatting + reset progress)
Goal: Eliminate redundant search/replace tools and consolidate terminal APIs
Removed grug-far plugin in favor of scooter for search/replace functionality. Refactored scooter to use Snacks.terminal API (replacing old toggleterm dependency). Updated Snacks dashboard to use :FindReplace command. Maintained backdrop functionality and all scooter keybindings.
Impact: Single search/replace tool, consistent terminal API across config
Files: 4 files modified, -67 lines
Goal: Improve navigation in lengthy documentation files
Added comprehensive Table of Contents to all major documentation files. README.md received 34-line TOC covering all sections. Completed missing TOC entries in FAQ, TROUBLESHOOTING, CONTRIBUTING, and ARCHITECTURE docs.
Impact: One-click navigation to any section, better discoverability
Files: 5 files modified, +39 lines
Goal: Modernize terminal strategy with GPU-accelerated terminal emulator
Strategic documentation update to position Ghostty as primary recommended terminal, relegating tmux to legacy/remote-only use cases. Updated prerequisites, installation guide, quick start workflows. Added Ghostty troubleshooting sections and comparison tables. Ghostty uses tmux-like keybindings (Ctrl+b prefix) for easy transition.
Impact: Simpler local dev setup, better performance, clear guidance
Files: 4 files (README, ARCHITECTURE, FAQ, TROUBLESHOOTING), +314 lines
Goal: Make documentation more accessible for new users
Split monolithic 1500-line README into specialized files with clear separation of concerns:
- README.md (~400 lines): User-facing quick start
- ARCHITECTURE.md (~600 lines): Technical deep-dive, 70+ plugins, 70+ keybindings
- TROUBLESHOOTING.md (~550 lines): Issue resolution by category
- FAQ.md (~300 lines): Common questions (40+ Q&As)
- CONTRIBUTING.md: Contribution guidelines
Impact: New users get started faster, easier maintenance, professional appearance
Files: Created 5 new doc files, reorganized all documentation
Goal: Comprehensive reference for keybindings and plugin ecosystem
Added detailed keybinding reference with 70+ mappings across 8 categories (File/Search, LSP, AI tools, Git, UI, etc.). Documented all 70+ plugins organized into 11 functional categories. Removed duplicate "Essential Shortcuts" section and outdated nvim-ide references.
Impact: Complete plugin/keybinding reference in one place
Files: README.md, +235, -53 lines
Goal: Modernize development environment and consolidate tools
Migrated lazygit from standalone plugin to Snacks built-in integration. Added Ghostty terminal config with TokyoNight theme and tmux-like keybindings. Added OpenCode AI assistant configuration with Claude Sonnet 4.5. Made install.sh colors theme-adaptive. Integrated bun runtime with completions. Updated 15 plugins including avante, opencode, snacks, lualine.
Impact: Cleaner plugin setup, modern terminal, AI assistant ready
Files: 12 files modified, removed 2 plugin files
Goal: Create reusable backdrop utility to reduce code duplication
Created create_backdrop() function in windows.lua for reusable backdrop management. Refactored with_win_backdrop() eliminating ~35 lines of duplication. Integrated backdrop with scooter terminal using on_open/on_close lifecycle hooks. Updated scooter config (winblend=12, width=175).
Impact: DRY code, consistent backdrop behavior across features
Files: windows.lua, scooter.lua, lazygit.lua, scooter.config.toml