Skip to content

Commit e41aabf

Browse files
authored
Block Level Indicators/Badges, Update TabBar Styling, Add Badges/Flags to Tabs (#3009)
1 parent 71f7e98 commit e41aabf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1205
-571
lines changed

.roo/rules/rules.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ The full API is defined in custom.d.ts as type ElectronApi.
8484

8585
- **CRITICAL: Completion format MUST be: "Done: [one-line description]"**
8686
- **Keep your Task Completed summaries VERY short**
87-
- **No lengthy pre-completion summaries** - Do not provide detailed explanations of implementation before using attempt_completion
88-
- **No recaps of changes** - Skip explaining what was done before completion
87+
- **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.
8988
- **Go directly to completion** - After making changes, proceed directly to attempt_completion without summarizing
9089
- The project is currently an un-released POC / MVP. Do not worry about backward compatibility when making changes
9190
- 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.

cmd/generatego/main-generatego.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ func GenerateWshClient() error {
2424
fmt.Fprintf(os.Stderr, "generating wshclient file to %s\n", WshClientFileName)
2525
var buf strings.Builder
2626
gogen.GenerateBoilerplate(&buf, "wshclient", []string{
27+
"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes",
28+
"github.com/wavetermdev/waveterm/pkg/baseds",
2729
"github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata",
28-
"github.com/wavetermdev/waveterm/pkg/wshutil",
29-
"github.com/wavetermdev/waveterm/pkg/wshrpc",
30-
"github.com/wavetermdev/waveterm/pkg/wconfig",
30+
"github.com/wavetermdev/waveterm/pkg/vdom",
3131
"github.com/wavetermdev/waveterm/pkg/waveobj",
32+
"github.com/wavetermdev/waveterm/pkg/wconfig",
3233
"github.com/wavetermdev/waveterm/pkg/wps",
33-
"github.com/wavetermdev/waveterm/pkg/vdom",
34-
"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes",
34+
"github.com/wavetermdev/waveterm/pkg/wshrpc",
35+
"github.com/wavetermdev/waveterm/pkg/wshutil",
3536
})
3637
wshDeclMap := wshrpc.GenerateWshCommandDeclMap()
3738
for _, key := range utilfn.GetOrderedMapKeys(wshDeclMap) {

cmd/server/main-server.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,11 @@ func main() {
573573
blocklogger.InitBlockLogger()
574574
jobcontroller.InitJobController()
575575
blockcontroller.InitBlockController()
576-
wcore.InitTabIndicatorStore()
576+
err = wcore.InitBadgeStore()
577+
if err != nil {
578+
log.Printf("error initializing badge store: %v\n", err)
579+
return
580+
}
577581
go func() {
578582
defer func() {
579583
panichandler.PanicHandler("GetSystemSummary", recover())

cmd/wsh/cmd/wshcmd-badge.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2026, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"runtime"
9+
10+
"github.com/google/uuid"
11+
"github.com/spf13/cobra"
12+
"github.com/wavetermdev/waveterm/pkg/baseds"
13+
"github.com/wavetermdev/waveterm/pkg/waveobj"
14+
"github.com/wavetermdev/waveterm/pkg/wps"
15+
"github.com/wavetermdev/waveterm/pkg/wshrpc"
16+
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
17+
"github.com/wavetermdev/waveterm/pkg/wshutil"
18+
)
19+
20+
var badgeCmd = &cobra.Command{
21+
Use: "badge [icon]",
22+
Short: "set or clear a block badge",
23+
Args: cobra.MaximumNArgs(1),
24+
RunE: badgeRun,
25+
PreRunE: preRunSetupRpcClient,
26+
}
27+
28+
var (
29+
badgeColor string
30+
badgePriority float64
31+
badgeClear bool
32+
badgeBeep bool
33+
badgePid int
34+
)
35+
36+
func init() {
37+
rootCmd.AddCommand(badgeCmd)
38+
badgeCmd.Flags().StringVar(&badgeColor, "color", "", "badge color")
39+
badgeCmd.Flags().Float64Var(&badgePriority, "priority", 10, "badge priority")
40+
badgeCmd.Flags().BoolVar(&badgeClear, "clear", false, "clear the badge")
41+
badgeCmd.Flags().BoolVar(&badgeBeep, "beep", false, "play system bell sound")
42+
badgeCmd.Flags().IntVar(&badgePid, "pid", 0, "watch a pid and automatically clear the badge when it exits (default priority 5)")
43+
}
44+
45+
func badgeRun(cmd *cobra.Command, args []string) (rtnErr error) {
46+
defer func() {
47+
sendActivity("badge", rtnErr == nil)
48+
}()
49+
50+
if badgePid > 0 && runtime.GOOS == "windows" {
51+
return fmt.Errorf("--pid flag is not supported on Windows")
52+
}
53+
if badgePid > 0 && !cmd.Flags().Changed("priority") {
54+
badgePriority = 5
55+
}
56+
57+
oref, err := resolveBlockArg()
58+
if err != nil {
59+
return fmt.Errorf("resolving block: %v", err)
60+
}
61+
if oref.OType != waveobj.OType_Block && oref.OType != waveobj.OType_Tab {
62+
return fmt.Errorf("badge oref must be a block or tab (got %q)", oref.OType)
63+
}
64+
65+
var eventData baseds.BadgeEvent
66+
eventData.ORef = oref.String()
67+
68+
if badgeClear {
69+
eventData.Clear = true
70+
} else {
71+
icon := "circle-small"
72+
if len(args) > 0 {
73+
icon = args[0]
74+
}
75+
badgeId, err := uuid.NewV7()
76+
if err != nil {
77+
return fmt.Errorf("generating badge id: %v", err)
78+
}
79+
eventData.Badge = &baseds.Badge{
80+
BadgeId: badgeId.String(),
81+
Icon: icon,
82+
Color: badgeColor,
83+
Priority: badgePriority,
84+
PidLinked: badgePid > 0,
85+
}
86+
}
87+
88+
event := wps.WaveEvent{
89+
Event: wps.Event_Badge,
90+
Scopes: []string{oref.String()},
91+
Data: eventData,
92+
}
93+
94+
err = wshclient.EventPublishCommand(RpcClient, event, &wshrpc.RpcOpts{NoResponse: true})
95+
if err != nil {
96+
return fmt.Errorf("publishing badge event: %v", err)
97+
}
98+
99+
if badgeBeep {
100+
err = wshclient.ElectronSystemBellCommand(RpcClient, &wshrpc.RpcOpts{Route: "electron"})
101+
if err != nil {
102+
return fmt.Errorf("playing system bell: %v", err)
103+
}
104+
}
105+
106+
if badgePid > 0 && eventData.Badge != nil {
107+
conn := RpcContext.Conn
108+
if conn == "" {
109+
conn = wshrpc.LocalConnName
110+
}
111+
connRoute := wshutil.MakeConnectionRouteId(conn)
112+
watchData := wshrpc.CommandBadgeWatchPidData{
113+
Pid: badgePid,
114+
ORef: *oref,
115+
BadgeId: eventData.Badge.BadgeId,
116+
}
117+
err = wshclient.BadgeWatchPidCommand(RpcClient, watchData, &wshrpc.RpcOpts{Route: connRoute})
118+
if err != nil {
119+
return fmt.Errorf("watching pid: %v", err)
120+
}
121+
}
122+
123+
if badgeClear {
124+
fmt.Printf("badge cleared\n")
125+
} else {
126+
fmt.Printf("badge set\n")
127+
}
128+
return nil
129+
}

cmd/wsh/cmd/wshcmd-tabindicator.go

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2025, Command Line Inc.
1+
// Copyright 2026, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

44
package cmd
@@ -7,7 +7,9 @@ import (
77
"fmt"
88
"os"
99

10+
"github.com/google/uuid"
1011
"github.com/spf13/cobra"
12+
"github.com/wavetermdev/waveterm/pkg/baseds"
1113
"github.com/wavetermdev/waveterm/pkg/waveobj"
1214
"github.com/wavetermdev/waveterm/pkg/wps"
1315
"github.com/wavetermdev/waveterm/pkg/wshrpc"
@@ -16,28 +18,26 @@ import (
1618

1719
var tabIndicatorCmd = &cobra.Command{
1820
Use: "tabindicator [icon]",
19-
Short: "set or clear a tab indicator",
21+
Short: "set or clear a tab indicator (deprecated: use 'wsh badge')",
2022
Args: cobra.MaximumNArgs(1),
2123
RunE: tabIndicatorRun,
2224
PreRunE: preRunSetupRpcClient,
2325
}
2426

2527
var (
26-
tabIndicatorTabId string
27-
tabIndicatorColor string
28-
tabIndicatorPriority float64
29-
tabIndicatorClear bool
30-
tabIndicatorPersistent bool
31-
tabIndicatorBeep bool
28+
tabIndicatorTabId string
29+
tabIndicatorColor string
30+
tabIndicatorPriority float64
31+
tabIndicatorClear bool
32+
tabIndicatorBeep bool
3233
)
3334

3435
func init() {
3536
rootCmd.AddCommand(tabIndicatorCmd)
3637
tabIndicatorCmd.Flags().StringVar(&tabIndicatorTabId, "tabid", "", "tab id (defaults to WAVETERM_TABID)")
3738
tabIndicatorCmd.Flags().StringVar(&tabIndicatorColor, "color", "", "indicator color")
38-
tabIndicatorCmd.Flags().Float64Var(&tabIndicatorPriority, "priority", 0, "indicator priority")
39+
tabIndicatorCmd.Flags().Float64Var(&tabIndicatorPriority, "priority", 10, "indicator priority")
3940
tabIndicatorCmd.Flags().BoolVar(&tabIndicatorClear, "clear", false, "clear the indicator")
40-
tabIndicatorCmd.Flags().BoolVar(&tabIndicatorPersistent, "persistent", false, "make indicator persistent (don't clear on focus)")
4141
tabIndicatorCmd.Flags().BoolVar(&tabIndicatorBeep, "beep", false, "play system bell sound")
4242
}
4343

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

49+
fmt.Fprintf(os.Stderr, "tabindicator is deprecated, use 'wsh badge' instead\n")
50+
4951
tabId := tabIndicatorTabId
5052
if tabId == "" {
5153
tabId = os.Getenv("WAVETERM_TABID")
@@ -54,34 +56,39 @@ func tabIndicatorRun(cmd *cobra.Command, args []string) (rtnErr error) {
5456
return fmt.Errorf("no tab id specified (use --tabid or set WAVETERM_TABID)")
5557
}
5658

57-
var indicator *wshrpc.TabIndicator
58-
if !tabIndicatorClear {
59+
oref := waveobj.MakeORef(waveobj.OType_Tab, tabId)
60+
61+
var eventData baseds.BadgeEvent
62+
eventData.ORef = oref.String()
63+
64+
if tabIndicatorClear {
65+
eventData.Clear = true
66+
} else {
5967
icon := "bell"
6068
if len(args) > 0 {
6169
icon = args[0]
6270
}
63-
indicator = &wshrpc.TabIndicator{
64-
Icon: icon,
65-
Color: tabIndicatorColor,
66-
Priority: tabIndicatorPriority,
67-
ClearOnFocus: !tabIndicatorPersistent,
71+
badgeId, err := uuid.NewV7()
72+
if err != nil {
73+
return fmt.Errorf("generating badge id: %v", err)
74+
}
75+
eventData.Badge = &baseds.Badge{
76+
BadgeId: badgeId.String(),
77+
Icon: icon,
78+
Color: tabIndicatorColor,
79+
Priority: tabIndicatorPriority,
6880
}
69-
}
70-
71-
eventData := wshrpc.TabIndicatorEventData{
72-
TabId: tabId,
73-
Indicator: indicator,
7481
}
7582

7683
event := wps.WaveEvent{
77-
Event: wps.Event_TabIndicator,
78-
Scopes: []string{waveobj.MakeORef(waveobj.OType_Tab, tabId).String()},
84+
Event: wps.Event_Badge,
85+
Scopes: []string{oref.String()},
7986
Data: eventData,
8087
}
8188

8289
err := wshclient.EventPublishCommand(RpcClient, event, &wshrpc.RpcOpts{NoResponse: true})
8390
if err != nil {
84-
return fmt.Errorf("publishing tab indicator event: %v", err)
91+
return fmt.Errorf("publishing badge event: %v", err)
8592
}
8693

8794
if tabIndicatorBeep {

emain/emain-menu.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ function makeViewMenu(
227227
label: "Toggle DevTools",
228228
accelerator: devToolsAccel,
229229
click: (_, window) => {
230-
let wc = getWindowWebContents(window) ?? webContents;
230+
const wc = getWindowWebContents(window) ?? webContents;
231231
wc?.toggleDevTools();
232232
},
233233
},

eslint.config.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,9 @@ export default [
7676
"@typescript-eslint/no-unused-vars": [
7777
"warn",
7878
{
79-
argsIgnorePattern: "^_[a-z0-9]*$",
80-
varsIgnorePattern: "^_[a-z0-9]*$",
79+
argsIgnorePattern: "^(_[a-zA-Z0-9_]*|e|get)$",
80+
varsIgnorePattern: "^(_[a-zA-Z0-9_]*|dlog|e)$",
81+
caughtErrorsIgnorePattern: "^(_[a-zA-Z0-9_]*|e)$",
8182
},
8283
],
8384
"prefer-const": "warn",

0 commit comments

Comments
 (0)