From 72feb96002e42ba018551d7d70e24cb69177eb89 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 17 Sep 2026 18:23:21 +0800 Subject: [PATCH 1/3] fix(nav): add in-app back button and prevent LAST_DOC_ID from drifting on every navigation - Add a back button (ArrowLeft icon) to the TopBar breadcrumbs slot that calls window.history.back(), leveraging the existing pushState/popstate infrastructure in @directory/util.ts for in-app navigation without browser chrome - Change LAST_DOC_ID persistence to only write on the first doc load per page session, preventing in-session sidebar navigation from silently overwriting the entry point that "Get Started" and external hosts rely on - Add i18n keys for the back button label (goBack) in zh-cn and en Closes: https://github.com/bytefolk/doc/issues/66 --- messages/en.json | 3 ++- messages/zh-cn.json | 3 ++- .../work/[id]/(content)/content-for-my-doc.tsx | 13 +++++++++++-- src/app/[locale]/work/[id]/top-bar.tsx | 11 ++++++++++- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/messages/en.json b/messages/en.json index 4f59012..e2d3f04 100644 --- a/messages/en.json +++ b/messages/en.json @@ -9,7 +9,8 @@ "documentActions": "Document actions", "moreDocumentActions": "More document actions", "changeLanguage": "Change language, current {locale}", - "closeDialog": "Close dialog" + "closeDialog": "Close dialog", + "goBack": "Go back" }, "metadata": { "title": "doc — self-hosted collaborative document infrastructure", diff --git a/messages/zh-cn.json b/messages/zh-cn.json index ea6bf72..c3f632e 100644 --- a/messages/zh-cn.json +++ b/messages/zh-cn.json @@ -9,7 +9,8 @@ "documentActions": "文档操作", "moreDocumentActions": "更多文档操作", "changeLanguage": "切换语言,当前为 {locale}", - "closeDialog": "关闭对话框" + "closeDialog": "关闭对话框", + "goBack": "返回上一页" }, "metadata": { "title": "doc — 自托管协作文档基础设施", diff --git a/src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx b/src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx index cccfc08..22068cd 100644 --- a/src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx +++ b/src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx @@ -19,6 +19,10 @@ import { getRandomElement } from '@/lib/utils' import { flushCurrentDocVersionByBeacon, flushDocVersionById } from '@/lib/doc-version/client' import AutoGrowingTitle from '@/components/auto-growing-title' +// Track whether we've written LAST_DOC_ID for this page session. +// Prevents in-session navigation from overwriting the entry point. +let hasRecordedEntryDoc = false + export default function ContentForMyDoc() { const docs = useDocsStore((s) => s.docs) const id = useDocsStore((s) => s.curDocId) @@ -27,9 +31,14 @@ export default function ContentForMyDoc() { const updateDocTitle = useDocsStore((s) => s.updateDocTitle) const updateDocIcon = useDocsStore((s) => s.updateDocIcon) - // record last doc id + // Record entry doc id only once per page session. + // This ensures the "Get Started" button returns users to where they entered, + // not to the last doc they browsed to before closing. useEffect(() => { - localStorage.setItem(LAST_DOC_ID_KEY, id) // 保存最后一次打开的文档 id + if (!hasRecordedEntryDoc) { + localStorage.setItem(LAST_DOC_ID_KEY, id) + hasRecordedEntryDoc = true + } }, [id]) useEffect(() => { diff --git a/src/app/[locale]/work/[id]/top-bar.tsx b/src/app/[locale]/work/[id]/top-bar.tsx index 61aa73c..3832027 100644 --- a/src/app/[locale]/work/[id]/top-bar.tsx +++ b/src/app/[locale]/work/[id]/top-bar.tsx @@ -1,7 +1,7 @@ 'use client' import { useMemo, useState } from 'react' -import { Ellipsis } from 'lucide-react' +import { ArrowLeft, Ellipsis } from 'lucide-react' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Button } from '@/components/ui/button' import Logo from '@/components/logo-component' @@ -39,6 +39,15 @@ export default function TopBar() { className="max-[1023px]:pl-14 max-[480px]:pr-2" breadcrumbs={
+
From b76e567f7bece6eb19edaed5174ecb635c0f573d Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Sep 2026 09:49:58 +0800 Subject: [PATCH 2/3] fix(nav): use sessionStorage for entry doc flag and hide back button on direct load - Replace module-level hasRecordedEntryDoc with sessionStorage to properly scope the flag to the page session (survives SPA nav, resets on reload) - Hide back button until first in-app navigation to avoid confusion on direct page load with no history --- .../[id]/(content)/content-for-my-doc.tsx | 11 ++---- src/app/[locale]/work/[id]/top-bar.tsx | 34 +++++++++++++------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx b/src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx index 22068cd..7551aaa 100644 --- a/src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx +++ b/src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx @@ -19,10 +19,6 @@ import { getRandomElement } from '@/lib/utils' import { flushCurrentDocVersionByBeacon, flushDocVersionById } from '@/lib/doc-version/client' import AutoGrowingTitle from '@/components/auto-growing-title' -// Track whether we've written LAST_DOC_ID for this page session. -// Prevents in-session navigation from overwriting the entry point. -let hasRecordedEntryDoc = false - export default function ContentForMyDoc() { const docs = useDocsStore((s) => s.docs) const id = useDocsStore((s) => s.curDocId) @@ -32,12 +28,11 @@ export default function ContentForMyDoc() { const updateDocIcon = useDocsStore((s) => s.updateDocIcon) // Record entry doc id only once per page session. - // This ensures the "Get Started" button returns users to where they entered, - // not to the last doc they browsed to before closing. + // Uses sessionStorage to survive SPA navigation but reset on full page reload. useEffect(() => { - if (!hasRecordedEntryDoc) { + if (!sessionStorage.getItem('hasRecordedEntryDoc')) { localStorage.setItem(LAST_DOC_ID_KEY, id) - hasRecordedEntryDoc = true + sessionStorage.setItem('hasRecordedEntryDoc', 'true') } }, [id]) diff --git a/src/app/[locale]/work/[id]/top-bar.tsx b/src/app/[locale]/work/[id]/top-bar.tsx index 3832027..c35c18e 100644 --- a/src/app/[locale]/work/[id]/top-bar.tsx +++ b/src/app/[locale]/work/[id]/top-bar.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo, useState } from 'react' +import { useMemo, useState, useEffect } from 'react' import { ArrowLeft, Ellipsis } from 'lucide-react' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Button } from '@/components/ui/button' @@ -24,6 +24,8 @@ import { useUserStore } from '@/stores/user-store' import { Topbar } from '@fullstack-ai-infra/ui' import { useTranslations } from 'next-intl' import { useCompactWorkspace } from '@/hooks/use-compact-workspace' +import emitter from '@/lib/emitter' +import { EVENT_KEY_NAV_DOC } from '@/constants' export default function TopBar() { const t = useTranslations('common') @@ -33,21 +35,33 @@ export default function TopBar() { const doc = useMemo(() => docs.find((d) => d.id === id), [docs, id]) const userInfo = useUserStore((s) => s.userInfo) + // Track whether there's in-app navigation history for the back button. + const [hasHistory, setHasHistory] = useState(false) + useEffect(() => { + const onNav = () => setHasHistory(true) + emitter.on(EVENT_KEY_NAV_DOC, onNav) + return () => { + emitter.off(EVENT_KEY_NAV_DOC, onNav) + } + }, []) + return ( - + {hasHistory && ( + + )}
From b68710fce8e5ff00bd0d23d3948053058190212f Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Sep 2026 11:30:09 +0800 Subject: [PATCH 3/3] docs: add CHANGELOG entry for back button and entry doc persistence (#66) --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 194c29b..040ebe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ All notable changes to `doc` are documented here. ### Fixed +- Add an in-app back button to the TopBar that appears only after the first in-app navigation, + and scope the entry-document flag to `sessionStorage` so it survives SPA navigation but resets + on full page reload (#66). - Replace hardcoded `timeAgo` strings with `next-intl` translation keys and return ISO-8601 `createdAt` from the version API so clients can format timestamps with `Intl.DateTimeFormat`. Remove unused `useTimeAgo` hook export and use real `messages/zh-cn.json` in `dt.test.ts`