Skip to content

Commit 37defed

Browse files
fix(media): default terminal QR to full-block output (#77820)
Avoid node-qrcode compact (small) terminal mode, which emits a dense ANSI final row that breaks scanning on some terminals. Covers WhatsApp/Feishu login flows and the pairing QR CLI path. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 0c977cd commit 37defed

9 files changed

Lines changed: 29 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Docs: https://docs.openclaw.ai
6969

7070
### Fixes
7171

72+
- CLI/media: render terminal QR codes with full-block characters by default so the bundled `qrcode` terminal renderer does not emit a pathologically dense ANSI final row in compact half-block mode that breaks scanning in some terminals. Fixes #77820. Thanks @KrasimirKralev.
7273
- TUI/sessions: bound the session picker to recent rows and use exact lookup-style refreshes for the active session, so dusty stores no longer make TUI hydrate weeks-old transcripts before becoming responsive. Thanks @vincentkoc.
7374
- Doctor/gateway: report recent supervisor restart handoffs in `openclaw doctor --deep`, using the installed service environment when available so service-managed clean exits are visible in guided diagnostics. Thanks @shakkernerd.
7475
- Gateway/status: show recent supervisor restart handoffs in `openclaw gateway status --deep`, including JSON details, so clean service-managed restarts are reported as restart handoffs instead of opaque stopped-service diagnostics. Thanks @shakkernerd.

extensions/feishu/src/app-registration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ export async function pollAppRegistration(params: {
252252
* otherwise the pattern is corrupted and cannot be scanned.
253253
*/
254254
export async function printQrCode(url: string): Promise<void> {
255-
const output = await renderQrTerminal(url, { small: true });
255+
const output = await renderQrTerminal(url);
256256
process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
257257
}
258258

extensions/whatsapp/src/login.coverage.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ describe("loginWeb coverage", () => {
147147
);
148148
expect(runtime.log).toHaveBeenCalledWith("terminal:initial-qr");
149149
expect(runtime.log).toHaveBeenCalledWith("terminal:restart-qr");
150-
expect(renderQrTerminalMock).toHaveBeenCalledWith("initial-qr", { small: true });
151-
expect(renderQrTerminalMock).toHaveBeenCalledWith("restart-qr", { small: true });
150+
expect(renderQrTerminalMock).toHaveBeenCalledWith("initial-qr");
151+
expect(renderQrTerminalMock).toHaveBeenCalledWith("restart-qr");
152152
});
153153

154154
it("clears creds and throws when logged out", async () => {

extensions/whatsapp/src/login.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export async function loginWeb(
2222
const restoredFromBackup = await restoreCredsFromBackupIfNeeded(account.authDir);
2323
const onQr = (qr: string) => {
2424
runtime.log("Open the WhatsApp app, go to Linked Devices, then scan this QR:");
25-
void renderQrTerminal(qr, { small: true })
25+
void renderQrTerminal(qr)
2626
.then((output) => {
2727
runtime.log(output.endsWith("\n") ? output.slice(0, -1) : output);
2828
})

extensions/whatsapp/src/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ async function safeSaveCreds(
119119
}
120120

121121
async function printTerminalQr(qr: string): Promise<void> {
122-
const output = await renderQrTerminal(qr, { small: true });
122+
const output = await renderQrTerminal(qr);
123123
process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
124124
}
125125

src/cli/qr-cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type QrCliOptions = {
2525
};
2626

2727
function renderQrAscii(data: string): Promise<string> {
28-
return renderQrTerminal(data, { small: true });
28+
return renderQrTerminal(data);
2929
}
3030
function readDevicePairPublicUrlFromConfig(cfg: OpenClawConfig): string | undefined {
3131
const value = cfg.plugins?.entries?.["device-pair"]?.config?.["publicUrl"];
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { describe, expect, it } from "vitest";
2+
import { renderQrTerminal } from "./qr-terminal.ts";
3+
4+
describe("renderQrTerminal (real qrcode runtime)", () => {
5+
it("keeps per-row ANSI sequence counts in line with typical rows", async () => {
6+
const sample = "https://wa.me/login/2@SAMPLE-TOKEN-1234567890ABCDEF";
7+
const rendered = await renderQrTerminal(sample);
8+
const ansiSgr = new RegExp(`${String.fromCharCode(0x1b)}\\[[0-9;]*m`, "g");
9+
const escCounts = rendered
10+
.split(/\r?\n/)
11+
.map((line) => (line.match(ansiSgr) ?? []).length)
12+
.filter((count) => count > 0);
13+
expect(escCounts.length).toBeGreaterThan(0);
14+
const sorted = escCounts.toSorted((a, b) => a - b);
15+
const median = sorted[Math.floor(sorted.length / 2)] ?? 0;
16+
const max = Math.max(...escCounts);
17+
expect(median).toBeGreaterThan(0);
18+
expect(max).toBeLessThanOrEqual(median * 6);
19+
});
20+
});

src/media/qr-terminal.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe("renderQrTerminal", () => {
1616
it("delegates terminal rendering to qrcode", async () => {
1717
await expect(renderQrTerminal("openclaw")).resolves.toBe("ASCII-QR");
1818
expect(toString).toHaveBeenCalledWith("openclaw", {
19-
small: true,
19+
small: false,
2020
type: "terminal",
2121
});
2222
});

src/media/qr-terminal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export async function renderQrTerminal(
66
): Promise<string> {
77
const qrCode = await loadQrCodeRuntime();
88
return await qrCode.toString(normalizeQrText(input), {
9-
small: opts.small ?? true,
9+
small: opts.small ?? false,
1010
type: "terminal",
1111
});
1212
}

0 commit comments

Comments
 (0)