diff --git a/packages/app/e2e/sidebar-help.spec.ts b/packages/app/e2e/sidebar-help.spec.ts index 9f95ed1ca..abf6919f1 100644 --- a/packages/app/e2e/sidebar-help.spec.ts +++ b/packages/app/e2e/sidebar-help.spec.ts @@ -6,6 +6,7 @@ const DISCORD_DESTINATION = /^https:\/\/(?:discord\.gg\/jz8T2uahpH|discord\.com\/invite\/jz8T2uahpH)(?:[/?#]|$)/; const GITHUB_ISSUE_DESTINATION = /^https:\/\/github\.com\/(?:getpaseo\/paseo\/issues\/new(?:\/choose)?(?:[/?#]|$)|login\?return_to=https%3A%2F%2Fgithub\.com%2Fgetpaseo%2Fpaseo%2Fissues%2Fnew$)/; +const CHANGELOG_DESTINATION = /^https:\/\/paseo\.sh\/changelog(?:[/?#]|$)/; const APP_VERSION = /^Paseo v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/; async function openHelpMenu(page: Page): Promise { @@ -52,8 +53,9 @@ test("opens troubleshooting tools from the sidebar help menu", async ({ page }) return { y, height }; }); expect(menuBox.y + menuBox.height).toBeLessThanOrEqual(triggerBox.y); - await expect(page.getByText("Troubleshoot", { exact: true })).toBeVisible(); + await expect(page.getByText("Help", { exact: true })).toBeVisible(); await expect(page.getByText("Report an issue", { exact: true })).toBeVisible(); + await expect(page.getByText("What's new", { exact: true })).toBeVisible(); await expect(page.getByTestId("sidebar-help-version")).toHaveText(APP_VERSION); await page.getByTestId("sidebar-help-diagnostics").click(); @@ -66,7 +68,7 @@ test("opens troubleshooting tools from the sidebar help menu", async ({ page }) await closeSheet(page, "keyboard-shortcuts-dialog"); }); -test("opens the preferred issue-reporting destinations", async ({ page }) => { +test("opens support and release destinations", async ({ page }) => { await gotoAppShell(page); await openHelpMenu(page); @@ -74,6 +76,9 @@ test("opens the preferred issue-reporting destinations", async ({ page }) => { await openHelpMenu(page); await expectExternalPage(page, "sidebar-help-github", GITHUB_ISSUE_DESTINATION); + + await openHelpMenu(page); + await expectExternalPage(page, "sidebar-help-changelog", CHANGELOG_DESTINATION); }); test("keeps diagnostics available from Settings after globalizing the sheet", async ({ page }) => { diff --git a/packages/app/src/components/sidebar/sidebar-help-menu.tsx b/packages/app/src/components/sidebar/sidebar-help-menu.tsx index 315bc8f15..6168d5d39 100644 --- a/packages/app/src/components/sidebar/sidebar-help-menu.tsx +++ b/packages/app/src/components/sidebar/sidebar-help-menu.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from "react"; import { Text, View } from "react-native"; -import { Activity, CircleHelp, Keyboard } from "lucide-react-native"; +import { Activity, CircleHelp, Gift, Keyboard } from "lucide-react-native"; import { useTranslation } from "react-i18next"; import { StyleSheet, withUnistyles } from "react-native-unistyles"; import { DiscordIcon } from "@/components/icons/discord-icon"; @@ -18,16 +18,21 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip import { useIsCompactFormFactor } from "@/constants/layout"; import { isNative } from "@/constants/platform"; import { useAppDiagnosticStore } from "@/diagnostics/store"; +import { useHostRuntimeIsConnected, useHosts } from "@/runtime/host-runtime"; import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store"; +import { useSessionStore } from "@/stores/session-store"; import { ICON_SIZE, type Theme } from "@/styles/theme"; +import type { HostProfile } from "@/types/host-connection"; import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates"; import { resolveAppVersion } from "@/utils/app-version"; import { openExternalUrl } from "@/utils/open-external-url"; const DISCORD_URL = "https://discord.gg/jz8T2uahpH"; const GITHUB_ISSUE_URL = "https://github.com/getpaseo/paseo/issues/new"; +const CHANGELOG_URL = "https://paseo.sh/changelog"; const ThemedActivity = withUnistyles(Activity); const ThemedCircleHelp = withUnistyles(CircleHelp); +const ThemedGift = withUnistyles(Gift); const ThemedKeyboard = withUnistyles(Keyboard); const ThemedDiscordIcon = withUnistyles(DiscordIcon); const ThemedGitHubIcon = withUnistyles(GitHubIcon); @@ -47,6 +52,29 @@ const discordLeadingIcon = ( const githubLeadingIcon = ( ); +const changelogLeadingIcon = ( + +); + +function HostVersionHint({ host }: { host: HostProfile }) { + const { t } = useTranslation(); + const isConnected = useHostRuntimeIsConnected(host.serverId); + const daemonVersion = useSessionStore( + (state) => state.sessions[host.serverId]?.serverInfo?.version ?? null, + ); + const version = isConnected + ? formatVersionWithPrefix(daemonVersion) + : t("settings.about.offline"); + + return ( + + {host.label} {version} + + ); +} export function SidebarHelpMenu() { const { t } = useTranslation(); @@ -56,6 +84,7 @@ export function SidebarHelpMenu() { const [open, setOpen] = useState(false); const showKeyboardShortcuts = !isNative && !isCompactLayout; const version = formatVersionWithPrefix(resolveAppVersion()); + const hosts = useHosts(); const openKeyboardShortcuts = useCallback(() => { setShortcutsDialogOpen(true); @@ -69,6 +98,10 @@ export function SidebarHelpMenu() { void openExternalUrl(GITHUB_ISSUE_URL); }, []); + const openChangelog = useCallback(() => { + void openExternalUrl(CHANGELOG_URL); + }, []); + return ( @@ -94,30 +127,34 @@ export function SidebarHelpMenu() { - {t("sidebar.help.troubleshoot")} - - {t("sidebar.help.diagnostics")} - + {t("sidebar.help.sectionHelp")} {showKeyboardShortcuts ? ( {t("sidebar.help.shortcuts")} ) : null} + + {t("sidebar.help.whatsNew")} + + + {t("sidebar.help.diagnostics")} + {t("sidebar.help.reportIssue")} @@ -125,16 +162,20 @@ export function SidebarHelpMenu() { {t("sidebar.help.github")} - - {t("sidebar.help.version", { version })} - + + + {t("sidebar.help.version", { version })} + + {hosts.map((host) => ( + + ))} + ); @@ -153,4 +194,11 @@ const styles = StyleSheet.create((theme) => ({ fontSize: theme.fontSize.sm, color: theme.colors.popoverForeground, }, + versionList: { + gap: theme.spacing[1], + paddingVertical: theme.spacing[2], + }, + versionHint: { + paddingVertical: 0, + }, })); diff --git a/packages/app/src/components/ui/dropdown-menu.tsx b/packages/app/src/components/ui/dropdown-menu.tsx index 9af347498..8bceebc34 100644 --- a/packages/app/src/components/ui/dropdown-menu.tsx +++ b/packages/app/src/components/ui/dropdown-menu.tsx @@ -675,10 +675,12 @@ export function DropdownMenuSeparator({ export function DropdownMenuHint({ children, + style, testID, -}: PropsWithChildren<{ testID?: string }>): ReactElement { +}: PropsWithChildren<{ style?: ViewStyle | ViewStyle[]; testID?: string }>): ReactElement { + const hintContainerStyle = useMemo(() => [styles.hintContainer, style], [style]); return ( - + {children} ); @@ -908,7 +910,7 @@ const styles = StyleSheet.create((theme) => ({ }, hintContainer: { paddingHorizontal: theme.spacing[3], - paddingBottom: theme.spacing[2], + paddingVertical: theme.spacing[2], }, hintText: { fontSize: theme.fontSize.xs, diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts index 0a39025b1..82e47079c 100644 --- a/packages/app/src/i18n/resources/ar.ts +++ b/packages/app/src/i18n/resources/ar.ts @@ -792,16 +792,13 @@ export const ar: TranslationResources = { }, help: { trigger: "المساعدة والدعم", - troubleshoot: "استكشاف الأخطاء وإصلاحها", + sectionHelp: "المساعدة", diagnostics: "تشغيل التشخيص", - diagnosticsDescription: "جمع تفاصيل التطبيق والمضيفين المتصلين", shortcuts: "اختصارات لوحة المفاتيح", - shortcutsDescription: "عرض اختصارات لوحة المفاتيح المتاحة", reportIssue: "الإبلاغ عن مشكلة", discord: "Discord", - discordDescription: "الأفضل للمساعدة السريعة والنقاش", github: "إنشاء مشكلة على GitHub", - githubDescription: "الإبلاغ عن خطأ يمكن إعادة إنتاجه", + whatsNew: "ما الجديد", version: "Paseo {{version}}", }, sections: { diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index 37eab1582..7f39945c1 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -799,16 +799,13 @@ export const en = { }, help: { trigger: "Help and support", - troubleshoot: "Troubleshoot", + sectionHelp: "Help", diagnostics: "Run diagnostics", - diagnosticsDescription: "Collect app and connected host details", shortcuts: "Keyboard shortcuts", - shortcutsDescription: "View available keyboard shortcuts", reportIssue: "Report an issue", discord: "Discord", - discordDescription: "Best for quick help and discussion", github: "Create GitHub issue", - githubDescription: "Report a reproducible bug", + whatsNew: "What's new", version: "Paseo {{version}}", }, sections: { diff --git a/packages/app/src/i18n/resources/es.ts b/packages/app/src/i18n/resources/es.ts index aba840de3..159fce658 100644 --- a/packages/app/src/i18n/resources/es.ts +++ b/packages/app/src/i18n/resources/es.ts @@ -819,16 +819,13 @@ export const es: TranslationResources = { }, help: { trigger: "Ayuda y soporte", - troubleshoot: "Solucionar problemas", + sectionHelp: "Ayuda", diagnostics: "Ejecutar diagnóstico", - diagnosticsDescription: "Recopila datos de la app y los hosts conectados", shortcuts: "Atajos de teclado", - shortcutsDescription: "Ver los atajos de teclado disponibles", reportIssue: "Informar de un problema", discord: "Discord", - discordDescription: "La mejor opción para ayuda rápida y conversación", github: "Crear incidencia en GitHub", - githubDescription: "Informar de un error reproducible", + whatsNew: "Novedades", version: "Paseo {{version}}", }, sections: { diff --git a/packages/app/src/i18n/resources/fr.ts b/packages/app/src/i18n/resources/fr.ts index 82681b4a0..cbbbbe05d 100644 --- a/packages/app/src/i18n/resources/fr.ts +++ b/packages/app/src/i18n/resources/fr.ts @@ -818,16 +818,13 @@ export const fr: TranslationResources = { }, help: { trigger: "Aide et assistance", - troubleshoot: "Dépannage", + sectionHelp: "Aide", diagnostics: "Lancer le diagnostic", - diagnosticsDescription: "Collecter les détails de l’app et des hôtes connectés", shortcuts: "Raccourcis clavier", - shortcutsDescription: "Afficher les raccourcis clavier disponibles", reportIssue: "Signaler un problème", discord: "Discord", - discordDescription: "Idéal pour obtenir une aide rapide et échanger", github: "Créer un ticket GitHub", - githubDescription: "Signaler un bug reproductible", + whatsNew: "Nouveautés", version: "Paseo {{version}}", }, sections: { diff --git a/packages/app/src/i18n/resources/ja.ts b/packages/app/src/i18n/resources/ja.ts index e43962423..823f7d0e8 100644 --- a/packages/app/src/i18n/resources/ja.ts +++ b/packages/app/src/i18n/resources/ja.ts @@ -804,16 +804,13 @@ export const ja: TranslationResources = { }, help: { trigger: "ヘルプとサポート", - troubleshoot: "トラブルシューティング", + sectionHelp: "ヘルプ", diagnostics: "診断を実行", - diagnosticsDescription: "アプリと接続中のホストの詳細を収集", shortcuts: "キーボードショートカット", - shortcutsDescription: "利用可能なキーボードショートカットを表示", reportIssue: "問題を報告", discord: "Discord", - discordDescription: "すばやいサポートや相談に最適", github: "GitHub Issueを作成", - githubDescription: "再現可能なバグを報告", + whatsNew: "新着情報", version: "Paseo {{version}}", }, sections: { diff --git a/packages/app/src/i18n/resources/pt-BR.ts b/packages/app/src/i18n/resources/pt-BR.ts index 7369b1db8..995896fc7 100644 --- a/packages/app/src/i18n/resources/pt-BR.ts +++ b/packages/app/src/i18n/resources/pt-BR.ts @@ -810,16 +810,13 @@ export const ptBR: TranslationResources = { }, help: { trigger: "Ajuda e suporte", - troubleshoot: "Resolver problemas", + sectionHelp: "Ajuda", diagnostics: "Executar diagnóstico", - diagnosticsDescription: "Coletar detalhes do app e dos hosts conectados", shortcuts: "Atalhos de teclado", - shortcutsDescription: "Ver os atalhos de teclado disponíveis", reportIssue: "Relatar um problema", discord: "Discord", - discordDescription: "Ideal para ajuda rápida e conversa", github: "Criar issue no GitHub", - githubDescription: "Relatar um bug reproduzível", + whatsNew: "Novidades", version: "Paseo {{version}}", }, sections: { diff --git a/packages/app/src/i18n/resources/ru.ts b/packages/app/src/i18n/resources/ru.ts index 208d4dbbc..fb6affc64 100644 --- a/packages/app/src/i18n/resources/ru.ts +++ b/packages/app/src/i18n/resources/ru.ts @@ -811,16 +811,13 @@ export const ru: TranslationResources = { }, help: { trigger: "Помощь и поддержка", - troubleshoot: "Устранение неполадок", + sectionHelp: "Помощь", diagnostics: "Запустить диагностику", - diagnosticsDescription: "Собрать данные приложения и подключённых хостов", shortcuts: "Сочетания клавиш", - shortcutsDescription: "Показать доступные сочетания клавиш", reportIssue: "Сообщить о проблеме", discord: "Discord", - discordDescription: "Для быстрой помощи и обсуждения", github: "Создать issue в GitHub", - githubDescription: "Сообщить о воспроизводимой ошибке", + whatsNew: "Что нового", version: "Paseo {{version}}", }, sections: { diff --git a/packages/app/src/i18n/resources/zh-CN.ts b/packages/app/src/i18n/resources/zh-CN.ts index f97c00a35..6dd094490 100644 --- a/packages/app/src/i18n/resources/zh-CN.ts +++ b/packages/app/src/i18n/resources/zh-CN.ts @@ -787,16 +787,13 @@ export const zhCN: TranslationResources = { }, help: { trigger: "帮助与支持", - troubleshoot: "问题排查", + sectionHelp: "帮助", diagnostics: "运行诊断", - diagnosticsDescription: "收集应用和已连接 Host 的详细信息", shortcuts: "键盘快捷键", - shortcutsDescription: "查看可用的键盘快捷键", reportIssue: "报告问题", discord: "Discord", - discordDescription: "适合快速求助和讨论", github: "创建 GitHub Issue", - githubDescription: "报告可复现的 bug", + whatsNew: "新功能", version: "Paseo {{version}}", }, sections: { diff --git a/packages/website/src/components/landing-page.tsx b/packages/website/src/components/landing-page.tsx index 09c9278d4..5c0ca9fa0 100644 --- a/packages/website/src/components/landing-page.tsx +++ b/packages/website/src/components/landing-page.tsx @@ -84,7 +84,7 @@ export function LandingPage({ title, subtitle }: LandingPageProps) { {/* Content section */} -
+
diff --git a/packages/website/src/routes/changelog.tsx b/packages/website/src/routes/changelog.tsx index 05a5a4881..bbfb19a52 100644 --- a/packages/website/src/routes/changelog.tsx +++ b/packages/website/src/routes/changelog.tsx @@ -4,6 +4,14 @@ import changelogMarkdown from "../../../../CHANGELOG.md?raw"; import { SiteShell } from "~/components/site-shell"; import { pageMeta } from "~/meta"; +interface ChangelogRelease { + version: string; + date: string; + markdown: string; +} + +const releaseHeadingPattern = /^## (.+?) - (\d{4}-\d{2}-\d{2})$/; + export const Route = createFileRoute("/changelog")({ head: () => pageMeta( @@ -14,12 +22,84 @@ export const Route = createFileRoute("/changelog")({ component: Changelog, }); +function formatDate(date: string): string { + const [year, month, day] = date.split("-").map(Number); + return new Intl.DateTimeFormat("en", { + month: "long", + day: "numeric", + year: "numeric", + timeZone: "UTC", + }).format(new Date(Date.UTC(year, month - 1, day))); +} + +function parseChangelog(markdown: string): ChangelogRelease[] { + const releases: ChangelogRelease[] = []; + const versions = new Set(); + let currentRelease: ChangelogRelease | null = null; + + for (const line of markdown.split("\n")) { + const heading = line.match(releaseHeadingPattern); + if (heading) { + const version = heading[1]; + if (versions.has(version)) { + throw new Error(`Duplicate changelog version: ${version}`); + } + versions.add(version); + + if (currentRelease) releases.push(currentRelease); + currentRelease = { + version, + date: heading[2], + markdown: "", + }; + continue; + } + + if (currentRelease) currentRelease.markdown += `${line}\n`; + } + + if (currentRelease) releases.push(currentRelease); + return releases; +} + +const changelogReleases = parseChangelog(changelogMarkdown); + +function Release({ release }: { release: ChangelogRelease }) { + const anchor = `release-${release.version}`; + + return ( +
+ +
+ + + +

+ Paseo {release.version} +

+
+
+ {release.markdown} +
+
+ ); +} + function Changelog() { return ( -
- {changelogMarkdown} -
+
+

Changelog

+ {changelogReleases.map((release) => ( + + ))} +
); } diff --git a/packages/website/src/styles.css b/packages/website/src/styles.css index 9a2020200..2042f34ac 100644 --- a/packages/website/src/styles.css +++ b/packages/website/src/styles.css @@ -1,5 +1,31 @@ @import "tailwindcss"; +* { + scrollbar-color: var(--color-scrollbar-handle) transparent; + scrollbar-width: thin; +} + +*::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +*::-webkit-scrollbar-track { + background: transparent; +} + +*::-webkit-scrollbar-thumb { + border: 2px solid transparent; + border-radius: 9999px; + background: var(--color-scrollbar-handle) content-box; +} + +*::-webkit-scrollbar-button { + display: none; + width: 0; + height: 0; +} + @keyframes voice-bar { 0% { transform: scaleY(1); @@ -19,11 +45,15 @@ } .social-proof-marquee { - margin-inline: calc(50% - 50vw); + margin-inline: calc(50% - 50cqw); overflow-x: clip; position: relative; } +.landing-content { + container-type: inline-size; +} + .social-proof-marquee::before, .social-proof-marquee::after { content: ""; @@ -93,66 +123,124 @@ font-size: 0.875em; } -/* Markdown rendering styles for /changelog */ -.changelog-markdown { - color: rgba(250, 250, 250, 0.82); +/* Changelog */ +.changelog-release { + padding-bottom: 3rem; +} + +.changelog-release + .changelog-release { + border-top: 1px solid var(--color-border); + padding-top: 3rem; +} + +.changelog-release-date { + display: block; + margin-bottom: 0.625rem; + color: var(--color-muted-foreground); + font-size: 0.8125rem; +} + +.changelog-release-heading { + position: relative; + scroll-margin-top: 1.5rem; + margin-bottom: 1.75rem; +} + +.changelog-release-title { + color: var(--color-foreground); + font-size: 1.25rem; + font-weight: 500; + letter-spacing: -0.01em; + line-height: 1.4; +} + +.changelog-release-title span { + color: var(--color-muted-foreground); + font-weight: 400; +} + +.changelog-heading-anchor { + position: absolute; + top: 0; + right: 100%; + bottom: 0; + display: inline-flex; + align-items: center; + padding-right: 0.375rem; + color: color-mix(in srgb, var(--color-foreground) 40%, transparent); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-weight: 400; + text-decoration: none; + opacity: 0; + transition: opacity 150ms ease; +} + +.changelog-release-heading:hover .changelog-heading-anchor, +.changelog-heading-anchor:focus { + opacity: 1; +} + +.changelog-heading-anchor:hover { + color: var(--color-foreground); +} + +.changelog-release-notes { + color: color-mix(in srgb, var(--color-foreground) 76%, transparent); + font-size: 0.9375rem; line-height: 1.7; } -.changelog-markdown h1 { - color: var(--color-foreground); - font-size: 2rem; - line-height: 1.2; - font-weight: 600; - margin-bottom: 1rem; -} - -.changelog-markdown h2 { - color: var(--color-foreground); - font-size: 1.5rem; - line-height: 1.3; - font-weight: 600; - margin-top: 2rem; - margin-bottom: 0.75rem; - padding-bottom: 0.5rem; - border-bottom: 1px solid var(--color-border); -} - -.changelog-markdown h3 { - color: var(--color-foreground); - font-size: 1.125rem; +.changelog-release-notes h3 { + margin-top: 1.75rem; + margin-bottom: 0.625rem; + color: var(--color-muted-foreground); + font-size: 0.875rem; + font-weight: 500; line-height: 1.4; - font-weight: 600; - margin-top: 1.5rem; - margin-bottom: 0.5rem; } -.changelog-markdown p { - margin-top: 0.5rem; - margin-bottom: 0.5rem; +.changelog-release-notes h3:first-child { + margin-top: 0; } -.changelog-markdown ul { - list-style: disc; - margin-left: 1.25rem; - margin-top: 0.75rem; - margin-bottom: 0.75rem; +.changelog-release-notes p { + margin-block: 0.75rem; +} + +.changelog-release-notes ul { display: grid; - gap: 0.5rem; + gap: 0.625rem; + margin-top: 0.75rem; + padding-left: 1.25rem; + list-style: disc; } -.changelog-markdown li { - padding-left: 0.125rem; +.changelog-release-notes li::marker { + color: color-mix(in srgb, var(--color-primary) 65%, var(--color-muted-foreground)); } -.changelog-markdown a { +.changelog-release-notes strong { color: var(--color-foreground); - text-decoration: underline; - text-underline-offset: 2px; + font-weight: 500; } -.changelog-markdown a:hover { - color: rgba(250, 250, 250, 0.8); +.changelog-release-notes a { + color: var(--color-foreground); + text-decoration-color: color-mix(in srgb, var(--color-primary) 65%, transparent); + text-decoration-line: underline; + text-underline-offset: 3px; +} + +.changelog-release-notes a:hover { + color: color-mix(in srgb, var(--color-foreground) 78%, var(--color-primary)); +} + +.changelog-release-notes code { + border-radius: 0.25rem; + background: var(--color-muted); + padding: 0.125rem 0.375rem; + color: var(--color-foreground); + font-size: 0.875em; } /* Docs page prose styles — mirrors the original docs/*.tsx components */ @@ -493,6 +581,7 @@ --color-muted-foreground: #a8adac; --color-card: #171d1c; --color-border: #252b2a; + --color-scrollbar-handle: #717574; --color-primary: #239956; --color-primary-foreground: #ffffff; --color-secondary: #252b2a;