Skip to content

fix: critical Vulkan/Wayland bugs, resource leaks, and build improvements#1

Closed
daedaevibin wants to merge 17 commits into
mainfrom
devin/1781863456-sprint1-critical-fixes
Closed

fix: critical Vulkan/Wayland bugs, resource leaks, and build improvements#1
daedaevibin wants to merge 17 commits into
mainfrom
devin/1781863456-sprint1-critical-fixes

Conversation

@daedaevibin

@daedaevibin daedaevibin commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

Sprint 1 critical fixes that resolve crashes, resource leaks, and build failures blocking any functional rendering.

Vulkan correctness fixes

Descriptor set layout crashcreate_graphics_pipeline() created a VkDescriptorSetLayout as a local, destroyed it, then create_descriptor_set() tried to allocate from it (and was missing pSetLayouts entirely). Now stored as descriptor_set_layout_ member, wired into both pipeline layout creation and descriptor allocation, destroyed in ~Sigil().

Render pass / swapchain format mismatch — render pass hardcoded VK_FORMAT_B8G8R8A8_SRGB while swapchain picks format dynamically. Added swapchain_format_ member; create_render_pass() now uses it.

Queue family hardcoded to 0 — no enumeration, fails on GPUs where family 0 isn't graphics+present. Now enumerates families via vkGetPhysicalDeviceSurfaceSupportKHR. Stored as graphics_queue_family_.

Fence deadlock on swapchain out-of-datevkResetFences was called before vkAcquireNextImageKHR; if acquire returned VK_ERROR_OUT_OF_DATE_KHR, the fence stayed unsignaled and the next vkWaitForFences deadlocked. Moved reset to after successful acquire.

Wayland event loop (complete rewrite)

  • poll_events() called wl_display_dispatch() in a blocking while-loop → replaced with wl_display_read_events() + dispatch_pending()
  • Added wl_display_flush() to the loop so outgoing requests (e.g. xdg_surface_ack_configure) actually reach the compositor
  • prepare_read() now returns bool; callers handle failure by looping on dispatch_pending
  • Added cancel_read() on poll error path to release Wayland read lock
  • eventfd was never consumed after poll → now reads 8 bytes to clear it

Resource cleanup

  • Destructor: vkDeviceWaitIdle first, then proper teardown of glyph atlas (image/memory/view/sampler), vertex buffer (unmap/buffer/memory), descriptor pool/layout, all Wayland objects
  • record_command_buffers(): frees old command buffers before re-allocating (prevents leak on resize)

Build system

  • Removed -Oz from COMMON_FLAGS (conflicted with -O3 in release)
  • Added glslc shader compilation: GLSL → SPIR-V via add_custom_target(shaders)
  • Added glslang-tools to CI deps; shaders/*.spv to .gitignore
  • Added missing <cctype> include for std::isdigit

Other

  • Shell exec: $SHELL/usr/bin/fish/bin/sh fallback chain
  • Adopted Veridian Zenith "Atmosphere" palette: vz_bg (#050200), vz_fg (#f3f4f6), vz_accent (#FFB347)
  • Removed ~40 std::cout debug lines and unused <iostream> includes

Link to Devin session: https://app.devin.ai/sessions/adbf553caeec4d948cdb3a25123d8b7e
Requested by: @daedaevibin

…ents

- Fix descriptor set layout: store as class member, wire into allocation
- Fix render pass format mismatch: use dynamic swapchain format
- Fix Wayland event loop: proper prepare_read/cancel_read/read_events
- Add queue family enumeration: find graphics+present capable family
- Fix eventfd busy-loop: consume wake events after poll
- Add SPIR-V shader compilation (glslc) to CMakeLists.txt
- Fix full Vulkan resource cleanup in destructor (atlas, vertex buf, descriptors, Wayland objects)
- Remove all debug std::cout logging, remove unused iostream includes
- Fix CMake -Oz flag conflict with -O3 in release
- Use $SHELL env with fish fallback instead of hardcoded /usr/bin/fish
- Adopt Veridian Zenith 'Atmosphere' color palette (#050200 bg, #f3f4f6 fg, #FFB347 accent)
- Add glslang-tools to CI dependencies
- Add shaders/*.spv to .gitignore
@devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@devin-ai-integration[bot], we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 6 minutes and 52 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6a204954-56f0-4146-80b7-ceb12fb9f65d

📥 Commits

Reviewing files that changed from the base of the PR and between 738b6ab and 4f5eff9.

📒 Files selected for processing (11)
  • CMakeLists.txt
  • include/common.hpp
  • include/glyph_engine.hpp
  • include/loom.hpp
  • include/nexus.hpp
  • include/sigil.hpp
  • src/glyph_engine.cpp
  • src/loom.cpp
  • src/main.cpp
  • src/nexus.cpp
  • src/sigil.cpp
📝 Walkthrough

Walkthrough

The PR adds automatic GLSL→SPIR-V shader compilation to the CMake build and CI. It replaces the nord_* terminal color palette with vz_* constants throughout. The Wayland event loop is corrected with a cancel_read() method and proper fd consumption. Vulkan device initialization is refactored to dynamically select the graphics+presentation queue family and promote descriptor_set_layout_, swapchain_format_, and graphics_queue_family_ to member state. PTY shell selection now reads the SHELL environment variable.

Changes

Kaelum runtime and build improvements

Layer / File(s) Summary
Shader build pipeline and CI dependency
CMakeLists.txt, .gitignore, .github/workflows/codeql.yml
Adds a glslc-based shader compilation step producing .spv outputs under a shaders custom target that kaelum depends on; removes -Oz from COMMON_FLAGS; ignores shaders/*.spv; installs glslang-tools in CI.
Color palette: nord → vz_* constants
include/common.hpp, src/nexus.cpp
Removes nord_bg/nord_fg, adds vz_bg, vz_fg, vz_accent; updates Cell default initializers and replaces hardcoded RGBA in Nexus::handle_ground.
Wayland event loop: cancel_read and fd consumption
include/sigil.hpp, src/sigil.cpp, src/main.cpp
Adds cancel_read() calling wl_display_cancel_read; changes poll_events() to wl_display_read_events + wl_display_dispatch_pending; main loop branches on Wayland fd readability and consumes wake fd via ::read to prevent busy-loop.
Vulkan device: queue family selection and member handles
include/sigil.hpp, src/sigil.cpp
Adds descriptor_set_layout_, swapchain_format_, graphics_queue_family_ members; init_vulkan scans queue families for graphics+presentation support; create_swapchain stores chosen format; create_render_pass uses it; pipeline layout and descriptor set layout become member handles wired through create_graphics_pipeline and create_descriptor_set.
Render vertex generation, clear colors, and destructor teardown
src/sigil.cpp
render() uses origin_x/origin_y centering and derives per-vertex RGBA from glyph cell fg; clear color constants updated in render pass and pre-recorded command buffers; destructor expanded with ordered Vulkan and Wayland resource teardown; glyph atlas creation and initialize() remove console logging and sequence with std::expected error propagation.
PTY shell selection and header cleanup
src/loom.cpp, src/glyph_engine.cpp
Loom::initialize reads SHELL env var with /usr/bin/fish and /bin/sh fallbacks; glyph_engine.cpp replaces <iostream> with <print>.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 Hop hop, the shaders compile with glee,
Nord colors fade — vz_* sets the theme free.
The Wayland loop no longer spins in vain,
Queue families chosen, member handles reign.
The destructor tidies every Vulkan trace,
And SHELL picks your terminal's comfy place!
A well-groomed warren, tidy and in grace. 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: critical Vulkan/Wayland bug fixes, resource leak fixes, and build improvements, which directly correspond to the primary themes in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1781863456-sprint1-critical-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 8 potential issues.

Open in Devin Review

Comment thread src/sigil.cpp
Comment thread src/sigil.cpp
Comment thread src/sigil.cpp
Comment thread include/sigil.hpp Outdated
Comment thread src/main.cpp
Comment thread src/sigil.cpp
Comment thread CMakeLists.txt
Comment thread src/loom.cpp Outdated
- Add missing <cctype> include in nexus.cpp (fixes CI: std::isdigit)
- Add wl_display_flush() to event loop (outgoing requests reach compositor)
- Return bool from prepare_read() and handle failure (dispatch_pending loop)
- Cancel Wayland read lock on poll error before breaking
- Move vkResetFences after successful vkAcquireNextImageKHR (prevents deadlock)
- Free old command buffers before re-allocation in record_command_buffers()
- Fix shell fallback comment accuracy
- Add -O1 to debug flags (satisfies _FORTIFY_SOURCE=3 requirement)
- Suppress unused parameter warnings in xdg_surface_handle_configure, parse_sgr
- Remove unused current_frame_ member
- Use configured dimensions from xdg_toplevel configure event when
  Vulkan reports UINT32_MAX extent (standard wlroots behavior)
- Defer resize handling until after initial Vulkan init is complete
  (prevents SIGSEGV when configure fires during init_wayland roundtrip)
- Query supportedCompositeAlpha from surface capabilities instead of
  hardcoding VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR (not supported
  on many Hyprland/wlroots setups)
- Guard against zero-extent swapchain creation

@coderabbitai coderabbitai 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.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sigil.cpp (1)

843-855: ⚠️ Potential issue | 🔴 Critical

Render pass and pipeline must be recreated during swapchain resize.

During on_resize(), create_swapchain() can select a different surface format if the preferred format is no longer available. Since create_render_pass() uses swapchain_format_ directly in the color attachment description, and create_graphics_pipeline() references the render pass, the old render pass and pipeline will be incompatible with the new swapchain if the format changes. Currently, only framebuffers and command buffers are recreated; create_render_pass() and create_graphics_pipeline() must also be called to maintain compatibility.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sigil.cpp` around lines 843 - 855, In the on_resize() method, after
successfully calling create_swapchain(), you must also call create_render_pass()
and create_graphics_pipeline() before calling create_framebuffers(). The issue
is that create_swapchain() may select a different surface format than previously
used, and since create_render_pass() depends on swapchain_format_ for its color
attachment description and create_graphics_pipeline() references the render
pass, these functions must be recreated to maintain compatibility with the new
swapchain format. Add these two function calls in the correct order after the
create_swapchain() check and before create_framebuffers().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/codeql.yml:
- Line 39: In the apt-get install command on line 39 of the codeql.yml workflow
file, replace the package name glslang-tools with glslc. The CMakeLists.txt
build configuration searches for the glslc executable which is provided by the
glslc package (not glslang-tools), and without it shader compilation will be
silently skipped rather than failing the build, allowing incomplete binaries to
be shipped without valid SPIR-V artifacts.

In `@CMakeLists.txt`:
- Around line 171-173: The else() block currently issues only a warning when
glslc is not found, but since the renderer requires SPIR-V shaders at runtime, a
missing glslc should cause the build configuration to fail immediately rather
than defer failure to runtime. Replace the message(WARNING "glslc not found.
Shaders will not be compiled automatically.") call with message(FATAL_ERROR ...)
using an appropriate error message to prevent silent build failures and ensure
shader compilation is always available when needed.
- Around line 156-163: The generated shader SPV files are being output to the
source directory using CMAKE_SOURCE_DIR in the SHADER_DIR variable and the
SHADER_SPV output path. This violates best practices for out-of-tree builds and
can cause issues in read-only or packaged source environments. Modify the
SHADER_DIR variable or create a separate output directory variable that uses
CMAKE_BINARY_DIR or CMAKE_CURRENT_BINARY_DIR instead of CMAKE_SOURCE_DIR. Update
the SHADER_SPV output path and the add_custom_command OUTPUT directive to use
this new build directory path so generated .spv files are placed in the build
tree, not the source tree.

In `@src/loom.cpp`:
- Around line 37-42: The startup message in src/main.cpp that reports the PTY
being linked to fish is now inaccurate because Loom::initialize() in
src/loom.cpp dynamically selects a shell based on the SHELL environment variable
with fallbacks to fish and then sh. Update the user-facing message in
src/main.cpp to accurately reflect this dynamic shell selection behavior instead
of hardcoding it to fish, so users receive correct information about which shell
is actually being used.

In `@src/main.cpp`:
- Around line 92-96: Remove the [[maybe_unused]] attribute and add explicit
error handling to the ::read() call when draining the wake_fd. Check the return
value: if it returns -1 and errno equals EINTR, loop back and retry the read
operation, otherwise handle the error appropriately. This ensures that if the
read is interrupted by a signal, the eventfd is properly drained before poll
returns, preventing busy-loop scenarios where the signal handler keeps
triggering poll without actually consuming the wakeup event.

In `@src/sigil.cpp`:
- Around line 256-274: The code unconditionally selects the first physical
device without verifying it supports the required queue families. Refactor the
physical device selection to iterate through all enumerated devices and check
each one for queue family support. Move the queue family search logic (the loop
checking for both VK_QUEUE_GRAPHICS_BIT and presentation support) into the
device iteration loop, so that graphics_queue_family_ is populated per device
candidate. Select and assign physical_device_ only when a device is found that
has a queue family supporting both graphics and presentation capabilities,
rather than blindly using devices[0].
- Around line 79-86: The seven setup functions `create_render_pass`,
`create_framebuffers`, `create_sync_objects`, `create_command_pool`,
`create_graphics_pipeline`, `record_command_buffers`, and `create_vertex_buffer`
are all void functions that only log Vulkan errors to stderr without returning
error information. This allows the initialize() method to return success even
when these critical setups fail, leaving invalid Vulkan objects. Refactor each
of these seven functions to return std::expected with an appropriate error type
(such as SigilError) instead of void, and have them return the error when Vulkan
API calls fail. Then in the initialize() method, check the return value of each
function call using the expected result pattern and propagate any errors up
before returning success at line 91.

---

Outside diff comments:
In `@src/sigil.cpp`:
- Around line 843-855: In the on_resize() method, after successfully calling
create_swapchain(), you must also call create_render_pass() and
create_graphics_pipeline() before calling create_framebuffers(). The issue is
that create_swapchain() may select a different surface format than previously
used, and since create_render_pass() depends on swapchain_format_ for its color
attachment description and create_graphics_pipeline() references the render
pass, these functions must be recreated to maintain compatibility with the new
swapchain format. Add these two function calls in the correct order after the
create_swapchain() check and before create_framebuffers().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2bcdd951-06d7-419e-bad7-ec57d2b1ec23

📥 Commits

Reviewing files that changed from the base of the PR and between f3a2085 and 738b6ab.

📒 Files selected for processing (10)
  • .github/workflows/codeql.yml
  • .gitignore
  • CMakeLists.txt
  • include/common.hpp
  • include/sigil.hpp
  • src/glyph_engine.cpp
  • src/loom.cpp
  • src/main.cpp
  • src/nexus.cpp
  • src/sigil.cpp
💤 Files with no reviewable changes (1)
  • src/glyph_engine.cpp

Comment thread .github/workflows/codeql.yml
Comment thread CMakeLists.txt
Comment thread CMakeLists.txt
Comment thread src/loom.cpp Outdated
Comment thread src/main.cpp Outdated
Comment thread src/sigil.cpp
Comment thread src/sigil.cpp
Root causes:
- on_resize was called synchronously inside the Wayland configure
  handler, but the compositor hadn't processed the surface commit yet,
  so vkGetPhysicalDeviceSurfaceCapabilitiesKHR still returned stale
  data causing swapchain creation to fail
- After failed recreation, swapchain_ still held the old (destroyed)
  handle, so render() tried to use it → SIGSEGV
- Old swapchain was not destroyed before creating new one in on_resize

Fixes:
- Defer resize: configure handler sets needs_resize_ flag; main loop
  calls process_pending_resize() after event dispatch (same pattern
  as Ghostty/Kitty)
- Destroy old swapchain in on_resize before creating replacement
- Commit wl_surface in xdg_surface_handle_configure after ack
- Query supportedCompositeAlpha (OPAQUE preferred)
- Add diagnostic VkResult/extent/format to swapchain failure message
io_uring fixes (root cause of blank window):
- Use offset -1 for PTY read/write (PTYs are non-seekable; offset 0
  caused -ESPIPE, so shell output never reached Nexus)
- Tag SQEs with URING_TAG_READ/WRITE to distinguish completions
- write() no longer blocks with io_uring_wait_cqe (was consuming read
  CQEs, causing terminal to go silent after first keypress)
- poll_read() iterates CQ ring, consuming write CQEs and returning
  on the first read CQE

Keyboard:
- Added numbers, symbols, backspace, tab, escape to key map
- Added backspace/DEL handling in Nexus (cursor_x_--)
- Filter other control characters < 0x20 in ground state

Swapchain recreation:
- Pass oldSwapchain to vkCreateSwapchainKHR on resize (Vulkan spec
  allows driver to reuse resources)
- Destroy old swapchain only after new one is created successfully
Dynamic resize:
- Grid changed from fixed std::array<Cell, 80*24> to std::vector<Cell>
  with runtime cols_/rows_ members
- Nexus::resize() copies existing content to new grid dimensions
- Loom::set_pty_size() sends TIOCSWINSZ to the shell on resize
- Sigil fires resize_callback_ from on_resize() with cols/rows
  calculated from pixel extent and cell dimensions
- Cell dimensions derived from FreeType max_advance and line_height

Rendering adapts to dynamic grid:
- render() reads nexus.cols()/rows() instead of k_default_cols/rows
- Cells fill full NDC range (-1..1) for the current grid size

New CSI sequences:
- J modes 0/1/2/3 (erase in display: cursor-to-end, start-to-cursor,
  full, full+scrollback)
- K modes 0/1/2 (erase in line)
- L/M (insert/delete lines)
- P/@ (delete/insert characters)
- d (vertical line position absolute)
- G (cursor character absolute)

Keyboard:
- Arrow keys, Home, End, PgUp, PgDn, Delete emit escape sequences
- Numbers, symbols, backspace, tab, escape mapped

@devin-ai-integration devin-ai-integration 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.

Devin Review found 7 new potential issues.

Open in Devin Review

Comment thread src/nexus.cpp
Comment thread src/loom.cpp
Comment thread src/sigil.cpp
Comment thread src/sigil.cpp
Comment thread src/sigil.cpp
Comment thread src/loom.cpp
Wayland protocol fixes:
- Add xdg_wm_base ping/pong listener (Hyprland marks unresponsive apps
  that don't respond, may withhold configure events)
- Handle xdg_toplevel close event (sets should_close flag, main loop
  exits cleanly)
- Ignore 0x0 configure (compositor saying 'pick your own size')
- Set xdg_toplevel_set_min_size(320,240) so compositor knows minimum
- Main loop checks should_close() instead of while(true)

Frame callback pacing:
- After present, request wl_surface.frame callback
- render() skips if frame_pending_ (waits for compositor to signal
  frame_done before drawing again — prevents overdriving)
- Properly clean up frame_callback in destructor

Also handle VK_SUBOPTIMAL_KHR on present by triggering resize
… diagnostics

Per Wayland protocol (and Kitty's documented pattern):
  ack_configure → set_window_geometry → commit
Without set_window_geometry, Hyprland doesn't know the app's drawing
area and cannot properly tile/resize the window.

Swapchain extent fixes:
- Removed early bail on currentExtent == 0 (happens on Hyprland before
  first buffer attach, was causing swapchain creation to fail)
- Fall through to use configured_width_/height_ when extent is 0 or
  UINT32_MAX, then clamp to driver-reported min/max

Added configured_width()/configured_height() accessors so the
xdg_surface configure handler can query current dimensions.

Diagnostic logging in handle_configure shows compositor-sent vs stored
dimensions and whether init is complete — helps debug resize chain.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 8 new potential issues.

Open in Devin Review

Comment thread src/sigil.cpp
Comment thread src/nexus.cpp
Comment thread src/loom.cpp
Comment thread src/nexus.cpp
Comment thread src/nexus.cpp
Comment thread src/nexus.cpp Outdated
Comment thread src/loom.cpp
Comment thread src/sigil.cpp
…ess bugs

Rendering:
- Glyph quads now use FreeType bearing offsets + actual bitmap dimensions
  for proper per-glyph positioning within cells (fixes distorted text)
- GlyphRect stores full pixel metrics (bearing_x/y, width, height, advance)
- GlyphMetric bearing types changed from uint32_t to int32_t (FreeType
  bitmap_left can be negative)
- Vertex positions computed in pixel-to-NDC space using extent dimensions
- Vertex buffer enlarged to 20k cells, with overflow clamping guard

Wayland/Hyprland:
- Added wl_pointer listener (enter/leave/motion/button/axis) so Hyprland
  can properly track pointer focus — fixes 'focus swaps when moving cursor'
- Capped wl_seat binding to version 5 (avoids needing v8+ pointer events)
- Configure handler debounced: only sets needs_resize_ when dimensions
  actually change

Terminal:
- Added scroll_up(): shifts grid up one line when cursor overflows bottom,
  clears last line — output no longer overwrites bottom row
- Fixed move_cursor() unsigned wraparound: arithmetic now done in int
  before clamping, prevents cursor teleporting to far edge on negative moves

I/O:
- Loom::write() now copies data into persistent write_buffer_ before
  submitting to io_uring, fixing dangling stack pointer when callers pass
  temporary buffers
… handlers

wl_pointer version 5 requires all event handlers up to axis_discrete.
Missing frame handler (opcode 5) caused SIGABRT on Hyprland.
… buffer map size

Rendering:
- Fixed Y-axis direction: Vulkan NDC has Y=-1 at top, Y=+1 at bottom.
  Row 0 now renders at top of screen instead of bottom.
- Baseline positioned at bottom of each cell; bearing_y offsets glyph
  upward from baseline (correct for Y-down coordinate system).
- Triangle winding order corrected for Y-down NDC.
- Added codepoint==0 skip alongside space skip.
- Fixed vertex buffer map size (was hardcoded 10k, now uses capacity).

Pointer:
- Added axis_value120 and axis_relative_direction handlers to complete
  the wl_pointer v5+ listener (fixes missing field warning).
- Grid and PTY size now synced to actual compositor dimensions at startup
  (was stuck at 80x24 until first resize event fired in main loop)
- Added temporary render diagnostics: first 5 frames print grid dimensions,
  non-space cell count, vertex count, and swapchain extent
- Fixed vertex buffer map size to use capacity variable
io_uring reads from non-blocking FDs complete immediately with -EAGAIN
when no data is available, instead of waiting for the shell to produce
output. This caused all io_uring read completions to fail silently,
so shell output never reached the grid (confirmed by render diagnostics
showing non_space=0 across all frames).

With the FD in blocking mode, io_uring correctly parks the read in the
kernel until the shell writes data, then signals the registered eventfd.
…designations

The fish shell prompt was invisible because most of its output consists of
escape sequences that the parser was mishandling:

- CSI '?' prefix (DEC private modes like ?2004h bracketed paste, ?25h show
  cursor) was treated as the final byte, causing '2004h' to render as text
- ESC ( ) * + charset designations (3-byte sequences) were partially consumed,
  leaking the final byte as visible text
- OSC sequences only terminated on BEL; ESC \ (ST) wasn't handled, leaving
  the parser stuck in OSC state eating all subsequent output

Also:
- Stop hiding mouse cursor on pointer enter (was setting cursor to NULL)
- Implement CSI f (HVP), X (ECH), S (SU), r/c/n/l/h/s/u stubs
- Add EscapeSkip state for consuming charset designation final byte
io_uring reads from PTY file descriptors were silently failing — the
eventfd was never signaled, so shell output never reached the grid
(non_space=0 across all frames).

Replaced with standard poll(master_fd)+read() pattern, which is what
Ghostty, Kitty, and every production terminal emulator uses. io_uring
is kept for async writes only.

Also increased render diagnostic count to 10 frames for better debugging.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 9 new potential issues.

Open in Devin Review

Comment thread src/nexus.cpp
Comment thread src/loom.cpp
Comment thread src/nexus.cpp
Comment thread src/loom.cpp
Comment thread src/main.cpp
Comment thread src/nexus.cpp
Comment thread src/sigil.cpp
Comment thread src/nexus.cpp
Comment thread src/nexus.cpp
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.

1 participant