From 57c6a4a2a453cc8db4bb55e06c9e7aeb69eb7a8e Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Tue, 19 May 2026 23:30:59 -0700 Subject: [PATCH] fix: do not treat pure-numeric property values as bare-domain URLs Fixes #710 A number property like "Year: 2026" rendered as "0.0.7.234" in the note list column because the WHATWG URL parser canonicalizes integer-only hosts to IPv4 form: new URL('https://2026').hostname === '0.0.7.234'. formatChipLabel called isUrlValue('2026'), which returned true via the bare-domain branch of normalizeExternalUrl, and then asked the URL parser for the hostname. Add a single guard in normalizeExternalUrl: when the input contains no '.', reject it before the bare-domain candidate is constructed. The parseHttpUrl(trimmed) branch above already handles inputs with an explicit scheme (https://...), so the guard only narrows the bare-domain path - the path that produced the bug. Existing cases ('example.com', 'localhost') remain unaffected. Two new vitest cases cover the regression ('2026' and '0') plus a parity case for https://example.com to make sure the scheme branch is untouched. --- src/utils/url.test.ts | 8 ++++++++ src/utils/url.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/src/utils/url.test.ts b/src/utils/url.test.ts index b8f9f2ac..44f34a2d 100644 --- a/src/utils/url.test.ts +++ b/src/utils/url.test.ts @@ -3,6 +3,7 @@ import { invoke } from '@tauri-apps/api/core' import { revealItemInDir } from '@tauri-apps/plugin-opener' import { copyLocalPath, + isUrlValue, normalizeExternalUrl, openExternalUrl, openLocalFile, @@ -24,10 +25,17 @@ function setClipboard(writeText: (value: string) => Promise) { describe('normalizeExternalUrl', () => { it('keeps valid http URLs and normalizes bare domains', () => { + expect(normalizeExternalUrl('https://example.com')).toBe('https://example.com') expect(normalizeExternalUrl('https://example.com/docs')).toBe('https://example.com/docs') expect(normalizeExternalUrl('example.com/docs')).toBe('https://example.com/docs') }) + it('rejects pure numeric values instead of treating them as bare domains', () => { + expect(normalizeExternalUrl('2026')).toBeNull() + expect(normalizeExternalUrl('0')).toBeNull() + expect(isUrlValue('2026')).toBe(false) + }) + it('rejects malformed or unsupported URLs', () => { expect(normalizeExternalUrl('https://exa mple.com')).toBeNull() expect(normalizeExternalUrl('javascript:alert(1)')).toBeNull() diff --git a/src/utils/url.ts b/src/utils/url.ts index 83f63cc6..8c667276 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -28,6 +28,7 @@ export function normalizeExternalUrl(value: string): string | null { } if (parseHttpUrl(trimmed)) return trimmed + if (!trimmed.includes('.')) return null const bareDomainCandidate = `https://${trimmed}` const parsedBareDomain = parseHttpUrl(bareDomainCandidate)