Skip to content

feat: add /screen slash command with tab-completion and screen capture - #35

Merged
quiet-node merged 11 commits into
mainfrom
worktree-cheerful-sauteeing-pudding
Apr 5, 2026
Merged

feat: add /screen slash command with tab-completion and screen capture#35
quiet-node merged 11 commits into
mainfrom
worktree-cheerful-sauteeing-pudding

Conversation

@quiet-node

Copy link
Copy Markdown
Owner

Description

Adds a /screen slash command that captures the full screen and attaches it as image context to the current question. Includes a tab-completion suggestion UI for slash commands in the ask bar.

Changes

  • /screen slash command: type /screen (optionally followed by a question) and press Enter. Thuki silently captures the entire screen (excluding its own windows), attaches the screenshot alongside any manually pasted images, and sends everything to Ollama in one shot.
  • Tab-completion UI: a CommandSuggestion component appears above the ask bar when the input matches a known slash command prefix, showing the command name and description. Tab or Enter accepts it; Escape dismisses.
  • Immediate pending state: submitting /screen shows the user's message bubble in chat right away with a loading placeholder tile (minimal spinner, no icon) while capture is in flight. Prevents double-submit spam.
  • Cancellation safety: pressing Stop (or closing/hiding the overlay, or starting a new conversation) during an in-flight capture signals the async tail to abort and restores the ask bar to its pre-capture state (query and context both restored).
  • CoreGraphics capture on main thread: the capture_full_screen_command Tauri command dispatches to the macOS main thread via run_on_main_thread + tokio::sync::oneshot to satisfy CoreGraphics thread-safety requirements. Excludes Thuki's own windows using kCGWindowListExcludeDesktopElements + app PID filter.
  • Error handling: capture failures surface a dismissible error banner in the ask bar; the query is restored so nothing is lost.
  • Docs: README features list and roadmap updated; CLAUDE.md architecture section updated with config/commands.ts and screenshot.rs; "Command key" typo fixed to "Control key".

Checklist

  • Tests added or updated for all changed code
  • bun run test:all:coverage passes with 100% coverage
  • bun run validate-build passes with zero warnings and zero errors
  • Commit message follows Conventional Commits (feat:, fix:, etc.) so the changelog is generated automatically

quiet-node and others added 11 commits April 5, 2026 16:58
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
Implements the full /screen command feature from the 2026-04-05 design spec:

- `src-tauri/src/screenshot.rs`: adds `capture_full_screen` using CoreGraphics
  CGWindowListCreateImageFromArray, filtering out Thuki's own PID so no window
  hide or flicker is needed. Wrapped by `capture_full_screen_command` Tauri command.
- `src-tauri/src/images.rs`: bumps MAX_IMAGES_PER_MESSAGE from 3 to 4 (3 manual + 1 /screen).
- `src-tauri/src/lib.rs`: registers `capture_full_screen_command` in the invoke handler.
- `src/config/commands.ts`: new command registry (single source of truth for slash commands).
- `src/components/CommandSuggestion.tsx`: presentational popover that renders above the
  ask bar when the user types a / prefix.
- `src/view/AskBarView.tsx`: wires up command suggestion state (show/hide, ArrowDown/Up,
  Tab completion, Escape dismiss).
- `src/App.tsx`: detects /screen at submit time, invokes capture_full_screen_command,
  merges screenshot path with attached images, and calls ask() with clean message.
- `docs/commands.md`: user-facing reference doc for all slash commands.

All tests pass (407 frontend, 110 backend). Frontend and backend coverage at 100% lines.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
…or handling

- Render CommandSuggestion in normal DOM flow (not portal) so the
  ResizeObserver grows the native window to reveal the popover in
  ask-bar mode. Wrap in AnimatePresence + motion.div for smooth
  height transition.

- Fix interactive screenshot crash: wrap window.hide() and
  panel.show_and_make_key() in run_on_main_thread() since AppKit
  APIs must only be called from the macOS main thread (was being
  called from tokio pool thread).

- Rewrite capture_full_screen to use CGWindowListCreateImage with
  CGDisplayBounds (concrete screen coordinates) instead of
  CGWindowListCreateImageFromArray with abstract CGRectNull. Use
  kCGWindowListOptionOnScreenBelowWindow to exclude Thuki's own
  NSPanel from the capture.

- Surface Rust backend errors directly in a capture error banner
  instead of swallowing them silently.

- Fix Enter key requiring two presses on exact trigger match:
  fall through to submit instead of completing with trailing space.

- Add outline-none to history button to prevent native macOS focus
  ring after screenshot capture.

- Smooth ask-bar history panel animation with delayed opacity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
CoreGraphics APIs (CGWindowListCreateImage, CGWindowListCopyWindowInfo,
CGDisplayBounds) internally dispatch work to the macOS main thread.
When called from tokio::task::spawn_blocking (a background thread),
they deadlock because the main thread event loop is not processing
their dispatched calls.

Split capture_full_screen_command into two phases:
1. Capture raw RGBA pixels on the main thread via run_on_main_thread()
   and a oneshot channel to return the result
2. Encode PNG and save to disk on a blocking thread (CPU-bound, no
   CoreGraphics dependency)

This matches the pattern used by capture_screenshot_command, which
already dispatches AppKit calls to the main thread.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
… flight

When the user submits a /screen message, the chat view now shows the
user bubble instantly (with a clean loading placeholder thumbnail)
instead of waiting silently for capture to complete. This prevents
double-submit spam and gives immediate visual feedback.

- handleScreenSubmit sets isSubmitPending + pendingUserMessage before
  the invoke call, then clears/commits on success or restores state on
  failure (query and context are preserved so the user can retry)
- ImageThumbnails gains a placeholder prop: renders a translucent
  bg-black/15 tile with a white spinner instead of a broken image
- ChatBubble detects the SCREEN_CAPTURE_PLACEHOLDER sentinel path and
  sets placeholder: true on the thumbnail item
- SCREEN_CAPTURE_PLACEHOLDER constant extracted to config/commands.ts
  so App.tsx and ChatBubble.tsx share a single source of truth

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
- Replace all em dashes in newly written code with colons or semicolons
  per the global writing style rule (docs, Rust module comments, JSX
  comments, test comments across 7 files)
- Fix non-macOS Rust test: capture_full_screen_returns_err_on_non_macos
  called the nonexistent capture_full_screen(); correct stub is
  capture_full_screen_pixels() (no arguments)
- Clear captureError in replayEntranceAnimation so stale error banners
  do not persist across overlay hide/show cycles
- Fix handleCancel to handle the /screen capture in-flight case: add
  case 2 that signals cancellation via screenCapturePendingRef and
  clears pending UI; handleScreenSubmit checks the ref after await to
  skip ask() when the user cancelled mid-capture
- Fix MAX_IMAGES comment: the constant is the manual attachment limit (3),
  not a mirror of MAX_IMAGES_PER_MESSAGE (4, which includes /screen)
- Add test: cancelling during in-flight capture prevents ask from firing

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
- handleCancel case 2 now restores query and selectedContext from
  screenCaptureInputSnapshotRef so the ask bar returns to its pre-capture
  state when the user aborts an in-flight /screen capture
- requestHideOverlay resets screenCapturePendingRef and snapshot ref so
  closing the overlay during a capture does not trigger a ghost ask()
- replayEntranceAnimation and resetForNewConversation do the same, so
  starting a new session mid-capture is also safe
- Remove superpowers design spec from worktree (development artifact only)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
- handleCancel case 2 now restores query and selectedContext from
  screenCaptureInputSnapshotRef so the ask bar returns to its pre-capture
  state when the user aborts an in-flight /screen capture
- requestHideOverlay resets screenCapturePendingRef and snapshot ref so
  closing the overlay during a capture does not trigger a ghost ask()
- replayEntranceAnimation and resetForNewConversation do the same, so
  starting a new session mid-capture is also safe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
- README features list: add /screen screen capture bullet; update image
  input bullet to clarify it covers both images and screenshots
- README roadmap: mark /screen as shipped in the slash commands item;
  update auto-capture screen context item to note /screen delivers
  full-screen capture today
- CLAUDE.md architecture: add config/commands.ts to Frontend section;
  add screenshot.rs to Backend section
- CLAUDE.md: fix "Command key" -> "Control key" in Key Design Constraints
  to match the rest of the document (activator.rs watches Control, not Command)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
cargo clippy pulls in Tauri's GTK/WebKit dependencies which require
system packages (libwebkit2gtk, libgtk-3-dev) that ubuntu-latest does
not have. Since Thuki is macOS-only and backend tests already run on
macos-latest, switching build validation to the same runner is the
clean fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
@quiet-node
quiet-node force-pushed the worktree-cheerful-sauteeing-pudding branch from b6c0be8 to fac38b4 Compare April 5, 2026 21:58
@quiet-node
quiet-node merged commit 354403a into main Apr 5, 2026
3 checks passed
@quiet-node
quiet-node deleted the worktree-cheerful-sauteeing-pudding branch April 5, 2026 22:10
quiet-node added a commit that referenced this pull request Apr 10, 2026
#35)

* docs: add design spec for /screen command and slash command system

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* docs: update spec to reflect existing screenshot.rs from PR #31

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* feat: add /screen slash command with tab-completion system

Implements the full /screen command feature from the 2026-04-05 design spec:

- `src-tauri/src/screenshot.rs`: adds `capture_full_screen` using CoreGraphics
  CGWindowListCreateImageFromArray, filtering out Thuki's own PID so no window
  hide or flicker is needed. Wrapped by `capture_full_screen_command` Tauri command.
- `src-tauri/src/images.rs`: bumps MAX_IMAGES_PER_MESSAGE from 3 to 4 (3 manual + 1 /screen).
- `src-tauri/src/lib.rs`: registers `capture_full_screen_command` in the invoke handler.
- `src/config/commands.ts`: new command registry (single source of truth for slash commands).
- `src/components/CommandSuggestion.tsx`: presentational popover that renders above the
  ask bar when the user types a / prefix.
- `src/view/AskBarView.tsx`: wires up command suggestion state (show/hide, ArrowDown/Up,
  Tab completion, Escape dismiss).
- `src/App.tsx`: detects /screen at submit time, invokes capture_full_screen_command,
  merges screenshot path with attached images, and calls ask() with clean message.
- `docs/commands.md`: user-facing reference doc for all slash commands.

All tests pass (407 frontend, 110 backend). Frontend and backend coverage at 100% lines.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: command suggestion visibility, screenshot crash, and capture error handling

- Render CommandSuggestion in normal DOM flow (not portal) so the
  ResizeObserver grows the native window to reveal the popover in
  ask-bar mode. Wrap in AnimatePresence + motion.div for smooth
  height transition.

- Fix interactive screenshot crash: wrap window.hide() and
  panel.show_and_make_key() in run_on_main_thread() since AppKit
  APIs must only be called from the macOS main thread (was being
  called from tokio pool thread).

- Rewrite capture_full_screen to use CGWindowListCreateImage with
  CGDisplayBounds (concrete screen coordinates) instead of
  CGWindowListCreateImageFromArray with abstract CGRectNull. Use
  kCGWindowListOptionOnScreenBelowWindow to exclude Thuki's own
  NSPanel from the capture.

- Surface Rust backend errors directly in a capture error banner
  instead of swallowing them silently.

- Fix Enter key requiring two presses on exact trigger match:
  fall through to submit instead of completing with trailing space.

- Add outline-none to history button to prevent native macOS focus
  ring after screenshot capture.

- Smooth ask-bar history panel animation with delayed opacity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: run CoreGraphics capture on main thread to prevent deadlock

CoreGraphics APIs (CGWindowListCreateImage, CGWindowListCopyWindowInfo,
CGDisplayBounds) internally dispatch work to the macOS main thread.
When called from tokio::task::spawn_blocking (a background thread),
they deadlock because the main thread event loop is not processing
their dispatched calls.

Split capture_full_screen_command into two phases:
1. Capture raw RGBA pixels on the main thread via run_on_main_thread()
   and a oneshot channel to return the result
2. Encode PNG and save to disk on a blocking thread (CPU-bound, no
   CoreGraphics dependency)

This matches the pattern used by capture_screenshot_command, which
already dispatches AppKit calls to the main thread.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* feat: show pending chat bubble immediately when /screen capture is in flight

When the user submits a /screen message, the chat view now shows the
user bubble instantly (with a clean loading placeholder thumbnail)
instead of waiting silently for capture to complete. This prevents
double-submit spam and gives immediate visual feedback.

- handleScreenSubmit sets isSubmitPending + pendingUserMessage before
  the invoke call, then clears/commits on success or restores state on
  failure (query and context are preserved so the user can retry)
- ImageThumbnails gains a placeholder prop: renders a translucent
  bg-black/15 tile with a white spinner instead of a broken image
- ChatBubble detects the SCREEN_CAPTURE_PLACEHOLDER sentinel path and
  sets placeholder: true on the thumbnail item
- SCREEN_CAPTURE_PLACEHOLDER constant extracted to config/commands.ts
  so App.tsx and ChatBubble.tsx share a single source of truth

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: address all code review issues before PR

- Replace all em dashes in newly written code with colons or semicolons
  per the global writing style rule (docs, Rust module comments, JSX
  comments, test comments across 7 files)
- Fix non-macOS Rust test: capture_full_screen_returns_err_on_non_macos
  called the nonexistent capture_full_screen(); correct stub is
  capture_full_screen_pixels() (no arguments)
- Clear captureError in replayEntranceAnimation so stale error banners
  do not persist across overlay hide/show cycles
- Fix handleCancel to handle the /screen capture in-flight case: add
  case 2 that signals cancellation via screenCapturePendingRef and
  clears pending UI; handleScreenSubmit checks the ref after await to
  skip ask() when the user cancelled mid-capture
- Fix MAX_IMAGES comment: the constant is the manual attachment limit (3),
  not a mirror of MAX_IMAGES_PER_MESSAGE (4, which includes /screen)
- Add test: cancelling during in-flight capture prevents ask from firing

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: restore input state and reset capture refs on cancel/close/new

- handleCancel case 2 now restores query and selectedContext from
  screenCaptureInputSnapshotRef so the ask bar returns to its pre-capture
  state when the user aborts an in-flight /screen capture
- requestHideOverlay resets screenCapturePendingRef and snapshot ref so
  closing the overlay during a capture does not trigger a ghost ask()
- replayEntranceAnimation and resetForNewConversation do the same, so
  starting a new session mid-capture is also safe
- Remove superpowers design spec from worktree (development artifact only)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: restore input state and reset capture refs on cancel/close/new

- handleCancel case 2 now restores query and selectedContext from
  screenCaptureInputSnapshotRef so the ask bar returns to its pre-capture
  state when the user aborts an in-flight /screen capture
- requestHideOverlay resets screenCapturePendingRef and snapshot ref so
  closing the overlay during a capture does not trigger a ghost ask()
- replayEntranceAnimation and resetForNewConversation do the same, so
  starting a new session mid-capture is also safe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* docs: update README and CLAUDE.md to reflect /screen slash command

- README features list: add /screen screen capture bullet; update image
  input bullet to clarify it covers both images and screenshots
- README roadmap: mark /screen as shipped in the slash commands item;
  update auto-capture screen context item to note /screen delivers
  full-screen capture today
- CLAUDE.md architecture: add config/commands.ts to Frontend section;
  add screenshot.rs to Backend section
- CLAUDE.md: fix "Command key" -> "Control key" in Key Design Constraints
  to match the rest of the document (activator.rs watches Control, not Command)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix(ci): run build validation on macos-latest

cargo clippy pulls in Tauri's GTK/WebKit dependencies which require
system packages (libwebkit2gtk, libgtk-3-dev) that ubuntu-latest does
not have. Since Thuki is macOS-only and backend tests already run on
macos-latest, switching build validation to the same runner is the
clean fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

---------

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
quiet-node added a commit that referenced this pull request Apr 10, 2026
#35)

* docs: add design spec for /screen command and slash command system

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* docs: update spec to reflect existing screenshot.rs from PR #31

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* feat: add /screen slash command with tab-completion system

Implements the full /screen command feature from the 2026-04-05 design spec:

- `src-tauri/src/screenshot.rs`: adds `capture_full_screen` using CoreGraphics
  CGWindowListCreateImageFromArray, filtering out Thuki's own PID so no window
  hide or flicker is needed. Wrapped by `capture_full_screen_command` Tauri command.
- `src-tauri/src/images.rs`: bumps MAX_IMAGES_PER_MESSAGE from 3 to 4 (3 manual + 1 /screen).
- `src-tauri/src/lib.rs`: registers `capture_full_screen_command` in the invoke handler.
- `src/config/commands.ts`: new command registry (single source of truth for slash commands).
- `src/components/CommandSuggestion.tsx`: presentational popover that renders above the
  ask bar when the user types a / prefix.
- `src/view/AskBarView.tsx`: wires up command suggestion state (show/hide, ArrowDown/Up,
  Tab completion, Escape dismiss).
- `src/App.tsx`: detects /screen at submit time, invokes capture_full_screen_command,
  merges screenshot path with attached images, and calls ask() with clean message.
- `docs/commands.md`: user-facing reference doc for all slash commands.

All tests pass (407 frontend, 110 backend). Frontend and backend coverage at 100% lines.

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: command suggestion visibility, screenshot crash, and capture error handling

- Render CommandSuggestion in normal DOM flow (not portal) so the
  ResizeObserver grows the native window to reveal the popover in
  ask-bar mode. Wrap in AnimatePresence + motion.div for smooth
  height transition.

- Fix interactive screenshot crash: wrap window.hide() and
  panel.show_and_make_key() in run_on_main_thread() since AppKit
  APIs must only be called from the macOS main thread (was being
  called from tokio pool thread).

- Rewrite capture_full_screen to use CGWindowListCreateImage with
  CGDisplayBounds (concrete screen coordinates) instead of
  CGWindowListCreateImageFromArray with abstract CGRectNull. Use
  kCGWindowListOptionOnScreenBelowWindow to exclude Thuki's own
  NSPanel from the capture.

- Surface Rust backend errors directly in a capture error banner
  instead of swallowing them silently.

- Fix Enter key requiring two presses on exact trigger match:
  fall through to submit instead of completing with trailing space.

- Add outline-none to history button to prevent native macOS focus
  ring after screenshot capture.

- Smooth ask-bar history panel animation with delayed opacity.

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: run CoreGraphics capture on main thread to prevent deadlock

CoreGraphics APIs (CGWindowListCreateImage, CGWindowListCopyWindowInfo,
CGDisplayBounds) internally dispatch work to the macOS main thread.
When called from tokio::task::spawn_blocking (a background thread),
they deadlock because the main thread event loop is not processing
their dispatched calls.

Split capture_full_screen_command into two phases:
1. Capture raw RGBA pixels on the main thread via run_on_main_thread()
   and a oneshot channel to return the result
2. Encode PNG and save to disk on a blocking thread (CPU-bound, no
   CoreGraphics dependency)

This matches the pattern used by capture_screenshot_command, which
already dispatches AppKit calls to the main thread.

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* feat: show pending chat bubble immediately when /screen capture is in flight

When the user submits a /screen message, the chat view now shows the
user bubble instantly (with a clean loading placeholder thumbnail)
instead of waiting silently for capture to complete. This prevents
double-submit spam and gives immediate visual feedback.

- handleScreenSubmit sets isSubmitPending + pendingUserMessage before
  the invoke call, then clears/commits on success or restores state on
  failure (query and context are preserved so the user can retry)
- ImageThumbnails gains a placeholder prop: renders a translucent
  bg-black/15 tile with a white spinner instead of a broken image
- ChatBubble detects the SCREEN_CAPTURE_PLACEHOLDER sentinel path and
  sets placeholder: true on the thumbnail item
- SCREEN_CAPTURE_PLACEHOLDER constant extracted to config/commands.ts
  so App.tsx and ChatBubble.tsx share a single source of truth

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: address all code review issues before PR

- Replace all em dashes in newly written code with colons or semicolons
  per the global writing style rule (docs, Rust module comments, JSX
  comments, test comments across 7 files)
- Fix non-macOS Rust test: capture_full_screen_returns_err_on_non_macos
  called the nonexistent capture_full_screen(); correct stub is
  capture_full_screen_pixels() (no arguments)
- Clear captureError in replayEntranceAnimation so stale error banners
  do not persist across overlay hide/show cycles
- Fix handleCancel to handle the /screen capture in-flight case: add
  case 2 that signals cancellation via screenCapturePendingRef and
  clears pending UI; handleScreenSubmit checks the ref after await to
  skip ask() when the user cancelled mid-capture
- Fix MAX_IMAGES comment: the constant is the manual attachment limit (3),
  not a mirror of MAX_IMAGES_PER_MESSAGE (4, which includes /screen)
- Add test: cancelling during in-flight capture prevents ask from firing

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: restore input state and reset capture refs on cancel/close/new

- handleCancel case 2 now restores query and selectedContext from
  screenCaptureInputSnapshotRef so the ask bar returns to its pre-capture
  state when the user aborts an in-flight /screen capture
- requestHideOverlay resets screenCapturePendingRef and snapshot ref so
  closing the overlay during a capture does not trigger a ghost ask()
- replayEntranceAnimation and resetForNewConversation do the same, so
  starting a new session mid-capture is also safe
- Remove superpowers design spec from worktree (development artifact only)

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: restore input state and reset capture refs on cancel/close/new

- handleCancel case 2 now restores query and selectedContext from
  screenCaptureInputSnapshotRef so the ask bar returns to its pre-capture
  state when the user aborts an in-flight /screen capture
- requestHideOverlay resets screenCapturePendingRef and snapshot ref so
  closing the overlay during a capture does not trigger a ghost ask()
- replayEntranceAnimation and resetForNewConversation do the same, so
  starting a new session mid-capture is also safe

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* docs: update README and CLAUDE.md to reflect /screen slash command

- README features list: add /screen screen capture bullet; update image
  input bullet to clarify it covers both images and screenshots
- README roadmap: mark /screen as shipped in the slash commands item;
  update auto-capture screen context item to note /screen delivers
  full-screen capture today
- CLAUDE.md architecture: add config/commands.ts to Frontend section;
  add screenshot.rs to Backend section
- CLAUDE.md: fix "Command key" -> "Control key" in Key Design Constraints
  to match the rest of the document (activator.rs watches Control, not Command)

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix(ci): run build validation on macos-latest

cargo clippy pulls in Tauri's GTK/WebKit dependencies which require
system packages (libwebkit2gtk, libgtk-3-dev) that ubuntu-latest does
not have. Since Thuki is macOS-only and backend tests already run on
macos-latest, switching build validation to the same runner is the
clean fix.

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

---------

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
quiet-node added a commit that referenced this pull request Apr 11, 2026
#35)

* docs: add design spec for /screen command and slash command system

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* docs: update spec to reflect existing screenshot.rs from PR #31

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* feat: add /screen slash command with tab-completion system

Implements the full /screen command feature from the 2026-04-05 design spec:

- `src-tauri/src/screenshot.rs`: adds `capture_full_screen` using CoreGraphics
  CGWindowListCreateImageFromArray, filtering out Thuki's own PID so no window
  hide or flicker is needed. Wrapped by `capture_full_screen_command` Tauri command.
- `src-tauri/src/images.rs`: bumps MAX_IMAGES_PER_MESSAGE from 3 to 4 (3 manual + 1 /screen).
- `src-tauri/src/lib.rs`: registers `capture_full_screen_command` in the invoke handler.
- `src/config/commands.ts`: new command registry (single source of truth for slash commands).
- `src/components/CommandSuggestion.tsx`: presentational popover that renders above the
  ask bar when the user types a / prefix.
- `src/view/AskBarView.tsx`: wires up command suggestion state (show/hide, ArrowDown/Up,
  Tab completion, Escape dismiss).
- `src/App.tsx`: detects /screen at submit time, invokes capture_full_screen_command,
  merges screenshot path with attached images, and calls ask() with clean message.
- `docs/commands.md`: user-facing reference doc for all slash commands.

All tests pass (407 frontend, 110 backend). Frontend and backend coverage at 100% lines.

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: command suggestion visibility, screenshot crash, and capture error handling

- Render CommandSuggestion in normal DOM flow (not portal) so the
  ResizeObserver grows the native window to reveal the popover in
  ask-bar mode. Wrap in AnimatePresence + motion.div for smooth
  height transition.

- Fix interactive screenshot crash: wrap window.hide() and
  panel.show_and_make_key() in run_on_main_thread() since AppKit
  APIs must only be called from the macOS main thread (was being
  called from tokio pool thread).

- Rewrite capture_full_screen to use CGWindowListCreateImage with
  CGDisplayBounds (concrete screen coordinates) instead of
  CGWindowListCreateImageFromArray with abstract CGRectNull. Use
  kCGWindowListOptionOnScreenBelowWindow to exclude Thuki's own
  NSPanel from the capture.

- Surface Rust backend errors directly in a capture error banner
  instead of swallowing them silently.

- Fix Enter key requiring two presses on exact trigger match:
  fall through to submit instead of completing with trailing space.

- Add outline-none to history button to prevent native macOS focus
  ring after screenshot capture.

- Smooth ask-bar history panel animation with delayed opacity.

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: run CoreGraphics capture on main thread to prevent deadlock

CoreGraphics APIs (CGWindowListCreateImage, CGWindowListCopyWindowInfo,
CGDisplayBounds) internally dispatch work to the macOS main thread.
When called from tokio::task::spawn_blocking (a background thread),
they deadlock because the main thread event loop is not processing
their dispatched calls.

Split capture_full_screen_command into two phases:
1. Capture raw RGBA pixels on the main thread via run_on_main_thread()
   and a oneshot channel to return the result
2. Encode PNG and save to disk on a blocking thread (CPU-bound, no
   CoreGraphics dependency)

This matches the pattern used by capture_screenshot_command, which
already dispatches AppKit calls to the main thread.

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* feat: show pending chat bubble immediately when /screen capture is in flight

When the user submits a /screen message, the chat view now shows the
user bubble instantly (with a clean loading placeholder thumbnail)
instead of waiting silently for capture to complete. This prevents
double-submit spam and gives immediate visual feedback.

- handleScreenSubmit sets isSubmitPending + pendingUserMessage before
  the invoke call, then clears/commits on success or restores state on
  failure (query and context are preserved so the user can retry)
- ImageThumbnails gains a placeholder prop: renders a translucent
  bg-black/15 tile with a white spinner instead of a broken image
- ChatBubble detects the SCREEN_CAPTURE_PLACEHOLDER sentinel path and
  sets placeholder: true on the thumbnail item
- SCREEN_CAPTURE_PLACEHOLDER constant extracted to config/commands.ts
  so App.tsx and ChatBubble.tsx share a single source of truth

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: address all code review issues before PR

- Replace all em dashes in newly written code with colons or semicolons
  per the global writing style rule (docs, Rust module comments, JSX
  comments, test comments across 7 files)
- Fix non-macOS Rust test: capture_full_screen_returns_err_on_non_macos
  called the nonexistent capture_full_screen(); correct stub is
  capture_full_screen_pixels() (no arguments)
- Clear captureError in replayEntranceAnimation so stale error banners
  do not persist across overlay hide/show cycles
- Fix handleCancel to handle the /screen capture in-flight case: add
  case 2 that signals cancellation via screenCapturePendingRef and
  clears pending UI; handleScreenSubmit checks the ref after await to
  skip ask() when the user cancelled mid-capture
- Fix MAX_IMAGES comment: the constant is the manual attachment limit (3),
  not a mirror of MAX_IMAGES_PER_MESSAGE (4, which includes /screen)
- Add test: cancelling during in-flight capture prevents ask from firing

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: restore input state and reset capture refs on cancel/close/new

- handleCancel case 2 now restores query and selectedContext from
  screenCaptureInputSnapshotRef so the ask bar returns to its pre-capture
  state when the user aborts an in-flight /screen capture
- requestHideOverlay resets screenCapturePendingRef and snapshot ref so
  closing the overlay during a capture does not trigger a ghost ask()
- replayEntranceAnimation and resetForNewConversation do the same, so
  starting a new session mid-capture is also safe
- Remove superpowers design spec from worktree (development artifact only)

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix: restore input state and reset capture refs on cancel/close/new

- handleCancel case 2 now restores query and selectedContext from
  screenCaptureInputSnapshotRef so the ask bar returns to its pre-capture
  state when the user aborts an in-flight /screen capture
- requestHideOverlay resets screenCapturePendingRef and snapshot ref so
  closing the overlay during a capture does not trigger a ghost ask()
- replayEntranceAnimation and resetForNewConversation do the same, so
  starting a new session mid-capture is also safe

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* docs: update README and CLAUDE.md to reflect /screen slash command

- README features list: add /screen screen capture bullet; update image
  input bullet to clarify it covers both images and screenshots
- README roadmap: mark /screen as shipped in the slash commands item;
  update auto-capture screen context item to note /screen delivers
  full-screen capture today
- CLAUDE.md architecture: add config/commands.ts to Frontend section;
  add screenshot.rs to Backend section
- CLAUDE.md: fix "Command key" -> "Control key" in Key Design Constraints
  to match the rest of the document (activator.rs watches Control, not Command)

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

* fix(ci): run build validation on macos-latest

cargo clippy pulls in Tauri's GTK/WebKit dependencies which require
system packages (libwebkit2gtk, libgtk-3-dev) that ubuntu-latest does
not have. Since Thuki is macOS-only and backend tests already run on
macos-latest, switching build validation to the same runner is the
clean fix.

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>

---------

Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
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