fix(tui): guarded hyperlinks before settings init

Return plain text from hyperlink helpers until Settings.init() has completed so image paste placeholders cannot crash early editor renders.

Fixes #3064
This commit is contained in:
roboomp
2026-06-19 18:14:40 +00:00
parent 259bb004e1
commit d99a6489dd
3 changed files with 21 additions and 3 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed image paste placeholders falling through to terminal hyperlink settings before `Settings.init()`, so early editor rendering falls back to plain text instead of crashing. ([#3064](https://github.com/can1357/oh-my-pi/issues/3064))
## [16.1.3] - 2026-06-19
### Changed
+6 -3
View File
@@ -7,7 +7,7 @@
*/
import * as url from "node:url";
import { TERMINAL } from "@oh-my-pi/pi-tui";
import { settings } from "../config/settings";
import { isSettingsInitialized, settings } from "../config/settings";
import {
LocalProtocolHandler,
memoryRootsFromRegistry,
@@ -45,8 +45,10 @@ function buildFileUri(filePath: string, opts?: { line?: number; col?: number }):
* - `"off"`: never
* - `"auto"`: when `process.stdout.isTTY`, `NO_COLOR` is unset, and the detected terminal reports hyperlink support
* - `"always"`: unconditionally (useful for viewers that support OSC 8 without advertising it)
* Before settings initialization, returns false so early render paths stay plain text.
*/
export function isHyperlinkEnabled(): boolean {
if (!isSettingsInitialized()) return false;
const mode = settings.get("tui.hyperlinks");
if (mode === "off") return false;
if (mode === "always") return true;
@@ -104,10 +106,11 @@ export function urlHyperlink(url: string, displayText: string): string {
* Wrap `displayText` in an OSC 8 hyperlink pointing at an HTTP(S) URL,
* bypassing terminal capability auto-detection. Used for auth prompts where
* an inert "click" label blocks login on terminals whose capabilities are
* not advertised. Still returns plain text when the user has explicitly
* opted out via `tui.hyperlinks=off`.
* not advertised. Still returns plain text before settings initialization or
* when the user has explicitly opted out via `tui.hyperlinks=off`.
*/
export function urlHyperlinkAlways(url: string, displayText: string): string {
if (!isSettingsInitialized()) return displayText;
if (settings.get("tui.hyperlinks") === "off") return displayText;
const normalized = url.match(/^www\./i) ? `https://${url}` : url;
try {
@@ -60,6 +60,17 @@ afterEach(() => {
});
describe("isHyperlinkEnabled", () => {
it("falls back to plain text before Settings.init", async () => {
resetSettingsForTest();
try {
expect(isHyperlinkEnabled()).toBe(false);
expect(fileHyperlink(path.resolve("/Users/foo/bar.ts"), "bar.ts")).toBe("bar.ts");
expect(urlHyperlinkAlways("https://example.com/path", "example")).toBe("example");
} finally {
await Settings.init({ inMemory: true });
}
});
it('returns false when mode is "off"', () => {
setHyperlinkMode("off");
expect(isHyperlinkEnabled()).toBe(false);