Skip to content

Commit e7c22ed

Browse files
authored
Merge branch 'main' into main
2 parents f25653a + e6d83d7 commit e7c22ed

46 files changed

Lines changed: 639 additions & 1488 deletions

Some content is hidden

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

.github/workflows/deploy-docsite.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
- name: Upload Build Artifact
5656
# Only upload the build artifact when pushed to the main branch
5757
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
58-
uses: actions/upload-pages-artifact@v3
58+
uses: actions/upload-pages-artifact@v4
5959
with:
6060
path: docs/build
6161
deploy:

cmd/server/main-server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,6 @@ func shutdownActivityUpdate() {
384384

385385
func createMainWshClient() {
386386
rpc := wshserver.GetMainRpcClient()
387-
wshfs.RpcClient = rpc
388387
wshutil.DefaultRouter.RegisterTrustedLeaf(rpc, wshutil.DefaultRoute)
389388
wps.Broker.SetClient(wshutil.DefaultRouter)
390389
localInitialEnv := envutil.PruneInitialEnv(envutil.SliceToMap(os.Environ()))
@@ -393,6 +392,8 @@ func createMainWshClient() {
393392
localConnWsh := wshutil.MakeWshRpc(wshrpc.RpcContext{Conn: wshrpc.LocalConnName}, remoteImpl, "conn:local")
394393
go wshremote.RunSysInfoLoop(localConnWsh, wshrpc.LocalConnName)
395394
wshutil.DefaultRouter.RegisterTrustedLeaf(localConnWsh, wshutil.MakeConnectionRouteId(wshrpc.LocalConnName))
395+
wshfs.RpcClient = localConnWsh
396+
wshfs.RpcClientRouteId = wshutil.MakeConnectionRouteId(wshrpc.LocalConnName)
396397
}
397398

398399
func grabAndRemoveEnvVars() error {

cmd/wsh/cmd/wshcmd-connserver.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func runListener(listener net.Listener, router *wshutil.WshRouter) {
183183
}
184184
}
185185

186-
func setupConnServerRpcClientWithRouter(router *wshutil.WshRouter, sockName string) (*wshutil.WshRpc, error) {
186+
func setupConnServerRpcClientWithRouter(router *wshutil.WshRouter, sockName string) (*wshutil.WshRpc, string, error) {
187187
routeId := wshutil.MakeConnectionRouteId(connServerConnName)
188188
rpcCtx := wshrpc.RpcContext{
189189
RouteId: routeId,
@@ -196,7 +196,7 @@ func setupConnServerRpcClientWithRouter(router *wshutil.WshRouter, sockName stri
196196

197197
connServerClient := wshutil.MakeWshRpc(rpcCtx, wshremote.MakeRemoteRpcServerImpl(os.Stdout, router, bareClient, false, connServerInitialEnv, sockName), routeId)
198198
router.RegisterTrustedLeaf(connServerClient, routeId)
199-
return connServerClient, nil
199+
return connServerClient, routeId, nil
200200
}
201201

202202
func serverRunRouter() error {
@@ -236,11 +236,12 @@ func serverRunRouter() error {
236236
sockName := getRemoteDomainSocketName()
237237

238238
// setup the connserver rpc client first
239-
client, err := setupConnServerRpcClientWithRouter(router, sockName)
239+
client, bareRouteId, err := setupConnServerRpcClientWithRouter(router, sockName)
240240
if err != nil {
241241
return fmt.Errorf("error setting up connserver rpc client: %v", err)
242242
}
243243
wshfs.RpcClient = client
244+
wshfs.RpcClientRouteId = bareRouteId
244245

245246
log.Printf("trying to get JWT public key")
246247

@@ -360,11 +361,12 @@ func serverRunRouterDomainSocket(jwtToken string) error {
360361
log.Printf("got JWT public key")
361362

362363
// now setup the connserver rpc client
363-
client, err := setupConnServerRpcClientWithRouter(router, sockName)
364+
client, bareRouteId, err := setupConnServerRpcClientWithRouter(router, sockName)
364365
if err != nil {
365366
return fmt.Errorf("error setting up connserver rpc client: %v", err)
366367
}
367368
wshfs.RpcClient = client
369+
wshfs.RpcClientRouteId = bareRouteId
368370

369371
// set up the local domain socket listener for local wsh commands
370372
unixListener, err := MakeRemoteUnixListener()
@@ -402,6 +404,7 @@ func serverRunNormal(jwtToken string) error {
402404
return err
403405
}
404406
wshfs.RpcClient = RpcClient
407+
wshfs.RpcClientRouteId = RpcClientRouteId
405408
WriteStdout("running wsh connserver (%s)\n", RpcContext.Conn)
406409
go func() {
407410
defer func() {

cmd/wsh/cmd/wshcmd-file-util.go

Lines changed: 34 additions & 4 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
@@ -12,10 +12,10 @@ import (
1212
"strings"
1313

1414
"github.com/wavetermdev/waveterm/pkg/remote/connparse"
15-
"github.com/wavetermdev/waveterm/pkg/remote/fileshare/fsutil"
1615
"github.com/wavetermdev/waveterm/pkg/util/fileutil"
1716
"github.com/wavetermdev/waveterm/pkg/wshrpc"
1817
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
18+
"github.com/wavetermdev/waveterm/pkg/wshutil"
1919
)
2020

2121
func convertNotFoundErr(err error) error {
@@ -91,8 +91,38 @@ func streamWriteToFile(fileData wshrpc.FileData, reader io.Reader) error {
9191
}
9292

9393
func streamReadFromFile(ctx context.Context, fileData wshrpc.FileData, writer io.Writer) error {
94-
ch := wshclient.FileReadStreamCommand(RpcClient, fileData, &wshrpc.RpcOpts{Timeout: fileTimeout})
95-
return fsutil.ReadFileStreamToWriter(ctx, ch, writer)
94+
broker := RpcClient.StreamBroker
95+
if broker == nil {
96+
return fmt.Errorf("stream broker not available")
97+
}
98+
if fileData.Info == nil {
99+
return fmt.Errorf("file info is required")
100+
}
101+
readerRouteId := RpcClientRouteId
102+
if readerRouteId == "" {
103+
return fmt.Errorf("no route id available")
104+
}
105+
conn, err := connparse.ParseURI(fileData.Info.Path)
106+
if err != nil {
107+
return fmt.Errorf("parsing file path: %w", err)
108+
}
109+
writerRouteId := wshutil.MakeConnectionRouteId(conn.Host)
110+
reader, streamMeta := broker.CreateStreamReader(readerRouteId, writerRouteId, 256*1024)
111+
defer reader.Close()
112+
go func() {
113+
<-ctx.Done()
114+
reader.Close()
115+
}()
116+
data := wshrpc.CommandFileStreamData{
117+
Info: fileData.Info,
118+
StreamMeta: *streamMeta,
119+
}
120+
_, err = wshclient.FileStreamCommand(RpcClient, data, nil)
121+
if err != nil {
122+
return fmt.Errorf("starting file stream: %w", err)
123+
}
124+
_, err = io.Copy(writer, reader)
125+
return err
96126
}
97127

98128
func fixRelativePaths(path string) (string, error) {

cmd/wsh/cmd/wshcmd-file.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,6 @@ func fileCatRun(cmd *cobra.Command, args []string) error {
172172
return err
173173
}
174174

175-
_, err = checkFileSize(path, MaxFileSize)
176-
if err != nil {
177-
return err
178-
}
179-
180175
fileData := wshrpc.FileData{
181176
Info: &wshrpc.FileInfo{
182177
Path: path}}

cmd/wsh/cmd/wshcmd-root.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var WrappedStdout io.Writer = &WrappedWriter{dest: os.Stdout}
3131
var WrappedStderr io.Writer = &WrappedWriter{dest: os.Stderr}
3232
var RpcClient *wshutil.WshRpc
3333
var RpcContext wshrpc.RpcContext
34+
var RpcClientRouteId string
3435
var UsingTermWshMode bool
3536
var blockArg string
3637
var WshExitCode int
@@ -140,7 +141,12 @@ func setupRpcClientWithToken(swapTokenStr string) (wshrpc.CommandAuthenticateRtn
140141
if err != nil {
141142
return rtn, fmt.Errorf("error setting up domain socket rpc client: %w", err)
142143
}
143-
return wshclient.AuthenticateTokenCommand(RpcClient, wshrpc.CommandAuthenticateTokenData{Token: token.Token}, &wshrpc.RpcOpts{Route: wshutil.ControlRoute})
144+
rtn, err = wshclient.AuthenticateTokenCommand(RpcClient, wshrpc.CommandAuthenticateTokenData{Token: token.Token}, &wshrpc.RpcOpts{Route: wshutil.ControlRoute})
145+
if err != nil {
146+
return rtn, err
147+
}
148+
RpcClientRouteId = rtn.RouteId
149+
return rtn, nil
144150
}
145151

146152
// returns the wrapped stdin and a new rpc client (that wraps the stdin input and stdout output)
@@ -158,10 +164,11 @@ func setupRpcClient(serverImpl wshutil.ServerImpl, jwtToken string) error {
158164
if err != nil {
159165
return fmt.Errorf("error setting up domain socket rpc client: %v", err)
160166
}
161-
_, err = wshclient.AuthenticateCommand(RpcClient, jwtToken, &wshrpc.RpcOpts{Route: wshutil.ControlRoute})
167+
authRtn, err := wshclient.AuthenticateCommand(RpcClient, jwtToken, &wshrpc.RpcOpts{Route: wshutil.ControlRoute})
162168
if err != nil {
163169
return fmt.Errorf("error authenticating: %v", err)
164170
}
171+
RpcClientRouteId = authRtn.RouteId
165172
blockId := os.Getenv("WAVETERM_BLOCKID")
166173
if blockId != "" {
167174
peerInfo := fmt.Sprintf("domain:block:%s", blockId)

docs/docs/releasenotes.mdx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@ sidebar_position: 200
66

77
# Release Notes
88

9+
### v0.14.4 &mdash; Mar 26, 2026
10+
11+
Wave v0.14.4 introduces vertical tabs, upgrades to xterm.js v6, and includes a collection of bug fixes and internal improvements.
12+
13+
**Vertical Tab Bar:**
14+
15+
- **New Vertical Tab Bar Option** - Tabs can now be displayed vertically along the side of the window, giving you more horizontal space and easier access to tabs when you have many open. Toggle between horizontal and vertical tab layouts in settings.
16+
17+
**Terminal Improvements:**
18+
19+
- **xterm.js v6.0.0 Upgrade** - Upgraded to the latest xterm.js v6, bringing improved terminal compatibility and rendering. This should resolve various terminal rendering quirks observed with tools like Claude Code.
20+
21+
**Other Changes:**
22+
23+
- **`backgrounds.json`** - Renamed `presets/bg.json` to `backgrounds.json` and moved background config to new `tab:background` key (auto-migrated on startup)
24+
- **Config Errors Moved** - Config errors removed from the tab bar and moved to Settings / WaveConfig view for less clutter
25+
- **Warn on Unsaved Changes** - WaveConfig view now warns before discarding unsaved changes
26+
- **Stream Performance** - Migrated file streaming to new modern interface with flow control, fixing a large time-to-first-byte streaming bug
27+
- **macOS First Click** - Improved first-click handling on macOS (cancel the click but properly set block/WaveAI focus)
28+
- Deprecated legacy AI widget has been removed
29+
- [bugfix] Fixed focus bug for newly created blocks
30+
- [bugfix] Fixed an issue around starting a new durable session by splitting an old one
31+
- Electron upgraded to v41
32+
- Package updates and dependency upgrades
33+
934
### v0.14.2 &mdash; Mar 12, 2026
1035

1136
Wave v0.14.2 adds block/tab badges, directory preview improvements, and assorted bug fixes.
Lines changed: 1 addition & 0 deletions
Loading

frontend/app/block/block.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => {
149149
const focusElemRef = useRef<HTMLInputElement>(null);
150150
const blockRef = useRef<HTMLDivElement>(null);
151151
const contentRef = useRef<HTMLDivElement>(null);
152+
const pendingFocusRafRef = useRef<number | null>(null);
152153
const [blockClicked, setBlockClicked] = useState(false);
153154
const blockView = useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "view")) ?? "";
154155
const isFocused = useAtomValue(nodeModel.isFocused);
@@ -161,6 +162,14 @@ const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => {
161162
const innerRect = useDebouncedNodeInnerRect(nodeModel);
162163
const noPadding = useAtomValueSafe(viewModel.noPadding);
163164

165+
useEffect(() => {
166+
return () => {
167+
if (pendingFocusRafRef.current != null) {
168+
cancelAnimationFrame(pendingFocusRafRef.current);
169+
}
170+
};
171+
}, []);
172+
164173
useLayoutEffect(() => {
165174
setBlockClicked(isFocused);
166175
}, [isFocused]);
@@ -226,11 +235,21 @@ const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => {
226235
);
227236

228237
const setFocusTarget = useCallback(() => {
238+
if (pendingFocusRafRef.current != null) {
239+
cancelAnimationFrame(pendingFocusRafRef.current);
240+
pendingFocusRafRef.current = null;
241+
}
229242
const ok = viewModel?.giveFocus?.();
230243
if (ok) {
231244
return;
232245
}
233246
focusElemRef.current?.focus({ preventScroll: true });
247+
pendingFocusRafRef.current = requestAnimationFrame(() => {
248+
pendingFocusRafRef.current = null;
249+
if (blockRef.current?.contains(document.activeElement)) {
250+
viewModel?.giveFocus?.();
251+
}
252+
});
234253
}, [viewModel]);
235254

236255
const focusFromPointerEnter = useCallback(

frontend/app/onboarding/onboarding-common.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2026, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
export const CurrentOnboardingVersion = "v0.14.3";
4+
export const CurrentOnboardingVersion = "v0.14.4";
55

66
export function OnboardingGradientBg() {
77
return (

0 commit comments

Comments
 (0)