Skip to content

CPLAT-10038: add slash command picker and input fixes#3

Merged
sf-jin-ku merged 5 commits into
sendbird:mainfrom
gavin-jeong:CPLAT-10038-slk-slash-picker
May 22, 2026
Merged

CPLAT-10038: add slash command picker and input fixes#3
sf-jin-ku merged 5 commits into
sendbird:mainfrom
gavin-jeong:CPLAT-10038-slk-slash-picker

Conversation

@gavin-jeong
Copy link
Copy Markdown
Collaborator

JIRA: https://sendbird.atlassian.net/browse/CPLAT-10038

Summary

  • show available slash commands when typing / in the slk compose box
  • execute slash commands through Slack's command endpoint
  • merge clipboard/input worktree fixes that support compose input behavior

Test plan

  • go test ./internal/ui
  • go test ./...

Show available slash commands when typing / in compose, execute them through Slack's command endpoint, and merge the pending worktree input improvements for tmux keyboard reporting and clipboard paste handling.
@gavin-jeong gavin-jeong marked this pull request as draft May 22, 2026 16:27
Improve slash command metadata parsing, support slash command execution from thread compose, and learn successfully executed slash commands into the picker list.
@gavin-jeong gavin-jeong marked this pull request as ready for review May 22, 2026 18:19
Resolve conflicts with main while preserving the PR feature changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@sf-jin-ku sf-jin-ku left a comment

Choose a reason for hiding this comment

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

Reviewed conflict resolution and/or existing changes for merge.

sf-jin-ku and others added 2 commits May 22, 2026 11:53
Bring the PR up to date with the merged input, activity, and file picker changes while preserving this PR's behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve the final overlap with Ctrl+Enter send handling while preserving this PR's feature behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sf-jin-ku sf-jin-ku merged commit c41ef8b into sendbird:main May 22, 2026
2 checks passed
sf-jin-ku added a commit that referenced this pull request May 22, 2026
…last-view restore (#18)

* Sort DMs by recency and lift mention-bearing channels

Adds two intra-section sort keys to the sidebar so the left menu
mirrors Slack's "most recent activity wins" UX:

- Direct Messages (1:1 + group DM) now sort by the channel's latest
  observed message ts (channels.latest_synced_ts, already advanced by
  the realtime message handler). DMs with no observed message keep
  their Slack-provided order at the bottom of the section.
- Channels lift items with mention_count > 0 to the top of their
  section and render a "•N" (capped at "•99+") badge next to the
  name. Muted channels suppress both the badge and the unread dot
  per the existing no-notification-surface contract.

Plumbing:
- New channels.mention_count column populated from client.counts at
  bootstrap and during reconnect catch-up. Realtime handler bumps it
  on every inbound message that contains <@selfUserID>, and the read-
  state writer resets it to 0 whenever a channel transitions to
  has_unread=false.
- ReadState exposes LatestTS and MentionCount so the sidebar (which
  already reads ReadState in rebuildFilter and buildCache) picks them
  up without new plumbing.
- SetReadStateReader and Invalidate now re-run rebuildFilter so the
  sort takes effect immediately when read state changes mid-session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix mention-bump races and tighten sort gate

Three correctness fixes flagged in review:

1. OnMessage now skips the mention_count bump when the message is an
   edit (edited=true / message_changed). Without the guard a single
   unread mention could be double-counted on every edit, and editing
   an already-read message to add <@self> would fabricate a fresh
   badge.
2. OnMessage now skips the bump when the sender is the current user.
   Slack never counts your own messages as mentions of yourself; the
   official client echoing a self-mention via WS used to bump slk's
   badge anyway.
3. IncrementChannelMentionCountIfUnread guards the SQL UPDATE with
   `has_unread = 1`. Closes a race where channel_marked clears
   has_unread between OnMessage's two writes; the late increment is
   now a no-op instead of stranding `mention_count=1, has_unread=0`
   in the cache.

Also tightens the sidebar sort/render path:

- effectiveMentionCount(state, item) mirrors the existing render-time
  gate (HasUnread && !IsMuted), and is now the single source of truth
  for both the sort comparator and the badge branch in buildCache.
  Belt-and-suspenders for #3 plus prevents muted channels from
  floating above non-muted siblings on a stale mention_count.

Test coverage added for each fix:
- event_handler_mention_test.go: edited / self-sender / DM / no-mention.
- mention_count_test.go: IfUnread no-op on read channels + bump on
  unread channels.
- sort_test.go: muted-with-mentions stays in place; stale mention on a
  read channel does not lift.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Restrict realtime mention bump to channel-kind only

The realtime OnMessage path used a negative type predicate
(chType != "dm") that let group_dm, app, and any future channel
type through to mention_count writes. The sidebar read side only
renders/sorts mention badges for "channel" + "private" — so the
write side was silently accumulating values nothing displays. Flip
to the positive predicate (chType == "channel" || chType == "private")
so the read and write paths agree on the universe of items that
have a mention_count.

Adds group_dm + app exclusion tests, plus a positive private-channel
test so the existing public-channel path isn't the only one
exercised.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Show loading indicators in sidebar/Threads/Activity during bootstrap

Replace the bare empty-state placeholders ("No channels", "no threads",
"no activity") with explicit "⏳  Loading…" indicators while the panes
are still waiting for their first data load. Without this the global
loading overlay dismisses as soon as the first workspace's connect
returns, leaving the active workspace flashing wrongly-empty panes
until users.conversations / threads-list / activity-list actually
populate.

Plumbing:
- sidebar.Model.bootstrapLoading + SetBootstrapLoading; flips off on
  any SetItems call so a workspace that legitimately has zero
  conversations still gets the "No channels" state.
- threadsview.Model.loading + SetLoading; flips off on first
  SetSummaries (empty results render the existing "no threads" copy).
- activityview.Model.loading + SetLoading; same pattern.
- App.SetLoadingWorkspaces now arms all three so the indicator state
  matches the global overlay.
- WorkspaceReadyMsg's deferInitialReady branch re-arms sidebar
  bootstrap-loading after SetChannels(empty) so DM workspaces don't
  flash "No channels" while their channel list hydrates via WS.

Also expands ListSubscribedThreads so the Threads view includes
cached threads the user is involved in (authored, replied to, or
mentioned), not just active thread_subscriptions. Without this the
Threads panel showed ~10 rows on workspaces with 1900+ involved
cached threads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Restore Threads/Activity as last view + collapse sections from any row

Two related sidebar/UX fixes.

1. Restore Threads/Activity as the last view on workspace startup
   and on workspace switch. Two synthetic last-viewed IDs
   ("__threads__" / "__activity__") encode "user was on this view"
   inside the existing channel_visits machinery, so no new schema is
   needed. Activating either view records its synthetic ID; the
   WorkspaceReadyMsg / WorkspaceSwitchedMsg handlers detect the
   sentinel and dispatch ThreadsViewActivatedMsg or
   ActivityViewActivatedMsg instead of trying to resolve it against
   the channel list.

2. Allow spacebar to toggle a section's collapsed state from any row
   inside it, not just the section header line. Users navigating
   with j/k often want to hide a section without first landing the
   cursor precisely on the header — pressing space on a channel row
   now toggles the row's parent section. Spacebar on Threads /
   Activity stays a no-op (those rows don't belong to a section).

Tests:
- TestToggleCollapseSelected_OnChannelRow_CollapsesParentSection
- TestToggleCollapseSelected_OnSectionHeader (regression)
- TestWorkspaceReadyRestoresThreadsView / RestoresActivityView
- TestThreadsViewActivationRecordsSyntheticVisit
- TestActivityViewActivationRecordsSyntheticVisit

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix spacebar key binding for sidebar section toggle

The ToggleSection binding was registered with key " " but the
bubbletea v2 / ultraviolet KeyPressMsg.String() method explicitly
returns "space" (not " ") for the space character — see
ultraviolet/key.go:391:

    if len(k.Text) > 0 && k.Text != " " {
        return k.Text
    }
    return k.Keystroke()  // -> "space" via keyTypeString[KeySpace]

So key.Matches() compared "space" against [" "] and never matched,
which is why pressing space on a section header (Channels /
Direct Messages / Apps / any custom section) silently did nothing.

Switching the binding key string to "space" makes the comparison
succeed and the existing dispatch path (handleNormalMode →
sidebar.ToggleCollapseSelected) now actually fires.

Regression test (TestSpacebarTogglesSection_{OnDMHeader,
OnChannelsHeader}) drives a tea.KeyPressMsg{Code: ' ', Text: " "}
through App.handleNormalMode and asserts the corresponding section
flips its collapsed state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Restore Enter-opens-channel on sidebar channel rows

The spacebar affordance commit widened sidebar.ToggleCollapseSelected
to also collapse a row's parent section when invoked from a channel
row. handleEnter still called ToggleCollapseSelected first, so
pressing Enter on a channel row collapsed the surrounding section
instead of opening the channel — a clear regression of the documented
Enter-to-open contract.

Fix: in handleEnter, gate the toggle on IsSectionHeaderSelected so
only header rows route through it. Channel rows fall through to the
existing ChannelSelectedMsg dispatch. ToggleCollapseSelected keeps
its widened behavior for the spacebar path, where collapsing from
inside a section is the intended affordance.

Regression tests:
- TestEnterOnChannelRow_OpensChannel asserts Enter on C1 queues a
  ChannelSelectedMsg and leaves Channels expanded.
- TestEnterOnSectionHeader_TogglesSection asserts Enter on the
  Channels header still toggles the section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Drop spacebar section toggle; Enter is the single affordance

User feedback (this PR): the spacebar binding for sidebar section
collapse adds a second keyboard surface for something Enter already
covers, and the Enter-on-channel-row regression that motivated the
recent split was itself caused by the wider ToggleCollapseSelected
behavior the spacebar path required. Remove the second affordance
entirely:

- Drop the ToggleSection field from KeyMap and its binding entry.
- Drop the matching case branch in handleNormalMode.
- Revert sidebar.ToggleCollapseSelected to the original header-only
  shape (Threads / Activity / channel rows are all no-ops).
- handleEnter's gating comment is updated to reflect that the helper
  is intrinsically header-scoped now; channel rows always fall
  through to the ChannelSelectedMsg dispatch.

Test surface:
- Remove space_toggle_test.go and collapse_from_row_test.go (both
  asserted behaviors that no longer exist).
- Add TestEnterOnDMRow_OpensDM so the user's "DM/Channel 둘다임"
  expectation has a regression guard alongside the channel-row case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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.

2 participants