CPLAT-10038: add slash command picker and input fixes#3
Merged
sf-jin-ku merged 5 commits intoMay 22, 2026
Conversation
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.
Improve slash command metadata parsing, support slash command execution from thread compose, and learn successfully executed slash commands into the picker list.
Resolve conflicts with main while preserving the PR feature changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sf-jin-ku
approved these changes
May 22, 2026
sf-jin-ku
left a comment
There was a problem hiding this comment.
Reviewed conflict resolution and/or existing changes for merge.
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
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>
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.
JIRA: https://sendbird.atlassian.net/browse/CPLAT-10038
Summary
/in the slk compose boxTest plan