Skip to content

feat: support multi-location placement fan-out for one transmitted image - #51

Merged
hisanari-dev merged 2 commits into
mainfrom
feat/multi-location-placement-fanout
Aug 19, 2026
Merged

hisanari-dev merged 2 commits into
mainfrom
feat/multi-location-placement-fanout

Conversation

@hisanari-dev

Copy link
Copy Markdown
Contributor

📦 Pull Request

Description

Adds multi-location placement fan-out for a single transmitted image: terminal.lua now carries a caller-supplied p= (placement id) on every placement/transmit/delete command instead of a single fixed constant, and renderer.lua allocates a distinct, never-reused placement id per handle so several handles can share one transmitted image id — each with its own live, independently addressable/deletable placement — without re-transmitting pixel data or clobbering a sibling's placement.

Also includes a follow-up fix (from code review of this branch): destroy_handle's fan-out-sibling-still-shares-the-id delete path now gets the same bounded delete retry as every other placement-scoped delete in renderer.lua, except when the caller is truly shutting down (VimLeavePre/test _reset()). Previously a mid-session M.show() failure tearing down a fan-out handle skipped the retry, risking an orphaned placement if the terminal dropped that one delete.

Related Issue

Closes #10

Type of Change

  • Bug fix
  • New feature
  • Refactoring
  • Documentation
  • CI / Infrastructure

Checklist

  • I have run make locally (stylua --check, selene, tests) and it passes.
  • I have added unit tests for new pure logic (escape sequences, chunking, geometry, detection).
  • I have updated the relevant docs/spec/ memo if protocol or terminal-detection behavior changed.
  • I have updated doc/blit.txt and/or README if this changes the public API.
  • I have followed Conventional Commits (feat:, fix:, etc.).

Every real placement command now carries a distinct, never-reused
placement id (`p=`) instead of the old fixed `terminal.PLACEMENT_ID = 1`.
This lets one transmitted image id carry several concurrent placements:
show()ing the same (path, mtime) a second time while the first is still
live now fans out onto the existing id under a fresh placement id instead
of transmitting a redundant copy under a new image id.

- terminal.lua: build_transmit/build_placement/build_delete take a caller-
  supplied placement_id; build_delete's `p=` scopes a delete to one
  placement instead of the whole id.
- renderer.lua: cache entries track active_placements (a count) instead of
  a single active boolean; find_reusable_entry (renamed from
  acquire_idle_entry) tries an idle entry first, then falls back to fanning
  out onto any active one. destroy_handle only frees an id's terminal-side
  data once the last handle referencing it is gone, regardless of the
  caller's free_data intent. The Ghostty resize-retransmit path now
  migrates every handle sharing a stale id together
  (retransmit_and_place_group), fixing a correctness gap the old per-handle
  version would have hit once ids could be shared.
- docs/spec/kitty-graphics.md and docs/spec/renderer-placement.md updated
  to match; docs/manual-testing.md gets a fan-out checklist item.

Closes #10
destroy_handle's fan-out-sibling-still-shares-the-id branch (issue #10)
returned without queuing a delete retry whenever free_data was
requested, even when the caller was M.show()'s own mid-session failure
path rather than VimLeavePre. If the terminal dropped that one-shot
scoped delete, the stray placement had no future redraw pass left to
revisit it and stayed orphaned for as long as the sibling handle
stayed alive.

Add an explicit opts.shutting_down flag to destroy_handle, set only by
on_vim_leave_pre and the test-only M._reset(). The fan-out branch now
skips the retry queue only when shutting_down is true; M.show()'s
failure paths get the same bounded pending_deletes retry every other
placement-scoped delete in this file already has.

Update docs/spec/renderer-placement.md's Lifecycle section, which
documented the old reasoning, and add a regression test covering
retransmit_and_place_group's multi-handle migration path (previously
only exercised with a single handle).

Found in code review of #10's multi-location placement fan-out.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation of multi-location placement fan-out is well-designed and comprehensive. The code correctly handles:

  • Placement ID allocation with an ever-incrementing counter that avoids reuse conflicts
  • Cache entry management tracking active_placements to support fan-out
  • Scoped delete operations using placement_id to prevent colliding with sibling placements
  • Ghostty-specific group migration that atomically moves all handles sharing a stale ID
  • Proper retry mechanisms for both destroy-path deletes and Ghostty retransmits

The extensive test coverage demonstrates the feature works correctly across various scenarios including fan-out, cache reuse, and terminal-specific behaviors. No defects blocking merge were identified.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

@hisanari-dev
hisanari-dev merged commit 9ae8bad into main Aug 19, 2026
3 checks passed
@hisanari-dev
hisanari-dev deleted the feat/multi-location-placement-fanout branch August 19, 2026 15:04
hisanari-dev added a commit that referenced this pull request Oct 4, 2026
PR #51 (issue #10, multi-location placement fan-out) added the
underlying primitive but never updated README/doc/blit.txt, despite
docs/review-checklist.md's Tests & Docs category requiring vimdoc and
README updates for user-facing behavior. Document the one concrete,
user-actionable consequence: calling show() again with the same path
reuses the already-transmitted image data and is the way to place the
same image into a second window after :split/:vsplit today, since
show() still binds one handle to one window and doesn't auto-detect
splits.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Multi-location placement fan-out for a single transmitted image

1 participant