From 6ceaade28041b52a7f63b17cbd88946899349d8d Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Mon, 5 Jan 2026 10:09:26 +0900 Subject: [PATCH 1/9] feat(changelog): enhance version changelog display --- .../components/main/body/changelog/index.tsx | 107 ++++++++++++++---- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/apps/desktop/src/components/main/body/changelog/index.tsx b/apps/desktop/src/components/main/body/changelog/index.tsx index 460ea352f2..59d7b710be 100644 --- a/apps/desktop/src/components/main/body/changelog/index.tsx +++ b/apps/desktop/src/components/main/body/changelog/index.tsx @@ -1,14 +1,44 @@ import { openUrl } from "@tauri-apps/plugin-opener"; -import { - ExternalLinkIcon, - GitCompareArrowsIcon, - SparklesIcon, -} from "lucide-react"; +import { ExternalLinkIcon, SparklesIcon } from "lucide-react"; +import { useEffect, useState } from "react"; + +import NoteEditor from "@hypr/tiptap/editor"; +import { md2json } from "@hypr/tiptap/shared"; import { type Tab } from "../../../../store/zustand/tabs"; import { StandardTabWrapper } from "../index"; import { type TabItem, TabItemBase } from "../shared"; +export const changelogFiles = import.meta.glob( + "../../../../../../web/content/changelog/*.mdx", + { query: "?raw", import: "default" }, +); + +export function getLatestVersion(): string | null { + const versions = Object.keys(changelogFiles) + .map((k) => { + const match = k.match(/\/([^/]+)\.mdx$/); + return match ? match[1] : null; + }) + .filter((v): v is string => v !== null) + .filter((v) => !v.includes("nightly")) + .sort((a, b) => b.localeCompare(a, undefined, { numeric: true })); + + return versions[0] || null; +} + +function stripFrontmatter(content: string): string { + const match = content.match(/^---\s*\n[\s\S]*?\n---\s*\n/); + if (match) { + return content.slice(match[0].length).trim(); + } + return content.trim(); +} + +function stripImageLine(content: string): string { + return content.replace(/^!\[.*?\]\(.*?\)\s*\n*/m, ""); +} + export const TabItemChangelog: TabItem> = ({ tab, tabIndex, @@ -41,41 +71,68 @@ export function TabContentChangelog({ }) { const { previous, current } = tab.state; + const { content, loading } = useChangelogContent(current); + return ( -
-
+
+

- Updated to v{current} + v{current}

{previous && (

from v{previous}

)}
-
+
+ {loading ? ( +

Loading...

+ ) : content ? ( + + ) : ( +

+ No changelog available for this version. +

+ )} +
+ +
- {previous && ( - - )}
); } + +function useChangelogContent(version: string) { + const [content, setContent] = useState | null>( + null, + ); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const key = Object.keys(changelogFiles).find((k) => + k.endsWith(`/${version}.mdx`), + ); + + if (!key) { + setLoading(false); + return; + } + + changelogFiles[key]().then((raw) => { + const markdown = stripImageLine(stripFrontmatter(raw as string)); + setContent(md2json(markdown)); + setLoading(false); + }); + }, [version]); + + return { content, loading }; +} From 9657052b776cc0a6790b84dbf9630f8f3de32591 Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Mon, 5 Jan 2026 10:09:02 +0900 Subject: [PATCH 2/9] feat(devtool): add changelog navigation button --- .../src/components/main/sidebar/devtool.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/apps/desktop/src/components/main/sidebar/devtool.tsx b/apps/desktop/src/components/main/sidebar/devtool.tsx index b1f429ee55..b09ab89aae 100644 --- a/apps/desktop/src/components/main/sidebar/devtool.tsx +++ b/apps/desktop/src/components/main/sidebar/devtool.tsx @@ -9,8 +9,10 @@ import { type Store as MainStore, STORE_ID as STORE_ID_PERSISTED, } from "../../../store/tinybase/store/main"; +import { useTabs } from "../../../store/zustand/tabs"; import { type SeedDefinition, seeds } from "../../devtool/seed/index"; import { useTrialExpiredModal } from "../../devtool/trial-expired-modal"; +import { getLatestVersion } from "../body/changelog"; declare global { interface Window { @@ -213,6 +215,8 @@ function SeedCard({ onSeed }: { onSeed: (seed: SeedDefinition) => void }) { } function NavigationCard() { + const openNew = useTabs((s) => s.openNew); + const handleShowMain = useCallback(() => { void windowsCommands.windowShow({ type: "main" }); }, []); @@ -230,6 +234,16 @@ function NavigationCard() { void windowsCommands.windowShow({ type: "control" }); }, []); + const handleShowChangelog = useCallback(() => { + const latestVersion = getLatestVersion(); + if (latestVersion) { + openNew({ + type: "changelog", + state: { current: latestVersion, previous: null }, + }); + } + }, [openNew]); + return (
@@ -272,6 +286,19 @@ function NavigationCard() { > Control +
); From 03a73956101bdda73efab7ed8fb446dcbf1d9feb Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Mon, 5 Jan 2026 10:47:50 +0900 Subject: [PATCH 3/9] fix(ui): optimize link container width class --- apps/web/src/routes/_view/changelog/$slug.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/routes/_view/changelog/$slug.tsx b/apps/web/src/routes/_view/changelog/$slug.tsx index 4ff775e723..73aea8d76f 100644 --- a/apps/web/src/routes/_view/changelog/$slug.tsx +++ b/apps/web/src/routes/_view/changelog/$slug.tsx @@ -244,7 +244,7 @@ function DownloadLinksHeroMobile({ version }: { version: string }) { style={{ transform: `translateX(-${activeIndex * 100}%)` }} > {allLinks.map((link) => ( -
+
Date: Mon, 5 Jan 2026 11:04:51 +0900 Subject: [PATCH 4/9] feat(desktop): update DevtoolCard with optional maxHeight prop --- apps/desktop/src/components/main/sidebar/devtool.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/components/main/sidebar/devtool.tsx b/apps/desktop/src/components/main/sidebar/devtool.tsx index b09ab89aae..414a144962 100644 --- a/apps/desktop/src/components/main/sidebar/devtool.tsx +++ b/apps/desktop/src/components/main/sidebar/devtool.tsx @@ -89,9 +89,11 @@ export function DevtoolView() { function DevtoolCard({ title, children, + maxHeight, }: { title: string; children: React.ReactNode; + maxHeight?: string; }) { return (
@@ -106,7 +109,12 @@ function DevtoolCard({ {title}
-
{children}
+
+ {children} +
); } @@ -191,7 +199,7 @@ function CalendarMockCard() { function SeedCard({ onSeed }: { onSeed: (seed: SeedDefinition) => void }) { return ( - +
{seeds.map((seed) => ( + + See all +
- +
); } @@ -128,7 +177,8 @@ function useChangelogContent(version: string) { } changelogFiles[key]().then((raw) => { - const markdown = stripImageLine(stripFrontmatter(raw as string)); + const stripped = stripImageLine(stripFrontmatter(raw as string)); + const markdown = addSpacingBeforeHeaders(stripped); setContent(md2json(markdown)); setLoading(false); }); @@ -136,3 +186,46 @@ function useChangelogContent(version: string) { return { content, loading }; } + +function useScrollFade() { + const scrollRef = useRef(null); + const [state, setState] = useState({ atStart: true, atEnd: true }); + + const update = useCallback(() => { + const el = scrollRef.current; + if (!el) return; + + const { scrollTop, scrollHeight, clientHeight } = el; + setState({ + atStart: scrollTop <= 1, + atEnd: scrollTop + clientHeight >= scrollHeight - 1, + }); + }, []); + + useResizeObserver({ ref: scrollRef as RefObject, onResize: update }); + + useEffect(() => { + const el = scrollRef.current; + if (!el) return; + + update(); + el.addEventListener("scroll", update); + return () => el.removeEventListener("scroll", update); + }, [update]); + + return { scrollRef, ...state }; +} + +function ScrollFadeOverlay({ position }: { position: "top" | "bottom" }) { + return ( +
+ ); +} From 7c0f3fe63728b4718c17e4fc447a0e1765452140 Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Mon, 5 Jan 2026 11:55:59 +0900 Subject: [PATCH 7/9] refactor(changelog): improve markdown parsing and changelog rendering --- .../components/main/body/changelog/index.tsx | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/apps/desktop/src/components/main/body/changelog/index.tsx b/apps/desktop/src/components/main/body/changelog/index.tsx index 210e96c16c..f01da8d5d4 100644 --- a/apps/desktop/src/components/main/body/changelog/index.tsx +++ b/apps/desktop/src/components/main/body/changelog/index.tsx @@ -51,8 +51,21 @@ function stripImageLine(content: string): string { return content.replace(/^!\[.*?\]\(.*?\)\s*\n*/m, ""); } -function addSpacingBeforeHeaders(content: string): string { - return content.replace(/\n(#{1,6}\s)/g, "\n\n$1"); +function addEmptyParagraphsBeforeHeaders( + json: ReturnType, +): ReturnType { + if (!json.content) return json; + + const newContent: typeof json.content = []; + for (let i = 0; i < json.content.length; i++) { + const node = json.content[i]; + if (node.type === "heading" && i > 0) { + newContent.push({ type: "paragraph" }); + } + newContent.push(node); + } + + return { ...json, content: newContent }; } export const TabItemChangelog: TabItem> = ({ @@ -85,7 +98,7 @@ export function TabContentChangelog({ }: { tab: Extract; }) { - const { previous, current } = tab.state; + const { current } = tab.state; const { content, loading } = useChangelogContent(current); const { scrollRef, atStart, atEnd } = useScrollFade(); @@ -98,24 +111,21 @@ export function TabContentChangelog({
-

- v{current} +

+ What's new in {current}?

- {previous && ( -

from v{previous}

- )}
-
+
{!atStart && } {!atEnd && } -
+
{loading ? ( -

Loading...

+

Loading...

) : content ? ( ) : ( -

+

No changelog available for this version.

)} @@ -151,7 +161,7 @@ function ChangelogHeader({ version }: { version: string }) { className="gap-1.5" onClick={() => openUrl("https://hyprnote.com/changelog")} > - + See all
@@ -177,9 +187,9 @@ function useChangelogContent(version: string) { } changelogFiles[key]().then((raw) => { - const stripped = stripImageLine(stripFrontmatter(raw as string)); - const markdown = addSpacingBeforeHeaders(stripped); - setContent(md2json(markdown)); + const markdown = stripImageLine(stripFrontmatter(raw as string)); + const json = md2json(markdown); + setContent(addEmptyParagraphsBeforeHeaders(json)); setLoading(false); }); }, [version]); From 3eaef0978658fd3b1e40dcdca5ec304724e0c1eb Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Mon, 5 Jan 2026 11:51:41 +0900 Subject: [PATCH 8/9] fix(ui): adjust calendar icon size in session metadata --- .../main/body/sessions/outer-header/metadata/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx index 0eb640357b..fc177f81aa 100644 --- a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx +++ b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx @@ -51,7 +51,7 @@ const TriggerInner = forwardRef< size="sm" className={cn([open && "bg-neutral-100"])} > - + {formatRelativeOrAbsolute(createdAt ? new Date(createdAt) : new Date())} ); From 831d0d5df2055a3f10d40fa22b7451e81806a92c Mon Sep 17 00:00:00 2001 From: John Jeong <63365510+ComputelessComputer@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:07:01 +0900 Subject: [PATCH 9/9] add error handling Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --- .../components/main/body/changelog/index.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/components/main/body/changelog/index.tsx b/apps/desktop/src/components/main/body/changelog/index.tsx index f01da8d5d4..1506b9abb7 100644 --- a/apps/desktop/src/components/main/body/changelog/index.tsx +++ b/apps/desktop/src/components/main/body/changelog/index.tsx @@ -186,12 +186,17 @@ function useChangelogContent(version: string) { return; } - changelogFiles[key]().then((raw) => { - const markdown = stripImageLine(stripFrontmatter(raw as string)); - const json = md2json(markdown); - setContent(addEmptyParagraphsBeforeHeaders(json)); - setLoading(false); - }); + changelogFiles[key]() + .then((raw) => { + const markdown = stripImageLine(stripFrontmatter(raw as string)); + const json = md2json(markdown); + setContent(addEmptyParagraphsBeforeHeaders(json)); + setLoading(false); + }) + .catch(() => { + setContent(null); + setLoading(false); + }); }, [version]); return { content, loading };