Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7280bf4
update rules
sawka Feb 19, 2026
4c6fd13
first cut at new block-tab based badge system
sawka Feb 20, 2026
51489eb
Merge remote-tracking branch 'origin/main' into sawka/block-indicators
sawka Mar 2, 2026
a75253f
run go generate, fix baseds import
sawka Mar 2, 2026
f7fda6a
Merge remote-tracking branch 'origin/main' into sawka/block-indicators
sawka Mar 5, 2026
f3d27e5
move indicators to their own file (badge.ts)
sawka Mar 5, 2026
95ec727
move tabindicatormap too
sawka Mar 5, 2026
ac7f295
move subscription to badge.ts, clean up some warnings
sawka Mar 5, 2026
e3aa0b8
clean up some warnings
sawka Mar 5, 2026
30788d0
setup FE badge store
sawka Mar 5, 2026
a7acdb9
working on badge integration
sawka Mar 5, 2026
f980494
add clearall for badge event
sawka Mar 5, 2026
260c767
add clearbyid
sawka Mar 5, 2026
c448ee7
add badgewatchpid
sawka Mar 5, 2026
f30f394
working on `wsh badge`
sawka Mar 5, 2026
a88c3bf
hook up pid watching to wsh badge command
sawka Mar 5, 2026
8d6f2ad
checkpoint on moving from tabiindicators to badges
sawka Mar 5, 2026
339cd6c
clear transient tab badges with focus as well
sawka Mar 5, 2026
ccf64e6
more badge migration
sawka Mar 5, 2026
498e0d9
remove tabindicators (backend+frontend), more badges
sawka Mar 6, 2026
09fed4e
getting the badges to show... up to 3 on a tab...
sawka Mar 6, 2026
2fb15c4
add flag color
sawka Mar 6, 2026
1806574
add context menu to flag tab...
sawka Mar 6, 2026
144db86
remove badge persistence
sawka Mar 6, 2026
820f535
focus should not clear pidlinked badges
sawka Mar 6, 2026
897f2d4
update tab bar, change flag to be a flag, resort badges
sawka Mar 6, 2026
c737c6e
Merge remote-tracking branch 'origin/main' into sawka/block-indicators
sawka Mar 6, 2026
e50de18
clean up some scss
sawka Mar 6, 2026
ddc9cff
remove ::after psudo element, just render the dividers in react
sawka Mar 6, 2026
9d1007d
dont use ctx in long running poller
sawka Mar 9, 2026
2518d31
fix nits
sawka Mar 9, 2026
dc2315d
fix nit
sawka Mar 9, 2026
64f6d4f
merge main
sawka Mar 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .roo/rules/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ The full API is defined in custom.d.ts as type ElectronApi.

- **CRITICAL: Completion format MUST be: "Done: [one-line description]"**
- **Keep your Task Completed summaries VERY short**
- **No lengthy pre-completion summaries** - Do not provide detailed explanations of implementation before using attempt_completion
- **No recaps of changes** - Skip explaining what was done before completion
- **No double-summarization** - Put your summary ONLY inside attempt_completion. Do not write a summary in the message body AND then repeat it in attempt_completion. One summary, one place.
- **Go directly to completion** - After making changes, proceed directly to attempt_completion without summarizing
- The project is currently an un-released POC / MVP. Do not worry about backward compatibility when making changes
- With React hooks, always complete all hook calls at the top level before any conditional returns (including jotai hook calls useAtom and useAtomValue); when a user explicitly tells you a function handles null inputs, trust them and stop trying to "protect" it with unnecessary checks or workarounds.
Expand Down
11 changes: 6 additions & 5 deletions cmd/generatego/main-generatego.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ func GenerateWshClient() error {
fmt.Fprintf(os.Stderr, "generating wshclient file to %s\n", WshClientFileName)
var buf strings.Builder
gogen.GenerateBoilerplate(&buf, "wshclient", []string{
"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes",
"github.com/wavetermdev/waveterm/pkg/baseds",
"github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata",
"github.com/wavetermdev/waveterm/pkg/wshutil",
"github.com/wavetermdev/waveterm/pkg/wshrpc",
"github.com/wavetermdev/waveterm/pkg/wconfig",
"github.com/wavetermdev/waveterm/pkg/vdom",
"github.com/wavetermdev/waveterm/pkg/waveobj",
"github.com/wavetermdev/waveterm/pkg/wconfig",
"github.com/wavetermdev/waveterm/pkg/wps",
"github.com/wavetermdev/waveterm/pkg/vdom",
"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes",
"github.com/wavetermdev/waveterm/pkg/wshrpc",
"github.com/wavetermdev/waveterm/pkg/wshutil",
})
wshDeclMap := wshrpc.GenerateWshCommandDeclMap()
for _, key := range utilfn.GetOrderedMapKeys(wshDeclMap) {
Expand Down
6 changes: 5 additions & 1 deletion cmd/server/main-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,11 @@ func main() {
blocklogger.InitBlockLogger()
jobcontroller.InitJobController()
blockcontroller.InitBlockController()
wcore.InitTabIndicatorStore()
err = wcore.InitBadgeStore()
if err != nil {
log.Printf("error initializing badge store: %v\n", err)
return
}
go func() {
defer func() {
panichandler.PanicHandler("GetSystemSummary", recover())
Expand Down
129 changes: 129 additions & 0 deletions cmd/wsh/cmd/wshcmd-badge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
"fmt"
"runtime"

"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/wavetermdev/waveterm/pkg/baseds"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wps"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
"github.com/wavetermdev/waveterm/pkg/wshutil"
)

var badgeCmd = &cobra.Command{
Use: "badge [icon]",
Short: "set or clear a block badge",
Args: cobra.MaximumNArgs(1),
RunE: badgeRun,
PreRunE: preRunSetupRpcClient,
}

var (
badgeColor string
badgePriority float64
badgeClear bool
badgeBeep bool
badgePid int
)

func init() {
rootCmd.AddCommand(badgeCmd)
badgeCmd.Flags().StringVar(&badgeColor, "color", "", "badge color")
badgeCmd.Flags().Float64Var(&badgePriority, "priority", 10, "badge priority")
badgeCmd.Flags().BoolVar(&badgeClear, "clear", false, "clear the badge")
badgeCmd.Flags().BoolVar(&badgeBeep, "beep", false, "play system bell sound")
badgeCmd.Flags().IntVar(&badgePid, "pid", 0, "watch a pid and automatically clear the badge when it exits (default priority 5)")
}

func badgeRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
sendActivity("badge", rtnErr == nil)
}()

if badgePid > 0 && runtime.GOOS == "windows" {
return fmt.Errorf("--pid flag is not supported on Windows")
}
if badgePid > 0 && !cmd.Flags().Changed("priority") {
badgePriority = 5
}

oref, err := resolveBlockArg()
if err != nil {
return fmt.Errorf("resolving block: %v", err)
}
if oref.OType != waveobj.OType_Block && oref.OType != waveobj.OType_Tab {
return fmt.Errorf("badge oref must be a block or tab (got %q)", oref.OType)
}

var eventData baseds.BadgeEvent
eventData.ORef = oref.String()

if badgeClear {
eventData.Clear = true
} else {
icon := "circle-small"
if len(args) > 0 {
icon = args[0]
}
badgeId, err := uuid.NewV7()
if err != nil {
return fmt.Errorf("generating badge id: %v", err)
}
eventData.Badge = &baseds.Badge{
BadgeId: badgeId.String(),
Icon: icon,
Color: badgeColor,
Priority: badgePriority,
PidLinked: badgePid > 0,
}
}

event := wps.WaveEvent{
Event: wps.Event_Badge,
Scopes: []string{oref.String()},
Data: eventData,
}

err = wshclient.EventPublishCommand(RpcClient, event, &wshrpc.RpcOpts{NoResponse: true})
if err != nil {
return fmt.Errorf("publishing badge event: %v", err)
}

if badgeBeep {
err = wshclient.ElectronSystemBellCommand(RpcClient, &wshrpc.RpcOpts{Route: "electron"})
if err != nil {
return fmt.Errorf("playing system bell: %v", err)
}
}

if badgePid > 0 && eventData.Badge != nil {
conn := RpcContext.Conn
if conn == "" {
conn = wshrpc.LocalConnName
}
connRoute := wshutil.MakeConnectionRouteId(conn)
watchData := wshrpc.CommandBadgeWatchPidData{
Pid: badgePid,
ORef: *oref,
BadgeId: eventData.Badge.BadgeId,
}
err = wshclient.BadgeWatchPidCommand(RpcClient, watchData, &wshrpc.RpcOpts{Route: connRoute})
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: This command calls BadgeWatchPidCommand which is only implemented in wshremote (remote server), not in wshserver (local server). When running wsh badge --pid locally without an SSH connection, this will fail with a "method not found" error because there's no handler in the local server. The BadgeWatchPidCommand handler needs to be added to pkg/wshrpc/wshserver/wshserver.go for local PID watching to work.

if err != nil {
return fmt.Errorf("watching pid: %v", err)
}
}

if badgeClear {
fmt.Printf("badge cleared\n")
} else {
fmt.Printf("badge set\n")
}
return nil
}
57 changes: 32 additions & 25 deletions cmd/wsh/cmd/wshcmd-tabindicator.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2025, Command Line Inc.
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

package cmd
Expand All @@ -7,7 +7,9 @@ import (
"fmt"
"os"

"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/wavetermdev/waveterm/pkg/baseds"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wps"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
Expand All @@ -16,28 +18,26 @@ import (

var tabIndicatorCmd = &cobra.Command{
Use: "tabindicator [icon]",
Short: "set or clear a tab indicator",
Short: "set or clear a tab indicator (deprecated: use 'wsh badge')",
Args: cobra.MaximumNArgs(1),
RunE: tabIndicatorRun,
PreRunE: preRunSetupRpcClient,
}

var (
tabIndicatorTabId string
tabIndicatorColor string
tabIndicatorPriority float64
tabIndicatorClear bool
tabIndicatorPersistent bool
tabIndicatorBeep bool
tabIndicatorTabId string
tabIndicatorColor string
tabIndicatorPriority float64
tabIndicatorClear bool
tabIndicatorBeep bool
)

func init() {
rootCmd.AddCommand(tabIndicatorCmd)
tabIndicatorCmd.Flags().StringVar(&tabIndicatorTabId, "tabid", "", "tab id (defaults to WAVETERM_TABID)")
tabIndicatorCmd.Flags().StringVar(&tabIndicatorColor, "color", "", "indicator color")
tabIndicatorCmd.Flags().Float64Var(&tabIndicatorPriority, "priority", 0, "indicator priority")
tabIndicatorCmd.Flags().Float64Var(&tabIndicatorPriority, "priority", 10, "indicator priority")
tabIndicatorCmd.Flags().BoolVar(&tabIndicatorClear, "clear", false, "clear the indicator")
tabIndicatorCmd.Flags().BoolVar(&tabIndicatorPersistent, "persistent", false, "make indicator persistent (don't clear on focus)")
tabIndicatorCmd.Flags().BoolVar(&tabIndicatorBeep, "beep", false, "play system bell sound")
}

Expand All @@ -46,6 +46,8 @@ func tabIndicatorRun(cmd *cobra.Command, args []string) (rtnErr error) {
sendActivity("tabindicator", rtnErr == nil)
}()

fmt.Fprintf(os.Stderr, "tabindicator is deprecated, use 'wsh badge' instead\n")

tabId := tabIndicatorTabId
if tabId == "" {
tabId = os.Getenv("WAVETERM_TABID")
Expand All @@ -54,34 +56,39 @@ func tabIndicatorRun(cmd *cobra.Command, args []string) (rtnErr error) {
return fmt.Errorf("no tab id specified (use --tabid or set WAVETERM_TABID)")
}

var indicator *wshrpc.TabIndicator
if !tabIndicatorClear {
oref := waveobj.MakeORef(waveobj.OType_Tab, tabId)

var eventData baseds.BadgeEvent
eventData.ORef = oref.String()

if tabIndicatorClear {
eventData.Clear = true
} else {
icon := "bell"
if len(args) > 0 {
icon = args[0]
}
indicator = &wshrpc.TabIndicator{
Icon: icon,
Color: tabIndicatorColor,
Priority: tabIndicatorPriority,
ClearOnFocus: !tabIndicatorPersistent,
badgeId, err := uuid.NewV7()
if err != nil {
return fmt.Errorf("generating badge id: %v", err)
}
eventData.Badge = &baseds.Badge{
BadgeId: badgeId.String(),
Icon: icon,
Color: tabIndicatorColor,
Priority: tabIndicatorPriority,
}
}

eventData := wshrpc.TabIndicatorEventData{
TabId: tabId,
Indicator: indicator,
}

event := wps.WaveEvent{
Event: wps.Event_TabIndicator,
Scopes: []string{waveobj.MakeORef(waveobj.OType_Tab, tabId).String()},
Event: wps.Event_Badge,
Scopes: []string{oref.String()},
Data: eventData,
}

err := wshclient.EventPublishCommand(RpcClient, event, &wshrpc.RpcOpts{NoResponse: true})
if err != nil {
return fmt.Errorf("publishing tab indicator event: %v", err)
return fmt.Errorf("publishing badge event: %v", err)
}

if tabIndicatorBeep {
Expand Down
2 changes: 1 addition & 1 deletion emain/emain-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ function makeViewMenu(
label: "Toggle DevTools",
accelerator: devToolsAccel,
click: (_, window) => {
let wc = getWindowWebContents(window) ?? webContents;
const wc = getWindowWebContents(window) ?? webContents;
wc?.toggleDevTools();
},
},
Expand Down
5 changes: 3 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ export default [
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_[a-z0-9]*$",
varsIgnorePattern: "^_[a-z0-9]*$",
argsIgnorePattern: "^(_[a-zA-Z0-9_]*|e|get)$",
varsIgnorePattern: "^(_[a-zA-Z0-9_]*|dlog|e)$",
caughtErrorsIgnorePattern: "^(_[a-zA-Z0-9_]*|e)$",
},
],
"prefer-const": "warn",
Expand Down
Loading
Loading