feat(repl): Ctrl+V image paste in graff#19
Merged
Conversation
The zsh-setup visual check and `graff zsh doctor` still rendered the legacy ` FORGE 33.0k` line; replace with ` GRAFF 33.0k` to match the rest of the rebrand. Cosmetic only — internal symbols, env vars, and crate names are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The right-hand prompt was rendering ` FORGE` (anvil + UpperSnake of
the agent ID) — the anvil glyph plus the visible brand. Swap the
agent symbol from `\u{f167a}` (nf-md-anvil) to `\u{f013}`
(nf-fa-cog) and special-case the `forge` agent ID to render as
`GRAFF` instead of `FORGE`. The internal agent ID stays `forge` so
`:forge` slash commands and config keep working.
Test fixtures updated; all 20 rprompt tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every forge_* crate hardcoded `version = "0.1.1"` in its Cargo.toml, so even though the v0.1.2 tag and `workspace.package.version` both said 0.1.2, the displayed `forge_tracker::VERSION` (which expands `CARGO_PKG_VERSION` of the per-crate manifest) stayed at 0.1.1. Switch all 25 crates to `version.workspace = true` so they inherit the workspace version. Future bumps now only need to edit the root Cargo.toml. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The REPL prompt rendered by reedline (`forge_main::prompt::ForgePrompt`) had its own copy of the anvil `AGENT_SYMBOL` and UpperSnake-of-agent-id formatting. The earlier sweep only fixed the zsh rprompt path, so launching `graff` interactively still showed ` FORGE` on the right side of the REPL prompt. Apply the same swap here: gear glyph plus `forge` agent ID rendered as `GRAFF` (internal ID stays `forge`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #16. When the user presses Ctrl+V in the graff REPL, the system clipboard is inspected. If it holds an image, the RGBA pixel buffer is encoded as PNG to a unique file under the system temp directory and `@[<temp.png>]` is inserted into the input buffer. The existing `forge_domain::Attachment` pipeline resolves that reference into a multimodal content block at message-send time, so the model sees the image with no further plumbing. If the clipboard does not hold an image, Ctrl+V falls back to plain text paste (replicating the standard binding) so users do not lose normal paste behaviour. If clipboard access fails entirely, the binding is a no-op. Why Ctrl+V (not Cmd+V): on macOS, Cmd+V is intercepted by the terminal itself; the app receives whatever the terminal forwards (usually text or filename, never raw image bytes). Ctrl+V reaches the app directly. This is the only binding that gives reliable in-process clipboard access without per-terminal configuration. Implementation: - New module `crates/forge_main/src/clipboard.rs` wraps the arboard clipboard API and writes PNGs via the `image` crate. - `ForgeEditMode::parse_event` (`crates/forge_main/src/editor.rs`) intercepts the Ctrl+V key event before delegating to the inner Emacs edit mode — the same pattern already used for bracketed paste. - `image` added to `[workspace.dependencies]` with PNG-only feature to keep binary size impact minimal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Save clipboard PNGs under `~/forge/clipboard/<8hex>.png` instead of the system temp directory. The resulting `@[...]` reference inserted into the input buffer drops from ~109 chars (`/var/folders/t3/...`) to ~50 chars, so the user's typed message is not visually swamped by the path. Falls back to the system temp dir when `~/forge/` cannot be reached (sandboxed builds, CI). Uses an 8-character UUID prefix so paths stay readable but unique within the directory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the long `@[/Users/.../forge/clipboard/<8hex>.png]` insertion
with a tidy `[Image 1]`, `[Image 2]`, ... chip. The chip is rewritten
to the canonical `@[<absolute-path>]` syntax at message-submit time,
so the existing `forge_domain::Attachment` pipeline sees no change.
Implementation:
- A process-level `LazyLock<Mutex<Vec<PathBuf>>>` registry maps slot
numbers to on-disk PNG paths. Lifetime is the process — counters
do not reset on `:new` for v1, simplest correct behavior.
- `capture_clipboard_image()` returns `CapturedImage { slot }`; the
Ctrl+V handler in `ForgeEditMode::parse_event` inserts
`[Image {slot}]` instead of the path.
- `expand_image_chips(buffer)` rewrites every `[Image N]` whose `N`
is a registered slot into `@[<absolute-path>]`. Out-of-range or
malformed chips (`[image 1]`, `[Image 1]`, `[Image 0]`) are left
as literal text so users can type them organically.
- `From<Signal> for ReadResult` calls the expander on the trimmed
buffer before handing it to the agent pipeline.
- 8 unit tests cover single chip, multiple chips, out-of-range,
zero index, lowercase, extra-space, empty registry, unicode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #16.
@[<temp.png>]into the input buffer — the existing attachment pipeline turns that into a multimodal content block at send time.crates/forge_main/src/clipboard.rs(~80 LoC) and a small extension toForgeEditMode::parse_eventineditor.rs— same interception pattern the bracketed-paste handler already uses.imageadded to[workspace.dependencies]with PNG-only feature to keep binary impact minimal.Why Ctrl+V (not Cmd+V)
On macOS, Cmd+V is intercepted by the terminal before the app sees it; the app only receives whatever the terminal forwards (usually text or filename, never raw image bytes). Ctrl+V reaches the app directly and is the only binding that gives reliable in-process clipboard access without per-terminal config.
Test plan
Cmd+Shift+Ctrl+4on macOS) → rungraff→ press Ctrl+V → verify@[/var/folders/.../graff-clipboard-<uuid>.png]is inserted.graff→ press Ctrl+V → verify text is inserted as normal paste.🤖 Generated with Claude Code