-
-
Notifications
You must be signed in to change notification settings - Fork 927
Block Level Indicators/Badges, Update TabBar Styling, Add Badges/Flags to Tabs #3009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
7280bf4
update rules
sawka 4c6fd13
first cut at new block-tab based badge system
sawka 51489eb
Merge remote-tracking branch 'origin/main' into sawka/block-indicators
sawka a75253f
run go generate, fix baseds import
sawka f7fda6a
Merge remote-tracking branch 'origin/main' into sawka/block-indicators
sawka f3d27e5
move indicators to their own file (badge.ts)
sawka 95ec727
move tabindicatormap too
sawka ac7f295
move subscription to badge.ts, clean up some warnings
sawka e3aa0b8
clean up some warnings
sawka 30788d0
setup FE badge store
sawka a7acdb9
working on badge integration
sawka f980494
add clearall for badge event
sawka 260c767
add clearbyid
sawka c448ee7
add badgewatchpid
sawka f30f394
working on `wsh badge`
sawka a88c3bf
hook up pid watching to wsh badge command
sawka 8d6f2ad
checkpoint on moving from tabiindicators to badges
sawka 339cd6c
clear transient tab badges with focus as well
sawka ccf64e6
more badge migration
sawka 498e0d9
remove tabindicators (backend+frontend), more badges
sawka 09fed4e
getting the badges to show... up to 3 on a tab...
sawka 2fb15c4
add flag color
sawka 1806574
add context menu to flag tab...
sawka 144db86
remove badge persistence
sawka 820f535
focus should not clear pidlinked badges
sawka 897f2d4
update tab bar, change flag to be a flag, resort badges
sawka c737c6e
Merge remote-tracking branch 'origin/main' into sawka/block-indicators
sawka e50de18
clean up some scss
sawka ddc9cff
remove ::after psudo element, just render the dividers in react
sawka 9d1007d
dont use ctx in long running poller
sawka 2518d31
fix nits
sawka dc2315d
fix nit
sawka 64f6d4f
merge main
sawka File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
first cut at new block-tab based badge system
- Loading branch information
commit 4c6fd13c7496c322bf0e660d7bbca4102f1c201c
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| // Copyright 2025, Command Line Inc. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package wcore | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "log" | ||
| "sync" | ||
| "time" | ||
|
|
||
| "github.com/wavetermdev/waveterm/pkg/baseds" | ||
| "github.com/wavetermdev/waveterm/pkg/util/utilfn" | ||
| "github.com/wavetermdev/waveterm/pkg/waveobj" | ||
| "github.com/wavetermdev/waveterm/pkg/wps" | ||
| "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" | ||
| "github.com/wavetermdev/waveterm/pkg/wstore" | ||
| ) | ||
|
|
||
| // BadgeStore is a write-through cache for badges. | ||
| // Each oref can carry two independent badges: | ||
| // - a persistent badge (stored in the DB and survives restarts) | ||
| // - a transient badge (in-memory only, cleared on restart) | ||
| // | ||
| // Values are stored by value (not pointer) to prevent external mutation. | ||
| type BadgeStore struct { | ||
| lock *sync.Mutex | ||
| persistent map[string]baseds.Badge // keyed by oref string | ||
| transient map[string]baseds.Badge // keyed by oref string | ||
| } | ||
|
|
||
| var globalBadgeStore = &BadgeStore{ | ||
| lock: &sync.Mutex{}, | ||
| persistent: make(map[string]baseds.Badge), | ||
| transient: make(map[string]baseds.Badge), | ||
| } | ||
|
|
||
| // InitBadgeStore loads all persisted badges from the DB into the in-memory | ||
| // cache and subscribes to incoming badge events. | ||
| func InitBadgeStore() error { | ||
| log.Printf("initializing badge store\n") | ||
|
|
||
| ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) | ||
| defer cancelFn() | ||
|
|
||
| // Load persisted badges from all tabs. | ||
| tabs, err := wstore.DBGetAllObjsByType[*waveobj.Tab](ctx, waveobj.OType_Tab) | ||
| if err != nil { | ||
| return fmt.Errorf("badge store: error loading tabs from DB: %w", err) | ||
| } | ||
| for _, tab := range tabs { | ||
| if tab.Badge != nil { | ||
| oref := waveobj.MakeORef(waveobj.OType_Tab, tab.OID).String() | ||
| globalBadgeStore.persistent[oref] = *tab.Badge | ||
| } | ||
| } | ||
|
|
||
| // Load persisted badges from all blocks. | ||
| blocks, err := wstore.DBGetAllObjsByType[*waveobj.Block](ctx, waveobj.OType_Block) | ||
| if err != nil { | ||
| return fmt.Errorf("badge store: error loading blocks from DB: %w", err) | ||
| } | ||
| for _, block := range blocks { | ||
| if block.Badge != nil { | ||
| oref := waveobj.MakeORef(waveobj.OType_Block, block.OID).String() | ||
| globalBadgeStore.persistent[oref] = *block.Badge | ||
| } | ||
| } | ||
|
|
||
| log.Printf("badge store: loaded %d persisted badges\n", len(globalBadgeStore.persistent)) | ||
|
|
||
| // Subscribe to badge events so we can update the cache when events arrive. | ||
| rpcClient := wshclient.GetBareRpcClient() | ||
| rpcClient.EventListener.On(wps.Event_Badge, handleBadgeEvent) | ||
| wshclient.EventSubCommand(rpcClient, wps.SubscriptionRequest{ | ||
| Event: wps.Event_Badge, | ||
| AllScopes: true, | ||
| }, nil) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func handleBadgeEvent(event *wps.WaveEvent) { | ||
| if event.Event != wps.Event_Badge { | ||
| return | ||
| } | ||
| var data baseds.BadgeEvent | ||
| err := utilfn.ReUnmarshal(&data, event.Data) | ||
| if err != nil { | ||
| log.Printf("badge store: error unmarshaling BadgeEvent: %v\n", err) | ||
| return | ||
| } | ||
| if data.ORef == "" { | ||
| log.Printf("badge store: received badge event with empty oref\n") | ||
| return | ||
| } | ||
|
|
||
| oref, err := waveobj.ParseORef(data.ORef) | ||
| if err != nil { | ||
| log.Printf("badge store: error parsing oref %q: %v\n", data.ORef, err) | ||
| return | ||
| } | ||
|
|
||
| setBadge(oref, data.Badge, data.Persistent, data.Clear) | ||
| } | ||
|
|
||
| // setBadge updates the appropriate in-memory map and, when persistent, writes | ||
| // through to the DB and fires a WaveObjUpdate event so the frontend stays in sync. | ||
| func setBadge(oref waveobj.ORef, badge *baseds.Badge, persistent bool, clear bool) { | ||
| globalBadgeStore.lock.Lock() | ||
| defer globalBadgeStore.lock.Unlock() | ||
|
|
||
| orefStr := oref.String() | ||
|
|
||
| if persistent { | ||
| if clear || badge == nil { | ||
| delete(globalBadgeStore.persistent, orefStr) | ||
| log.Printf("badge store: persistent badge cleared: oref=%s\n", orefStr) | ||
| go persistBadge(oref, nil) | ||
| } else { | ||
| globalBadgeStore.persistent[orefStr] = *badge | ||
| log.Printf("badge store: persistent badge set: oref=%s badge=%+v\n", orefStr, *badge) | ||
| go persistBadge(oref, badge) | ||
| } | ||
| } else { | ||
| if clear || badge == nil { | ||
| delete(globalBadgeStore.transient, orefStr) | ||
| log.Printf("badge store: transient badge cleared: oref=%s\n", orefStr) | ||
| } else { | ||
| globalBadgeStore.transient[orefStr] = *badge | ||
| log.Printf("badge store: transient badge set: oref=%s badge=%+v\n", orefStr, *badge) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // persistBadge writes the badge (or nil to clear) to the appropriate DB object. | ||
| func persistBadge(oref waveobj.ORef, badge *baseds.Badge) { | ||
| ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) | ||
| defer cancelFn() | ||
|
|
||
| switch oref.OType { | ||
| case waveobj.OType_Tab: | ||
| err := wstore.DBUpdateFn[*waveobj.Tab](ctx, oref.OID, func(tab *waveobj.Tab) { | ||
| tab.Badge = badge | ||
| }) | ||
| if err != nil { | ||
| log.Printf("badge store: error persisting badge for tab %s: %v\n", oref.OID, err) | ||
| return | ||
| } | ||
| log.Printf("badge store: persisted badge for tab %s\n", oref.OID) | ||
| SendWaveObjUpdate(oref) | ||
|
|
||
| case waveobj.OType_Block: | ||
| err := wstore.DBUpdateFn[*waveobj.Block](ctx, oref.OID, func(block *waveobj.Block) { | ||
| block.Badge = badge | ||
| }) | ||
| if err != nil { | ||
| log.Printf("badge store: error persisting badge for block %s: %v\n", oref.OID, err) | ||
| return | ||
| } | ||
| log.Printf("badge store: persisted badge for block %s\n", oref.OID) | ||
| SendWaveObjUpdate(oref) | ||
|
|
||
| default: | ||
| log.Printf("badge store: unsupported oref type for persistence: %s\n", oref.OType) | ||
| } | ||
| } | ||
|
|
||
| // GetAllBadges returns a snapshot of all currently active badges as a slice of | ||
| // BadgeEvent values. Each entry carries the ORef, the Persistent flag, and the | ||
| // Badge itself. An oref that has both a persistent and a transient badge will | ||
| // appear twice in the result. | ||
| func GetAllBadges() []baseds.BadgeEvent { | ||
| globalBadgeStore.lock.Lock() | ||
| defer globalBadgeStore.lock.Unlock() | ||
|
|
||
| result := make([]baseds.BadgeEvent, 0, len(globalBadgeStore.persistent)+len(globalBadgeStore.transient)) | ||
| for orefStr, badge := range globalBadgeStore.persistent { | ||
| b := badge // copy | ||
| result = append(result, baseds.BadgeEvent{ | ||
| ORef: orefStr, | ||
| Persistent: true, | ||
| Badge: &b, | ||
| }) | ||
| } | ||
| for orefStr, badge := range globalBadgeStore.transient { | ||
| b := badge // copy | ||
| result = append(result, baseds.BadgeEvent{ | ||
| ORef: orefStr, | ||
| Badge: &b, | ||
| }) | ||
| } | ||
| return result | ||
| } | ||
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
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
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
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bubble up badge subscription failures.
Line 36 ignores the
EventSubCommanderror, so the new startup check incmd/server/main-server.gocan never fire and the server can come up with a non-functional badge store.Suggested fix
func InitBadgeStore() error { log.Printf("initializing badge store\n") rpcClient := wshclient.GetBareRpcClient() rpcClient.EventListener.On(wps.Event_Badge, handleBadgeEvent) - wshclient.EventSubCommand(rpcClient, wps.SubscriptionRequest{ + if err := wshclient.EventSubCommand(rpcClient, wps.SubscriptionRequest{ Event: wps.Event_Badge, AllScopes: true, - }, nil) + }, nil); err != nil { + return err + } return nil }📝 Committable suggestion
🤖 Prompt for AI Agents