* stage 12: PDF ingestion
Add PDF ingestion to internal/imageinput so specs, design docs, and tickets
that arrive as PDF can be attached the same way images are.
- internal/imageinput/pdf.go: LoadDocument detects PDFs by magic bytes
(%PDF-, never extension alone), enforces a 32 MiB raw-file cap and a
256 KiB extracted-text cap (truncated with a marker, not rejected), and
extracts the text layer in pure Go via github.com/ledongthuc/pdf. The
parser can panic on malformed structures, so extraction is wrapped in a
recover that turns a bad PDF into a clean error. A textless PDF with no
rasterizer returns an explicit "no extractable text; OCR not available"
error rather than silent empty success.
- Dependency posture: default build is pure-Go text extraction (no CGO, no
transitive deps) so the static binary still cross-compiles. The optional
poppler tools (pdftotext / pdftoppm) are used for richer extraction and
page rasterization ONLY when present on PATH (same posture as the LSP
language servers); when absent, extraction degrades to the pure-Go text
path and never errors.
- Vision path: with a vision model and pdftoppm available, the first N pages
(default 10) render to PNG and flow through the existing image pipeline
(NormalizeImageMediaType, per-image cap, allow-list). Without a rasterizer
it degrades to the text layer.
- TUI integration: /image <file.pdf> routes to the document path. The text
layer attaches for any model and is prepended to the next prompt as a
labeled preamble; rendered pages attach as images for vision models. The
chip row and /image clear cover documents too.
Tests cover magic-byte detection, text extraction on a generated fixture,
both size caps, the no-text/no-raster message, malformed-input safety, the
pure-Go fallback, and the TUI attach/submit/clear wiring. go build, go test,
and gofmt are clean; CGO_ENABLED=0 cross-compiles for linux/amd64,
windows/arm64, and darwin/arm64.
* stage 12: address review (pdf page count, text cap, content-based routing)