Skip to content

Commit 8f56c1b

Browse files
committed
Merge remote-tracking branch 'origin/main' into processviewer-widget
2 parents 5ebd70a + e6d83d7 commit 8f56c1b

23 files changed

Lines changed: 425 additions & 1172 deletions

File tree

.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:

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 — 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 — 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/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 (

frontend/app/onboarding/onboarding-upgrade-patch.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { UpgradeOnboardingModal_v0_13_1_Content } from "./onboarding-upgrade-v01
2525
import { UpgradeOnboardingModal_v0_14_0_Content } from "./onboarding-upgrade-v0140";
2626
import { UpgradeOnboardingModal_v0_14_1_Content } from "./onboarding-upgrade-v0141";
2727
import { UpgradeOnboardingModal_v0_14_2_Content } from "./onboarding-upgrade-v0142";
28+
import { UpgradeOnboardingModal_v0_14_4_Content } from "./onboarding-upgrade-v0144";
2829

2930
interface VersionConfig {
3031
version: string;
@@ -139,6 +140,12 @@ export const UpgradeOnboardingVersions: VersionConfig[] = [
139140
version: "v0.14.3",
140141
content: () => <UpgradeOnboardingModal_v0_14_2_Content />,
141142
prevText: "Prev (v0.14.1)",
143+
nextText: "Next (v0.14.4)",
144+
},
145+
{
146+
version: "v0.14.4",
147+
content: () => <UpgradeOnboardingModal_v0_14_4_Content />,
148+
prevText: "Prev (v0.14.3)",
142149
},
143150
];
144151

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2026, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
const UpgradeOnboardingModal_v0_14_4_Content = () => {
5+
return (
6+
<div className="flex flex-col items-start gap-6 w-full mb-4 unselectable">
7+
<div className="text-secondary leading-relaxed">
8+
<p className="mb-0">
9+
Wave v0.14.4 introduces vertical tabs, upgrades to xterm.js v6, and includes bug fixes and UI
10+
improvements.
11+
</p>
12+
</div>
13+
14+
<div className="flex w-full items-start gap-4">
15+
<div className="flex-shrink-0">
16+
<i className="text-[24px] text-accent fa-solid fa-table-columns"></i>
17+
</div>
18+
<div className="flex flex-col items-start gap-2 flex-1">
19+
<div className="text-foreground text-base font-semibold leading-[18px]">Vertical Tab Bar</div>
20+
<div className="text-secondary leading-5">
21+
<ul className="list-disc list-outside space-y-1 pl-5">
22+
<li>
23+
<strong>New Vertical Tab Bar Option</strong> - Tabs can now be displayed vertically
24+
along the side of the window for more horizontal space. Toggle between horizontal and
25+
vertical layouts in settings.
26+
</li>
27+
</ul>
28+
</div>
29+
</div>
30+
</div>
31+
32+
<div className="flex w-full items-start gap-4">
33+
<div className="flex-shrink-0">
34+
<i className="text-[24px] text-accent fa-solid fa-terminal"></i>
35+
</div>
36+
<div className="flex flex-col items-start gap-2 flex-1">
37+
<div className="text-foreground text-base font-semibold leading-[18px]">Terminal Improvements</div>
38+
<div className="text-secondary leading-5">
39+
<ul className="list-disc list-outside space-y-1 pl-5">
40+
<li>
41+
<strong>xterm.js v6.0.0 Upgrade</strong> - Improved terminal compatibility and
42+
rendering, resolving quirks with tools like Claude Code
43+
</li>
44+
</ul>
45+
</div>
46+
</div>
47+
</div>
48+
49+
<div className="flex w-full items-start gap-4">
50+
<div className="flex-shrink-0">
51+
<i className="text-[24px] text-accent fa-solid fa-wrench"></i>
52+
</div>
53+
<div className="flex flex-col items-start gap-2 flex-1">
54+
<div className="text-foreground text-base font-semibold leading-[18px]">Other Changes</div>
55+
<div className="text-secondary leading-5">
56+
<ul className="list-disc list-outside space-y-1 pl-5">
57+
<li>
58+
<strong>macOS First Click</strong> - First click now focuses the clicked widget
59+
</li>
60+
<li>
61+
<strong>
62+
<code>backgrounds.json</code>
63+
</strong>{" "}
64+
- Renamed <code>presets/bg.json</code> to <code>backgrounds.json</code>
65+
</li>
66+
<li>
67+
<strong>Config Errors Moved</strong> - Config errors to the WaveConfig view for less
68+
clutter
69+
</li>
70+
<li>WaveConfig now warns on Unsaved Changes</li>
71+
<li>Preview streaming fixes for images/videos</li>
72+
<li>Deprecated legacy AI widget has been removed</li>
73+
<li>[bugfix] Fixed focus bug for newly created blocks</li>
74+
</ul>
75+
</div>
76+
</div>
77+
</div>
78+
</div>
79+
);
80+
};
81+
82+
UpgradeOnboardingModal_v0_14_4_Content.displayName = "UpgradeOnboardingModal_v0_14_4_Content";
83+
84+
export { UpgradeOnboardingModal_v0_14_4_Content };
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { isClaudeCodeCommand } from "./osc-handlers";
4+
5+
describe("isClaudeCodeCommand", () => {
6+
it("matches direct Claude Code invocations", () => {
7+
expect(isClaudeCodeCommand("claude")).toBe(true);
8+
expect(isClaudeCodeCommand("claude --dangerously-skip-permissions")).toBe(true);
9+
});
10+
11+
it("matches Claude Code invocations wrapped with env assignments", () => {
12+
expect(isClaudeCodeCommand('ANTHROPIC_API_KEY="test" claude')).toBe(true);
13+
expect(isClaudeCodeCommand("env FOO=bar claude --print")).toBe(true);
14+
});
15+
16+
it("ignores other commands", () => {
17+
expect(isClaudeCodeCommand("claudes")).toBe(false);
18+
expect(isClaudeCodeCommand("echo claude")).toBe(false);
19+
expect(isClaudeCodeCommand("ls ~/claude")).toBe(false);
20+
expect(isClaudeCodeCommand("cat /logs/claude")).toBe(false);
21+
expect(isClaudeCodeCommand("")).toBe(false);
22+
});
23+
});

frontend/app/view/term/osc-handlers.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const Osc52MaxRawLength = 128 * 1024; // includes selector + base64 + whitespace
2525
// See aiprompts/wave-osc-16162.md for full documentation
2626
export type ShellIntegrationStatus = "ready" | "running-command";
2727

28+
const ClaudeCodeRegex = /^claude\b/;
29+
2830
type Osc16162Command =
2931
| { command: "A"; data: Record<string, never> }
3032
| { command: "C"; data: { cmd64?: string } }
@@ -43,41 +45,56 @@ type Osc16162Command =
4345
| { command: "I"; data: { inputempty?: boolean } }
4446
| { command: "R"; data: Record<string, never> };
4547

48+
function normalizeCmd(decodedCmd: string): string {
49+
let normalizedCmd = decodedCmd.trim();
50+
normalizedCmd = normalizedCmd.replace(/^env\s+/, "");
51+
normalizedCmd = normalizedCmd.replace(/^(?:\w+=(?:"[^"]*"|'[^']*'|\S+)\s+)*/, "");
52+
return normalizedCmd;
53+
}
54+
4655
function checkCommandForTelemetry(decodedCmd: string) {
4756
if (!decodedCmd) {
4857
return;
4958
}
5059

51-
if (decodedCmd.startsWith("ssh ")) {
60+
const normalizedCmd = normalizeCmd(decodedCmd);
61+
62+
if (normalizedCmd.startsWith("ssh ")) {
5263
recordTEvent("conn:connect", { "conn:conntype": "ssh-manual" });
5364
return;
5465
}
5566

5667
const editorsRegex = /^(vim|vi|nano|nvim)\b/;
57-
if (editorsRegex.test(decodedCmd)) {
68+
if (editorsRegex.test(normalizedCmd)) {
5869
recordTEvent("action:term", { "action:type": "cli-edit" });
5970
return;
6071
}
6172

6273
const tailFollowRegex = /(^|\|\s*)tail\s+-[fF]\b/;
63-
if (tailFollowRegex.test(decodedCmd)) {
74+
if (tailFollowRegex.test(normalizedCmd)) {
6475
recordTEvent("action:term", { "action:type": "cli-tailf" });
6576
return;
6677
}
6778

68-
const claudeRegex = /^claude\b/;
69-
if (claudeRegex.test(decodedCmd)) {
79+
if (ClaudeCodeRegex.test(normalizedCmd)) {
7080
recordTEvent("action:term", { "action:type": "claude" });
7181
return;
7282
}
7383

7484
const opencodeRegex = /^opencode\b/;
75-
if (opencodeRegex.test(decodedCmd)) {
85+
if (opencodeRegex.test(normalizedCmd)) {
7686
recordTEvent("action:term", { "action:type": "opencode" });
7787
return;
7888
}
7989
}
8090

91+
export function isClaudeCodeCommand(decodedCmd: string): boolean {
92+
if (!decodedCmd) {
93+
return false;
94+
}
95+
return ClaudeCodeRegex.test(normalizeCmd(decodedCmd));
96+
}
97+
8198
function handleShellIntegrationCommandStart(
8299
termWrap: TermWrap,
83100
blockId: string,
@@ -101,16 +118,20 @@ function handleShellIntegrationCommandStart(
101118
const decodedCmd = base64ToString(cmd.data.cmd64);
102119
rtInfo["shell:lastcmd"] = decodedCmd;
103120
globalStore.set(termWrap.lastCommandAtom, decodedCmd);
121+
const isCC = isClaudeCodeCommand(decodedCmd);
122+
globalStore.set(termWrap.claudeCodeActiveAtom, isCC);
104123
checkCommandForTelemetry(decodedCmd);
105124
} catch (e) {
106125
console.error("Error decoding cmd64:", e);
107126
rtInfo["shell:lastcmd"] = null;
108127
globalStore.set(termWrap.lastCommandAtom, null);
128+
globalStore.set(termWrap.claudeCodeActiveAtom, false);
109129
}
110130
}
111131
} else {
112132
rtInfo["shell:lastcmd"] = null;
113133
globalStore.set(termWrap.lastCommandAtom, null);
134+
globalStore.set(termWrap.claudeCodeActiveAtom, false);
114135
}
115136
rtInfo["shell:lastcmdexitcode"] = null;
116137
}
@@ -287,6 +308,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo
287308
case "A": {
288309
rtInfo["shell:state"] = "ready";
289310
globalStore.set(termWrap.shellIntegrationStatusAtom, "ready");
311+
globalStore.set(termWrap.claudeCodeActiveAtom, false);
290312
const marker = terminal.registerMarker(0);
291313
if (marker) {
292314
termWrap.promptMarkers.push(marker);
@@ -324,6 +346,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo
324346
}
325347
break;
326348
case "D":
349+
globalStore.set(termWrap.claudeCodeActiveAtom, false);
327350
if (cmd.data.exitcode != null) {
328351
rtInfo["shell:lastcmdexitcode"] = cmd.data.exitcode;
329352
} else {
@@ -337,6 +360,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo
337360
break;
338361
case "R":
339362
globalStore.set(termWrap.shellIntegrationStatusAtom, null);
363+
globalStore.set(termWrap.claudeCodeActiveAtom, false);
340364
if (terminal.buffer.active.type === "alternate") {
341365
terminal.write("\x1b[?1049l");
342366
}

frontend/app/view/term/term-model.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { waveEventSubscribeSingle } from "@/app/store/wps";
1010
import { RpcApi } from "@/app/store/wshclientapi";
1111
import { makeFeBlockRouteId } from "@/app/store/wshrouter";
1212
import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil";
13-
import { TerminalView } from "@/app/view/term/term";
13+
import { TermClaudeIcon, TerminalView } from "@/app/view/term/term";
1414
import { TermWshClient } from "@/app/view/term/term-wsh";
1515
import { VDomModel } from "@/app/view/vdom/vdom-model";
1616
import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
@@ -404,10 +404,12 @@ export class TermViewModel implements ViewModel {
404404
return null;
405405
}
406406
const shellIntegrationStatus = get(this.termRef.current.shellIntegrationStatusAtom);
407+
const claudeCodeActive = get(this.termRef.current.claudeCodeActiveAtom);
408+
const icon = claudeCodeActive ? React.createElement(TermClaudeIcon) : "sparkles";
407409
if (shellIntegrationStatus == null) {
408410
return {
409411
elemtype: "iconbutton",
410-
icon: "sparkles",
412+
icon,
411413
className: "text-muted",
412414
title: "No shell integration — Wave AI unable to run commands.",
413415
noAction: true,
@@ -416,14 +418,16 @@ export class TermViewModel implements ViewModel {
416418
if (shellIntegrationStatus === "ready") {
417419
return {
418420
elemtype: "iconbutton",
419-
icon: "sparkles",
421+
icon,
420422
className: "text-accent",
421423
title: "Shell ready — Wave AI can run commands in this terminal.",
422424
noAction: true,
423425
};
424426
}
425427
if (shellIntegrationStatus === "running-command") {
426-
let title = "Shell busy — Wave AI unable to run commands while another command is running.";
428+
let title = claudeCodeActive
429+
? "Claude Code Detected"
430+
: "Shell busy — Wave AI unable to run commands while another command is running.";
427431

428432
if (this.termRef.current) {
429433
const inAltBuffer = this.termRef.current.terminal?.buffer?.active?.type === "alternate";
@@ -436,7 +440,7 @@ export class TermViewModel implements ViewModel {
436440

437441
return {
438442
elemtype: "iconbutton",
439-
icon: "sparkles",
443+
icon,
440444
className: "text-warning",
441445
title: title,
442446
noAction: true,

0 commit comments

Comments
 (0)