summaryrefslogtreecommitdiff
path: root/AGENTS.md
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-03-20 16:41:38 +0100
committercashmere <cashmere@autistici.org>2026-03-20 16:41:38 +0100
commitaf41db8b92739bbbb273d08d1a8426cc8a08f4a5 (patch)
tree156c1d0f28e835403f285c1799fe2e526ec65314 /AGENTS.md
parent538fb833e0f4446b7a23d5a7f1636745a6196167 (diff)
downloadkitty-graphics-af41db8b92739bbbb273d08d1a8426cc8a08f4a5.tar.gz
docs: update AGENTS.md for Sixel backend architecture
- Document backend dispatch system (alist-based, detect-protocol) - Update architecture section with Kitty vs Sixel pipeline - Add Sixel-compatible terminals to testing/dependencies - Note Sixel limitations (256 colors, stateless) - Track org-mode misplacement bug as WIP - Bump documented version to 0.3.0
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md267
1 files changed, 267 insertions, 0 deletions
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..62d8813
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,267 @@
+# AGENTS.md -- kitty-graphics
+
+## Project Overview
+
+Pure Emacs Lisp package (`kitty-graphics.el`) for displaying images in
+terminal Emacs (`emacs -nw`) using either the Kitty graphics protocol or
+Sixel, with automatic protocol detection. Also renders org-mode headings
+at scaled font sizes using the Kitty text sizing protocol (OSC 66).
+Integrates with org-mode, image-mode, shr/eww/mu4e/gnus, dired, and dirvish.
+
+Language: **Emacs Lisp** (`lexical-binding: t`). Single source file (~1876 lines).
+Current version: **0.3.0** (see Versioning section).
+
+## Build / Validate
+
+### Byte-compile (primary lint check -- run after every change)
+
+```sh
+emacs -Q -batch -f batch-byte-compile kitty-graphics.el
+```
+
+**This is the closest thing to CI** -- always run it before considering
+a change complete.
+
+### Load-test (verify file evaluates without error)
+
+```sh
+emacs -Q -batch -l kitty-graphics.el -f kill-emacs
+```
+
+## Testing
+
+No automated test framework. All testing is manual/interactive and
+requires a supported terminal (Kitty, WezTerm, Ghostty for Kitty backend;
+foot, Konsole, xterm, mlterm, mintty for Sixel backend).
+The `tests/` directory contains `test-kitty-gfx.org`, `test-image.png`,
+and `test-document.pdf` for manual testing -- use these rather than
+creating new test files.
+
+**Important**: Always set `TERM=xterm-256color` when launching terminal
+Emacs -- the `xterm-kitty` terminfo is often missing.
+
+### Test with org-mode
+
+```sh
+TERM=xterm-256color emacs -nw -Q -l kitty-graphics.el \
+ --eval "(kitty-graphics-mode 1)" tests/test-kitty-gfx.org
+# Then: C-c C-x C-v (org-toggle-inline-images)
+```
+
+### Test text sizing (org heading scales)
+
+```sh
+TERM=xterm-256color emacs -nw -Q -l kitty-graphics.el \
+ --eval "(setq kitty-gfx-heading-sizes-auto t)" \
+ --eval "(kitty-graphics-mode 1)" tests/test-kitty-gfx.org
+# Headings should render at scaled sizes automatically.
+# S-TAB to unfold all, then scroll to verify fold/unfold/scroll.
+```
+
+### Test image-mode
+
+```sh
+TERM=xterm-256color emacs -nw -Q -l kitty-graphics.el \
+ --eval "(kitty-graphics-mode 1)" tests/test-image.png
+```
+
+### Debug logging
+
+Set `kitty-gfx-debug` to `t` to log debug info to `/tmp/kitty-gfx.log`.
+
+### Dev environment
+
+The repo uses a Nix flake (`.envrc` → `use flake`). If you have direnv
+and Nix, dependencies are provided automatically. This is optional --
+only `emacs` and optionally `imagemagick` are needed.
+
+### Agent workflow
+
+When you need the user to manually test a feature in a Kitty-compatible
+terminal, or have any question that requires user action, notify them:
+
+```sh
+notify-send "kitty-graphics" "Please test: <description of what to test>"
+```
+
+## Code Style
+
+### File structure (mandatory order)
+
+```
+;;; file.el --- Description -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+(require ...) ; dependencies
+(declare-function ...) ; forward declarations for optional deps
+(defgroup ...) ; customization group
+(defcustom ...) ; user options
+(defconst ...) ; constants
+(defvar / defvar-local) ; internal state
+;; ... implementation sections with ;;;; headers ...
+(provide 'feature)
+;;; file.el ends here
+```
+
+### Formatting
+
+- **Spaces only**, no tabs (`indent-tabs-mode: nil`; enforced by `Local Variables` block)
+- `lexical-binding: t` required in the first line
+- `;;;;` section headers to separate logical sections
+
+### Naming conventions
+
+| Category | Pattern | Example |
+|-----------------------|--------------------------------|----------------------------------|
+| Public API | `kitty-gfx-` prefix | `kitty-gfx-display-image` |
+| Internal functions | `kitty-gfx--` (double dash) | `kitty-gfx--transmit-image` |
+| Buffer-local vars | `defvar-local` + double dash | `kitty-gfx--overlays` |
+| Customization vars | `defcustom` + single dash | `kitty-gfx-max-width` |
+| Global internal vars | `defvar` + double dash | `kitty-gfx--next-id` |
+| Advice functions | `kitty-gfx--MODE-VERB-advice` | `kitty-gfx--org-display-advice` |
+| Minor mode | `kitty-graphics-mode` | |
+
+All identifiers use `kebab-case`.
+
+### Requires / Dependencies
+
+- Only `cl-lib` is `require`d directly
+- All other dependencies (org, image-mode, shr, dired, dirvish) use
+ `declare-function` for forward declarations and `with-eval-after-load`
+ for deferred integration -- **never `require` optional packages at
+ top level**
+
+### Error handling
+
+- `user-error` for user-facing errors (e.g., unsupported terminal)
+- `condition-case` around I/O (terminal queries, files, image processing)
+- `ignore-errors` around `send-string-to-terminal` -- terminal writes
+ must never crash Emacs
+- `when`/`unless` guards rather than explicit error branches
+- `unwind-protect` for cleanup (see `kitty-gfx--refresh`)
+
+### Advice convention
+
+All advice uses `:around` and guards on both mode and terminal:
+
+```elisp
+(defun kitty-gfx--FOO-advice (orig-fn &rest args)
+ (if (and kitty-graphics-mode (not (display-graphic-p)))
+ ... ;; terminal path
+ (apply orig-fn args)))
+```
+
+New mode integrations **must** follow this pattern. Each mode needs
+explicit advice -- there is no generic image display hook.
+
+## Architecture
+
+### Backend dispatch
+
+Protocol-specific code is isolated behind an alist-based backend system:
+
+- `kitty-gfx--backends` -- alist mapping `'kitty` / `'sixel` to operation alists
+- `kitty-gfx--active-backend` -- symbol for the detected backend
+- `kitty-gfx--backend-fn` -- dispatch helper: `(funcall (kitty-gfx--backend-fn 'place) ...)`
+- `kitty-gfx--detect-protocol` -- tries Kitty (env check), then Sixel; sets active backend
+
+Each backend implements: `detect`, `prepare`, `place`, `delete`, `cleanup`, `cleanup-all`.
+
+### Image pipeline (shared)
+
+1. **Prepare**: backend-specific -- Kitty transmits via APC `a=t`; Sixel encodes via ImageMagick to temp-file
+2. **Reserve space**: `kitty-gfx--make-overlay` -- overlay with blank `display` property
+3. **Place**: backend-specific -- Kitty emits `a=p` with `p=PID`; Sixel reads temp-file and emits DCS
+4. **Refresh**: `kitty-gfx--refresh` -- debounced via `run-at-time`; walks windows,
+ re-places moved overlays, deletes hidden ones
+5. **Cache**: `kitty-gfx--image-cache` -- keyed by file path → image ID (integer).
+ Dimensions are computed fresh each time. LRU eviction via `kitty-gfx--cache-lru`
+ (max `kitty-gfx-cache-size` entries, default 64).
+ Sixel also caches encoded data in temp-files under `/tmp/kitty-gfx-sixel-*.six`.
+
+Overlay properties: `kitty-gfx` (bool marker), `kitty-gfx-id` (image ID),
+`kitty-gfx-pid` (placement ID), `kitty-gfx-cols`/`kitty-gfx-rows` (cell size),
+`kitty-gfx-last-row`/`kitty-gfx-last-col` (cached position, nil if hidden),
+`kitty-gfx-file` (source file path, needed by Sixel for re-encoding).
+All terminal output is wrapped in synchronized output (BSU/ESU, DEC mode
+2026) to prevent flicker.
+
+### Text sizing (OSC 66)
+
+Unlike images, the text sizing protocol is **stateless** — there are no
+image IDs or placement IDs. The terminal renders scaled text inline
+but any redraw clears it.
+
+1. **Detect**: `kitty-gfx--query-text-sizing-support` — 3 CPR queries
+ to probe width-only vs scale vs no support. Cached in
+ `kitty-gfx--text-sizing-support` (`scale`, `width`, or `none`).
+2. **Scan**: `kitty-gfx--org-apply-heading-sizes` — walks org headings,
+ looks up scale from `kitty-gfx-heading-scales` alist, creates overlays.
+3. **Overlay**: `kitty-gfx--make-heading-overlay` — overlay **without**
+ `display` property (Emacs shows the heading line normally; OSC 66 is
+ painted on top by the refresh cycle). No vertical space reservation.
+4. **Place**: `kitty-gfx--place-heading` — emits `\e]66;s=SCALE;TEXT\a`
+ at cursor position with SGR bold + 24-bit foreground color from
+ `org-level-N` faces.
+5. **Refresh**: `kitty-gfx--refresh-heading-overlay` — **always re-emits**
+ OSC 66 when visible (stateless protocol). Erases old position on move
+ via `kitty-gfx--erase-heading` (writes spaces across all rows —
+ per spec, overwriting any cell in the topmost row of a multicell
+ character erases the entire block).
+
+Heading overlay properties (in addition to the common ones above):
+`kitty-gfx-heading` (bool), `kitty-gfx-heading-text`, `kitty-gfx-heading-scale`,
+`kitty-gfx-heading-level`. Heading overlays have `modification-hooks` so
+editing the heading text removes the stale overlay for re-creation.
+Re-scan after edits is debounced via `kitty-gfx--heading-rescan-timer`
+(buffer-local, 0.2s) to prevent redundant scans from rapid typing.
+
+Key design differences from images:
+
+- Heading overlays **preserve their position cache** in
+ `on-window-change` (images clear it). This lets the refresh cycle
+ compare old→new position and erase properly. Images can afford to
+ clear cache because re-placing with the same PID is idempotent.
+- In `on-buffer-change`, heading overlays are **defensively erased**
+ when their buffer becomes hidden (not visible in any window). This
+ prevents ghost multicell artifacts if the new buffer's content does
+ not fully overwrite the heading's terminal cells.
+- Overlap detection (`kitty-gfx--heading-occupied-rows`) is reset
+ **per-window** inside `walk-windows`, not globally. This ensures
+ headings in one window don't block headings at different terminal
+ rows in another window showing the same buffer.
+
+## Known Issues & Planned Work
+
+- **Fixed**: Overlays remain visible when org headings are collapsed (#1)
+- **Limitation**: Does not work inside tmux (neither Kitty passthrough nor Sixel) (#2)
+- **Limitation**: Each mode needs explicit `:around` advice integration
+- **Limitation**: GIF files are not supported (multi-frame conversion issues,
+ no animation support in terminal)
+- **Limitation**: Sixel is 256 colors (vs truecolor on Kitty), stateless (re-emits on scroll)
+- **Done**: LaTeX fragment preview in org-mode (#3)
+- **Done**: doc-view / pdf-view-mode integration (#4)
+- **Done**: Text sizing protocol for org heading sizes (OSC 66, Kitty >= 0.40.0)
+- **Done**: Sixel backend with auto-detection (v0.3.0)
+- **WIP**: Sixel image misplacement in org-mode (reported in foot terminal)
+- **Planned**: comint-mime integration for shell buffer images (#5)
+- **Planned**: Newsticker (RSS reader) integration (#6)
+
+## Dependencies
+
+- **Required**: Emacs >= 27.1, a supported terminal (Kitty/WezTerm/Ghostty
+ for Kitty backend; foot/Konsole/xterm/mlterm/mintty for Sixel)
+- **Required for Sixel**: ImageMagick (`magick`/`convert`) for encoding
+- **Optional for Kitty**: ImageMagick for non-PNG formats and pixel-accurate sizing
+- **No external Elisp deps** beyond built-in `cl-lib`
+
+## Versioning
+
+Version is declared in the file header (`Version: X.Y.Z` in
+`kitty-graphics.el` line 6). This is the **single source of truth**.
+
+- **Patch** (0.2.x): bug fixes, minor improvements
+- **Minor** (0.x.0): new features, new mode integrations
+- **Major** (x.0.0): breaking changes to public API (`kitty-gfx-` symbols)
+
+Bump the version in the header when preparing a release.