Background
The doc web pages are often opened inside an embedded container (webview / panel) that has no browser chrome — no back button, no forward button, no address bar. Users navigate between documents entirely through the in-page sidebar tree. This issue covers two interlocking problems that leave users stranded once they navigate away from their entry point.
Entry & button lifecycle (external host context)
An external host application provides a "Connect Doc" / "Open Doc" button with two states:
- Unconfigured: shows "Connect Doc"; user pastes a doc web URL once.
- Configured: shows "Open Doc"; clicking always opens the saved URL.
The entry URL is written once by the user and should remain stable. The host app's button UI and configuration storage are outside this repository (grep confirmed: no "连接 Doc" / "打开 Doc" / "Connect Doc" / "Open Doc" keys exist in messages/ or src/components/). This issue only addresses what this repository controls.
Current behavior
1. No in-app back / up / breadcrumb navigation
The workspace layout (src/app/[locale]/work/[id]/layout.tsx) renders:
TopBar (src/app/[locale]/work/[id]/top-bar.tsx) — its breadcrumbs slot contains only <Logo /> and <DocUpdateStatus />, not a navigational breadcrumb trail.
WorkSidebar — a document tree for jumping to any doc, but no "go back" or "go up to parent" control.
BottomBar — timestamps only.
HomeNav (src/components/home-nav.tsx) — used on /pub/[publishId]; Logo + locale/theme toggles, no back button.
A full-repo search for breadcrumb, back (navigation), goBack, ArrowLeft (navigation) confirms: no back-navigation control exists anywhere. The only back-to-top.tsx is a scroll-to-top button, unrelated to page navigation.
2. nav() uses pushState correctly, but the history stack is unreachable in chromeless containers
src/app/[locale]/work/[id]/@directory/util.ts lines 17–31:
export function nav(id: string) {
// ...
history.pushState({ docId: id }, '', url)
}
// ...
window.addEventListener('popstate', handlePopState)
Document switching pushes to the browser history stack and listens for popstate. This is correct for a normal browser — the browser back button would work. However, in an embedded container with no browser chrome, popstate never fires because the user has no way to trigger browser back. The history stack exists but is inaccessible.
3. LAST_DOC_ID silently tracks every navigation, making the entry URL drift
src/constants.ts line 9:
export const LAST_DOC_ID_KEY = 'LAST_DOC_ID'
src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx lines 31–33:
useEffect(() => {
localStorage.setItem(LAST_DOC_ID_KEY, id)
}, [id])
Every time the user navigates to any document — including deep sub-documents — the LAST_DOC_ID localStorage key is overwritten with the current doc ID.
src/components/start-button.tsx lines 12–16 reads it back:
const lastDocId = localStorage.getItem(LAST_DOC_ID_KEY)
if (lastDocId) { setLastDocId(lastDocId) }
// ...
<a role="start-link" href={lastDocId ? `/work/${lastDocId}` : '/work/'}>
And src/app/[locale]/work/page.tsx lines 21–29 unconditionally redirects /work/ to the most recently updated document.
Combined effect: If an external host saves the current page URL as the entry point, and the user navigates to a deep sub-document before closing, the next launch lands on that deep document — not the original entry. There is no way to return to the entry or any intermediate page.
4. No way to manage or clear LAST_DOC_ID from the UI
A full-repo search for LAST_DOC_ID finds only two references:
| Location |
Operation |
src/constants.ts:9 |
Key definition |
src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx:32 |
localStorage.setItem (write) |
src/components/start-button.tsx:13 |
localStorage.getItem (read) |
There is no UI to view, edit, clear, or reset this value. No settings page, no "clear recent data" button, no long-press menu. Once a wrong or deep doc ID is written, the only recovery is clearing browser/app storage manually.
Note: Symptom 4 from the original report (the "Connect Doc" / "Open Doc" button has no way to re-enter the unconfigured state) belongs to the external host application, not this repository. The LAST_DOC_ID issue here is the doc-side analogue: an opaque, unmanageable persisted value that silently affects where the user lands.
Steps to reproduce
Reproduce A — No back navigation
- Open any doc at
/work/<docA> in an embedded container (or a browser with hidden toolbar).
- Click a different document in the sidebar tree → URL changes to
/work/<docB>.
- Click another document → URL changes to
/work/<docC>.
- Observe: No back button, no breadcrumb, no "go to previous" control exists. In a chromeless container, the user is stuck on
docC with no way to return to docA or docB except clicking through the sidebar tree manually.
Reproduce B — Entry URL drifts to last browsed position
- Open
/work/<docA> (the intended entry point).
- Navigate through the sidebar to
docB → docC → deep docD.
- Close the container.
- Reopen using the saved entry URL or the "Get Started" button.
- Observe: The app lands on
docD (the last browsed position), not docA.
- Observe: No UI exists to return to
docA or to clear/reset the saved position.
Reproduce C — Wrong URL, no self-service recovery
- If a user somehow arrives at a deleted or invalid doc ID (e.g., after data migration),
LAST_DOC_ID may point to an inaccessible location.
- Observe: No UI to clear or correct the persisted doc ID. The user must clear browser/app data manually.
Expected behavior
-
Provide in-app back navigation that works without browser chrome. At minimum one of:
- A visible "Back" button in the
TopBar that calls window.history.back() (or uses the popstate infrastructure already in util.ts).
- A breadcrumb trail showing the document's ancestors (using
parentId from the doc tree), allowing the user to jump to any ancestor.
- Both are preferred.
-
Separate "entry point" from "last browsed position". If LAST_DOC_ID is intended for "resume where you left off", make it an explicit opt-in action (e.g., a "Continue reading X" prompt on the landing page) rather than silently overwriting the entry on every navigation. Alternatively, only write LAST_DOC_ID on initial page load, not on every in-session nav() call.
-
Provide a UI to manage LAST_DOC_ID. At minimum:
- A "Clear recent data" or "Reset entry point" action accessible from the sidebar or settings.
- The persisted value should be inspectable and clearable without clearing all browser storage.
-
Acceptance criteria:
- After navigating
docA → docB → docC, the user can return to docB and docA through in-app UI (not relying on browser chrome).
LAST_DOC_ID either (a) is not overwritten by in-session navigation, or (b) is surfaced as an explicit "resume" suggestion that the user can dismiss.
- A user who cannot reach their desired doc can clear or correct the persisted position through the UI.
Existing workaround
None found. There is no UI to go back, no breadcrumb, and no way to clear LAST_DOC_ID from within the application. The only recovery is clearing browser localStorage or app data manually.
Responsibility boundary & suspected code
| Symptom |
Layer |
Evidence |
| No back button / breadcrumb |
This repo |
top-bar.tsx breadcrumbs slot has only Logo; no goBack/ArrowLeft navigation component exists |
pushState without accessible back |
This repo (correct implementation, missing UI) |
@directory/util.ts:24 uses pushState; popstate handler at line 27–31 works but is unreachable in chromeless containers |
LAST_DOC_ID overwritten on every nav |
This repo |
content-for-my-doc.tsx:32 writes on every id change |
| Entry URL drifts to last position |
External host (saves current URL) + this repo (silently overwrites LAST_DOC_ID) |
The host app saves the URL at close time; this repo ensures that URL is always the last-browsed doc |
| "Connect Doc" / "Open Doc" button lock-in |
External host (not this repo) |
No matching i18n keys or components found in messages/ or src/ |
Key files
| File |
Relevance |
src/app/[locale]/work/[id]/@directory/util.ts — nav(), handlePopState |
Client-side doc switching with pushState (line 24) |
src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx:31-33 |
Writes LAST_DOC_ID on every doc switch |
src/components/start-button.tsx:12-16 |
Reads LAST_DOC_ID for "Get Started" link |
src/app/[locale]/work/page.tsx:21-29 |
Server-side redirect to most recent doc |
src/app/[locale]/work/[id]/top-bar.tsx:40-47 |
breadcrumbs slot — only Logo, no navigation |
src/app/[locale]/work/[id]/layout.tsx |
Workspace layout — no back button |
src/components/home-nav.tsx |
Public page nav — no back button |
src/constants.ts:9 |
LAST_DOC_ID_KEY definition |
Suggested fix direction
-
Add a back button to TopBar (top-bar.tsx) that calls window.history.back(). The popstate infrastructure in util.ts already handles the state restoration — the button just needs to trigger it. Consider adding it next to the Logo in the breadcrumbs slot.
-
Optionally add breadcrumb navigation using the document tree's parentId chain. This provides a visible path and works even when the history stack is empty (e.g., direct link to a deep doc).
-
Decouple LAST_DOC_ID from in-session navigation: either (a) only write it once on initial page load (check if it's already set for this session), or (b) introduce a separate ENTRY_DOC_ID that is set only on the first navigation and never overwritten, while LAST_DOC_ID tracks the most recent position for "resume reading" purposes.
-
Add a "Reset entry point" action — a button in the sidebar or workspace settings that clears LAST_DOC_ID from localStorage, so users can recover from a wrong or stale entry point without clearing all browser data.
Background
The doc web pages are often opened inside an embedded container (webview / panel) that has no browser chrome — no back button, no forward button, no address bar. Users navigate between documents entirely through the in-page sidebar tree. This issue covers two interlocking problems that leave users stranded once they navigate away from their entry point.
Entry & button lifecycle (external host context)
An external host application provides a "Connect Doc" / "Open Doc" button with two states:
The entry URL is written once by the user and should remain stable. The host app's button UI and configuration storage are outside this repository (grep confirmed: no "连接 Doc" / "打开 Doc" / "Connect Doc" / "Open Doc" keys exist in
messages/orsrc/components/). This issue only addresses what this repository controls.Current behavior
1. No in-app back / up / breadcrumb navigation
The workspace layout (
src/app/[locale]/work/[id]/layout.tsx) renders:TopBar(src/app/[locale]/work/[id]/top-bar.tsx) — itsbreadcrumbsslot contains only<Logo />and<DocUpdateStatus />, not a navigational breadcrumb trail.WorkSidebar— a document tree for jumping to any doc, but no "go back" or "go up to parent" control.BottomBar— timestamps only.HomeNav(src/components/home-nav.tsx) — used on/pub/[publishId]; Logo + locale/theme toggles, no back button.A full-repo search for
breadcrumb,back(navigation),goBack,ArrowLeft(navigation) confirms: no back-navigation control exists anywhere. The onlyback-to-top.tsxis a scroll-to-top button, unrelated to page navigation.2.
nav()usespushStatecorrectly, but the history stack is unreachable in chromeless containerssrc/app/[locale]/work/[id]/@directory/util.tslines 17–31:Document switching pushes to the browser history stack and listens for
popstate. This is correct for a normal browser — the browser back button would work. However, in an embedded container with no browser chrome,popstatenever fires because the user has no way to trigger browser back. The history stack exists but is inaccessible.3.
LAST_DOC_IDsilently tracks every navigation, making the entry URL driftsrc/constants.tsline 9:src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsxlines 31–33:Every time the user navigates to any document — including deep sub-documents — the
LAST_DOC_IDlocalStorage key is overwritten with the current doc ID.src/components/start-button.tsxlines 12–16 reads it back:And
src/app/[locale]/work/page.tsxlines 21–29 unconditionally redirects/work/to the most recently updated document.Combined effect: If an external host saves the current page URL as the entry point, and the user navigates to a deep sub-document before closing, the next launch lands on that deep document — not the original entry. There is no way to return to the entry or any intermediate page.
4. No way to manage or clear
LAST_DOC_IDfrom the UIA full-repo search for
LAST_DOC_IDfinds only two references:src/constants.ts:9src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx:32localStorage.setItem(write)src/components/start-button.tsx:13localStorage.getItem(read)There is no UI to view, edit, clear, or reset this value. No settings page, no "clear recent data" button, no long-press menu. Once a wrong or deep doc ID is written, the only recovery is clearing browser/app storage manually.
Steps to reproduce
Reproduce A — No back navigation
/work/<docA>in an embedded container (or a browser with hidden toolbar)./work/<docB>./work/<docC>.docCwith no way to return todocAordocBexcept clicking through the sidebar tree manually.Reproduce B — Entry URL drifts to last browsed position
/work/<docA>(the intended entry point).docB→docC→ deepdocD.docD(the last browsed position), notdocA.docAor to clear/reset the saved position.Reproduce C — Wrong URL, no self-service recovery
LAST_DOC_IDmay point to an inaccessible location.Expected behavior
Provide in-app back navigation that works without browser chrome. At minimum one of:
TopBarthat callswindow.history.back()(or uses thepopstateinfrastructure already inutil.ts).parentIdfrom the doc tree), allowing the user to jump to any ancestor.Separate "entry point" from "last browsed position". If
LAST_DOC_IDis intended for "resume where you left off", make it an explicit opt-in action (e.g., a "Continue reading X" prompt on the landing page) rather than silently overwriting the entry on every navigation. Alternatively, only writeLAST_DOC_IDon initial page load, not on every in-sessionnav()call.Provide a UI to manage
LAST_DOC_ID. At minimum:Acceptance criteria:
docA → docB → docC, the user can return todocBanddocAthrough in-app UI (not relying on browser chrome).LAST_DOC_IDeither (a) is not overwritten by in-session navigation, or (b) is surfaced as an explicit "resume" suggestion that the user can dismiss.Existing workaround
None found. There is no UI to go back, no breadcrumb, and no way to clear
LAST_DOC_IDfrom within the application. The only recovery is clearing browser localStorage or app data manually.Responsibility boundary & suspected code
top-bar.tsxbreadcrumbs slot has only Logo; nogoBack/ArrowLeftnavigation component existspushStatewithout accessible back@directory/util.ts:24usespushState;popstatehandler at line 27–31 works but is unreachable in chromeless containersLAST_DOC_IDoverwritten on every navcontent-for-my-doc.tsx:32writes on everyidchangeLAST_DOC_ID)messages/orsrc/Key files
src/app/[locale]/work/[id]/@directory/util.ts—nav(),handlePopStatepushState(line 24)src/app/[locale]/work/[id]/(content)/content-for-my-doc.tsx:31-33LAST_DOC_IDon every doc switchsrc/components/start-button.tsx:12-16LAST_DOC_IDfor "Get Started" linksrc/app/[locale]/work/page.tsx:21-29src/app/[locale]/work/[id]/top-bar.tsx:40-47breadcrumbsslot — only Logo, no navigationsrc/app/[locale]/work/[id]/layout.tsxsrc/components/home-nav.tsxsrc/constants.ts:9LAST_DOC_ID_KEYdefinitionSuggested fix direction
Add a back button to
TopBar(top-bar.tsx) that callswindow.history.back(). Thepopstateinfrastructure inutil.tsalready handles the state restoration — the button just needs to trigger it. Consider adding it next to the Logo in thebreadcrumbsslot.Optionally add breadcrumb navigation using the document tree's
parentIdchain. This provides a visible path and works even when the history stack is empty (e.g., direct link to a deep doc).Decouple
LAST_DOC_IDfrom in-session navigation: either (a) only write it once on initial page load (check if it's already set for this session), or (b) introduce a separateENTRY_DOC_IDthat is set only on the first navigation and never overwritten, whileLAST_DOC_IDtracks the most recent position for "resume reading" purposes.Add a "Reset entry point" action — a button in the sidebar or workspace settings that clears
LAST_DOC_IDfrom localStorage, so users can recover from a wrong or stale entry point without clearing all browser data.