Live Neovim sessions are user-owned state, and any external process that wants to inspect them must compose its queries out of pure read-only APIs: nvim_list_wins, nvim_win_get_buf, nvim_buf_get_name, nvim_win_get_width/nvim_win_get_height, vim.fn.win_screenpos, nvim_get_hl. Never :normal, never mode switches, never zooming. A read feature (preview, status, picker) that mutates mode to scrape contents is a latent user-facing bug; reaching for :normal or window-zoom is a sign you haven’t found the right read API yet.
When mapping a Neovim window to outer-terminal coordinates, use vim.fn.win_screenpos (absolute, 1-indexed, includes the tabline offset), not nvim_win_get_position (editor-area-relative, excludes the tabline). Mixing the two silently produces off-by-tabline captures that look right for tabline-less Neovim setups and break otherwise. The nvim_win_get_position form is correct only when you’re already working in editor-area coordinates.
Two mutation traps reinforce why read-only is the floor. Terminal mode is sticky over RPC, so calling nvim_exec2(":normal ...") against a terminal-mode window fails with E5108: Can't re-enter normal mode from terminal mode. An RPC that zooms a terminal window before failing can leave the user’s input state corrupted (keystrokes producing garbage until restart). Both are evidence the API surface is the design-time signal: if you need mutation to make your design work, your design is wrong.
nvim_get_hl(0, {name="Normal"}).bg returns the background as a packed 24-bit integer, not a hex string. Decompose via math.floor(bg/65536)%256, math.floor(bg/256)%256, bg%256 for R/G/B. When feeding Neovim window geometry to tools that speak in absolute screen coordinates (tmux capture-pane, external renderers), reach for win_screenpos first. See Tmux capture-pane Color Semantics for the consumer of these coordinates and Fzf Preview and Popup Control for where the captured window content typically renders.