diff --git a/web/src/components/AppSidebar/AppSidebar.tsx b/web/src/components/AppSidebar/AppSidebar.tsx
index 51e950f5..be91ed05 100644
--- a/web/src/components/AppSidebar/AppSidebar.tsx
+++ b/web/src/components/AppSidebar/AppSidebar.tsx
@@ -270,7 +270,6 @@ const MemoDetailSidebarContent = () => {
memo={memoDetail.memo}
parentMemo={memoDetail.parentMemo}
parentPage={memoDetail.from}
- parentScope={memoDetail.fromScope}
hasExplicitOrigin={memoDetail.hasExplicitOrigin}
commentCount={memoDetail.commentCount}
forceReadonly={memoDetail.readonly}
diff --git a/web/src/components/AttachmentLibrary/AttachmentLibraryPrimitives.tsx b/web/src/components/AttachmentLibrary/AttachmentLibraryPrimitives.tsx
index 854e1b84..b0015bbc 100644
--- a/web/src/components/AttachmentLibrary/AttachmentLibraryPrimitives.tsx
+++ b/web/src/components/AttachmentLibrary/AttachmentLibraryPrimitives.tsx
@@ -63,7 +63,7 @@ export const AttachmentSourceChip = ({
return (
{t("attachment-library.labels.memo")}
diff --git a/web/src/components/MemoCommentSection.tsx b/web/src/components/MemoCommentSection.tsx
index 6bfe6842..5d028763 100644
--- a/web/src/components/MemoCommentSection.tsx
+++ b/web/src/components/MemoCommentSection.tsx
@@ -3,7 +3,6 @@ import { type ComponentType, forwardRef, useCallback, useImperativeHandle, useRe
import { loadMemoEditor } from "@/components/MemoEditor/loader";
import type { MemoEditorProps } from "@/components/MemoEditor/types";
import MemoView from "@/components/MemoView";
-import type { MemoOriginScope } from "@/components/MemoView/navigation";
import { Button } from "@/components/ui/button";
import useCurrentUser from "@/hooks/useCurrentUser";
import { MEMO_COMMENTS_ANCHOR_ID } from "@/lib/memo-comments";
@@ -17,7 +16,6 @@ interface Props {
comments: Memo[];
commentCount?: number;
parentPage?: string;
- parentScope?: MemoOriginScope;
hasMoreComments?: boolean;
isFetchingMoreComments?: boolean;
onLoadMoreComments?: () => void;
@@ -29,19 +27,7 @@ export interface MemoCommentSectionHandle {
}
const MemoCommentSection = forwardRef(
- (
- {
- memo,
- comments,
- commentCount = comments.length,
- parentPage,
- parentScope,
- hasMoreComments,
- isFetchingMoreComments,
- onLoadMoreComments,
- },
- ref,
- ) => {
+ ({ memo, comments, commentCount = comments.length, parentPage, hasMoreComments, isFetchingMoreComments, onLoadMoreComments }, ref) => {
const t = useTranslate();
const currentUser = useCurrentUser();
const [showEditor, setShowEditor] = useState(false);
@@ -154,7 +140,7 @@ const MemoCommentSection = forwardRef(
)}
{comments.map((comment) => (
))}
{hasMoreComments && (
diff --git a/web/src/components/MemoContent/MemoMarkdownRenderer.tsx b/web/src/components/MemoContent/MemoMarkdownRenderer.tsx
index ec456e54..44486c74 100644
--- a/web/src/components/MemoContent/MemoMarkdownRenderer.tsx
+++ b/web/src/components/MemoContent/MemoMarkdownRenderer.tsx
@@ -7,7 +7,6 @@ import { isMentionElement, isTagElement, isTaskListItemElement } from "@/types/m
import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb";
import { lazyWithReload } from "@/utils/lazy";
import { resolveManagedAttachmentImageSource } from "@/utils/managed-attachment";
-import type { MemoOriginScope } from "../MemoView/navigation";
import { CodeBlock } from "./CodeBlock";
import { MarkdownRenderContext, rootMarkdownRenderContext } from "./MarkdownRenderContext";
import { Mention } from "./Mention";
@@ -26,7 +25,6 @@ export interface MemoMarkdownRendererProps {
memoName?: string;
/** Collection page that opened the memo detail. */
parentPage?: string;
- parentScope?: MemoOriginScope;
/** Whether the memo is rendered as a collapsed feed card. */
compact?: boolean;
}
@@ -67,7 +65,6 @@ export const MemoMarkdownRendererCore = ({
resolvedMentionUsernames,
memoName,
parentPage,
- parentScope,
compact,
mathRemarkPlugins = [],
mathRehypePlugins = [],
@@ -134,7 +131,7 @@ export const MemoMarkdownRendererCore = ({
// than opening a new tab; everything else is treated as an external link.
if (typeof href === "string" && href.startsWith("#")) {
return (
-
+
{children}
);
@@ -195,7 +192,6 @@ export const MemoMarkdownRenderer = memo(
previous.attachments === next.attachments &&
previous.memoName === next.memoName &&
previous.parentPage === next.parentPage &&
- previous.parentScope === next.parentScope &&
previous.compact === next.compact &&
haveEqualResolvedMentions(previous.resolvedMentionUsernames, next.resolvedMentionUsernames),
);
diff --git a/web/src/components/MemoContent/index.tsx b/web/src/components/MemoContent/index.tsx
index a28192a3..740c5543 100644
--- a/web/src/components/MemoContent/index.tsx
+++ b/web/src/components/MemoContent/index.tsx
@@ -40,7 +40,6 @@ const MemoContent = (props: MemoContentProps) => {
resolvedMentionUsernames={resolvedMentionUsernames}
memoName={props.memoName}
parentPage={props.parentPage}
- parentScope={props.parentScope}
compact={Boolean(props.compact)}
/>
diff --git a/web/src/components/MemoContent/markdown/AnchorLink.tsx b/web/src/components/MemoContent/markdown/AnchorLink.tsx
index 75be8db4..b4cd27d2 100644
--- a/web/src/components/MemoContent/markdown/AnchorLink.tsx
+++ b/web/src/components/MemoContent/markdown/AnchorLink.tsx
@@ -2,7 +2,7 @@ import { Link } from "react-router-dom";
import { markdownStyles } from "@/lib/markdownStyles";
import { cn } from "@/lib/utils";
import { findAnchorTarget } from "@/utils/markdown-manipulation";
-import { createMemoNavigationState, type MemoOriginScope } from "../../MemoView/navigation";
+import { createMemoNavigationState } from "../../MemoView/navigation";
import type { ReactMarkdownProps } from "./types";
interface AnchorLinkProps extends React.AnchorHTMLAttributes, ReactMarkdownProps {
@@ -11,7 +11,6 @@ interface AnchorLinkProps extends React.AnchorHTMLAttributes,
memoName?: string;
/** Collection page that rendered the enclosing memo. */
parentPage?: string;
- parentScope?: MemoOriginScope;
/** Whether the memo is rendered as a collapsed feed card. */
compact?: boolean;
children: React.ReactNode;
@@ -26,17 +25,7 @@ interface AnchorLinkProps extends React.AnchorHTMLAttributes,
* below the fold, so we fall back to navigating to the memo detail page (with the hash), where
* MemoDetail scrolls the target into view.
*/
-export const AnchorLink = ({
- href,
- memoName,
- parentPage,
- parentScope,
- compact,
- children,
- className,
- node: _node,
- ...props
-}: AnchorLinkProps) => {
+export const AnchorLink = ({ href, memoName, parentPage, compact, children, className, node: _node, ...props }: AnchorLinkProps) => {
const handleClick = (event: React.MouseEvent) => {
if (compact) return; // Let the link navigate to the detail page.
const id = decodeURIComponent(href.slice(1));
@@ -57,7 +46,7 @@ export const AnchorLink = ({
return (
{
const { ref, title } = useOverflowTitle(snippet);
@@ -74,7 +71,7 @@ const BacklinkRow = ({
aria-label={`${referencedByLabel}: ${snippet}`}
className={cn(SIDEBAR_ROW_CLASSES, "text-muted-foreground hover:bg-sidebar-accent/65 hover:text-foreground")}
to={`/${relatedMemo.name}`}
- state={parentPage && parentScope ? createMemoNavigationState(parentPage, parentScope) : undefined}
+ state={parentPage ? createMemoNavigationState(parentPage) : undefined}
title={title}
viewTransition
>
@@ -90,7 +87,6 @@ const MemoDetailSidebar = ({
memo,
parentMemo,
parentPage,
- parentScope,
hasExplicitOrigin = false,
commentCount,
className,
@@ -219,7 +215,7 @@ const MemoDetailSidebar = ({
aria-label={`${t("memo.parent-memo")}: ${parentSnippet}`}
className={cn(SIDEBAR_ROW_CLASSES, "text-muted-foreground hover:bg-sidebar-accent/65 hover:text-foreground")}
to={`/${parentMemo.name}`}
- state={parentPage && parentScope ? createMemoNavigationState(parentPage, parentScope) : undefined}
+ state={parentPage ? createMemoNavigationState(parentPage) : undefined}
title={parentSnippet}
viewTransition
>
@@ -235,7 +231,6 @@ const MemoDetailSidebar = ({
relation={relation}
snippet={backlinkSnippet(relation)}
parentPage={parentPage}
- parentScope={parentScope}
referencedByLabel={t("common.referenced-by")}
/>
);
diff --git a/web/src/components/MemoMetadata/Relation/RelationCard.tsx b/web/src/components/MemoMetadata/Relation/RelationCard.tsx
index 83cb6d27..4cfeba9f 100644
--- a/web/src/components/MemoMetadata/Relation/RelationCard.tsx
+++ b/web/src/components/MemoMetadata/Relation/RelationCard.tsx
@@ -1,21 +1,20 @@
import MemoSnippetLink from "@/components/MemoView/components/MemoSnippetLink";
-import { createMemoNavigationState, type MemoOriginScope } from "@/components/MemoView/navigation";
+import { createMemoNavigationState } from "@/components/MemoView/navigation";
import type { MemoRelation_Memo } from "@/types/proto/api/v1/memo_service_pb";
interface RelationCardProps {
memo: MemoRelation_Memo;
parentPage?: string;
- parentScope?: MemoOriginScope;
className?: string;
}
-const RelationCard = ({ memo, parentPage, parentScope, className }: RelationCardProps) => {
+const RelationCard = ({ memo, parentPage, className }: RelationCardProps) => {
return (
);
diff --git a/web/src/components/MemoMetadata/Relation/RelationListView.tsx b/web/src/components/MemoMetadata/Relation/RelationListView.tsx
index 1e1c7e3e..3030080c 100644
--- a/web/src/components/MemoMetadata/Relation/RelationListView.tsx
+++ b/web/src/components/MemoMetadata/Relation/RelationListView.tsx
@@ -1,7 +1,6 @@
import { LinkIcon, MilestoneIcon } from "lucide-react";
import { useMemo, useState } from "react";
import MetadataSection from "@/components/MemoMetadata/MetadataSection";
-import type { MemoOriginScope } from "@/components/MemoView/navigation";
import { useNearViewport } from "@/hooks/useNearViewport";
import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb";
import { useTranslate } from "@/utils/i18n";
@@ -13,11 +12,10 @@ interface RelationListViewProps {
relations: MemoRelation[];
currentMemoName?: string;
parentPage?: string;
- parentScope?: MemoOriginScope;
className?: string;
}
-function RelationListView({ relations, currentMemoName, parentPage, parentScope, className }: RelationListViewProps) {
+function RelationListView({ relations, currentMemoName, parentPage, className }: RelationListViewProps) {
const t = useTranslate();
const [activeTab, setActiveTab] = useState<"referencing" | "referenced">("referencing");
const { ref: viewportRef, isNearViewport } = useNearViewport();
@@ -81,12 +79,7 @@ function RelationListView({ relations, currentMemoName, parentPage, parentScope,
return null;
}
return (
-
+
);
})}
diff --git a/web/src/components/MemoPreview/MemoPreview.tsx b/web/src/components/MemoPreview/MemoPreview.tsx
index 279c420b..2663071e 100644
--- a/web/src/components/MemoPreview/MemoPreview.tsx
+++ b/web/src/components/MemoPreview/MemoPreview.tsx
@@ -29,7 +29,6 @@ const STUB_CONTEXT: MemoViewContextValue = {
creator: undefined,
currentUser: undefined,
parentPage: "/",
- parentScope: "all",
cardWidth: 0,
isArchived: false,
readonly: true,
diff --git a/web/src/components/MemoView/MemoView.tsx b/web/src/components/MemoView/MemoView.tsx
index af1ae51e..e9904f7a 100644
--- a/web/src/components/MemoView/MemoView.tsx
+++ b/web/src/components/MemoView/MemoView.tsx
@@ -25,7 +25,7 @@ import { MemoBody, MemoCommentListView, MemoHeader } from "./components";
import { MEMO_CARD_BASE_CLASSES } from "./constants";
import { useImagePreview } from "./hooks";
import { computeCommentAmount, MemoViewContext } from "./MemoViewContext";
-import { isMemoDetailPath, resolveMemoOrigin } from "./navigation";
+import { isMemoDetailPath, resolveMemoParentPage } from "./navigation";
import type { MemoViewHandle, MemoViewProps } from "./types";
const MemoShareImageDialog = lazyWithReload(() => import("../MemoActionMenu/MemoShareImageDialog"));
@@ -36,7 +36,6 @@ const MemoView = forwardRef((props, ref) => {
memo: memoData,
className,
parentPage: parentPageProp,
- parentScope: parentScopeProp,
compact,
timeDisplay,
showCreator,
@@ -55,9 +54,8 @@ const MemoView = forwardRef((props, ref) => {
const isArchived = memoData.state === State.ARCHIVED;
const readonly = memoData.creator !== currentUser?.name && !isSuperUser(currentUser);
const location = useLocation();
- const { parentPage, parentScope } = resolveMemoOrigin({
+ const parentPage = resolveMemoParentPage({
explicitParentPage: parentPageProp,
- explicitParentScope: parentScopeProp,
pathname: location.pathname,
search: location.search,
memoName: memoData.name,
@@ -135,7 +133,6 @@ const MemoView = forwardRef((props, ref) => {
creator,
currentUser,
parentPage,
- parentScope,
cardWidth,
isArchived,
readonly,
@@ -150,7 +147,6 @@ const MemoView = forwardRef((props, ref) => {
creator,
currentUser,
parentPage,
- parentScope,
cardWidth,
isArchived,
readonly,
diff --git a/web/src/components/MemoView/MemoViewContext.tsx b/web/src/components/MemoView/MemoViewContext.tsx
index f682b1d6..432692f7 100644
--- a/web/src/components/MemoView/MemoViewContext.tsx
+++ b/web/src/components/MemoView/MemoViewContext.tsx
@@ -7,14 +7,13 @@ import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb";
import type { User } from "@/types/proto/api/v1/user_service_pb";
import type { PreviewMediaItem } from "@/utils/media-item";
import { RELATIVE_TIME_THRESHOLD_MS } from "./constants";
-import { isMemoDetailPath, type MemoOriginScope } from "./navigation";
+import { isMemoDetailPath } from "./navigation";
export interface MemoViewContextValue {
memo: Memo;
creator: User | undefined;
currentUser: User | undefined;
parentPage: string;
- parentScope: MemoOriginScope;
cardWidth: number;
isArchived: boolean;
readonly: boolean;
diff --git a/web/src/components/MemoView/components/MemoBody.tsx b/web/src/components/MemoView/components/MemoBody.tsx
index c73ee458..ccd05fe2 100644
--- a/web/src/components/MemoView/components/MemoBody.tsx
+++ b/web/src/components/MemoView/components/MemoBody.tsx
@@ -32,8 +32,7 @@ const BlurOverlay: React.FC<{ onClick?: () => void }> = ({ onClick }) => {
};
const MemoBody: React.FC = ({ compact }) => {
- const { memo, parentPage, parentScope, showBlurredContent, blurred, readonly, openEditor, openPreview, toggleBlurVisibility } =
- useMemoViewContext();
+ const { memo, parentPage, showBlurredContent, blurred, readonly, openEditor, openPreview, toggleBlurVisibility } = useMemoViewContext();
const { handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({ readonly, openEditor, openPreview });
@@ -58,7 +57,6 @@ const MemoBody: React.FC = ({ compact }) => {
= ({ compact }) => {
compact={Boolean(compact)}
/>
-
+
{memo.location && }
diff --git a/web/src/components/MemoView/components/MemoCommentListView.tsx b/web/src/components/MemoView/components/MemoCommentListView.tsx
index 5b06b0b6..0e6f900b 100644
--- a/web/src/components/MemoView/components/MemoCommentListView.tsx
+++ b/web/src/components/MemoView/components/MemoCommentListView.tsx
@@ -10,7 +10,7 @@ import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext";
import { createMemoNavigationState } from "../navigation";
const MemoCommentListView: React.FC = () => {
- const { memo, parentPage, parentScope } = useMemoViewContext();
+ const { memo, parentPage } = useMemoViewContext();
const { isInMemoDetailPage, commentAmount } = useMemoViewDerived();
const { ref: viewportRef, isNearViewport } = useNearViewport();
@@ -32,7 +32,7 @@ const MemoCommentListView: React.FC = () => {
Comments{commentAmount > 1 ? ` (${commentAmount})` : ""}
View all
@@ -46,7 +46,7 @@ const MemoCommentListView: React.FC = () => {
diff --git a/web/src/components/MemoView/components/MemoHeader.tsx b/web/src/components/MemoView/components/MemoHeader.tsx
index ca7abe08..7cbf3f29 100644
--- a/web/src/components/MemoView/components/MemoHeader.tsx
+++ b/web/src/components/MemoView/components/MemoHeader.tsx
@@ -29,15 +29,15 @@ const MemoHeader: React.FC = ({ timeDisplay = "relative", showC
const t = useTranslate();
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
- const { memo, creator, currentUser, parentPage, parentScope, isArchived, readonly, openEditor } = useMemoViewContext();
+ const { memo, creator, currentUser, parentPage, isArchived, readonly, openEditor } = useMemoViewContext();
const { createTime, updateTime, displayTime: memoDisplayTime, isDisplayingUpdatedTime, relativeTimeFormat } = useMemoViewDerived();
const { newMemoName } = useNewMemo();
const visibilityOption = getVisibilityOption(memo.visibility);
const navigateTo = useNavigateTo();
const handleGotoMemoDetailPage = useCallback(() => {
- navigateTo(`/${memo.name}`, { state: createMemoNavigationState(parentPage, parentScope) });
- }, [memo.name, parentPage, parentScope, navigateTo]);
+ navigateTo(`/${memo.name}`, { state: createMemoNavigationState(parentPage) });
+ }, [memo.name, parentPage, navigateTo]);
const { unpinMemo } = useMemoActions(memo);
diff --git a/web/src/components/MemoView/navigation.ts b/web/src/components/MemoView/navigation.ts
index be0a9931..9cf8ce45 100644
--- a/web/src/components/MemoView/navigation.ts
+++ b/web/src/components/MemoView/navigation.ts
@@ -1,11 +1,8 @@
-import { getProfileUsername, isMemoScopeRoute } from "@/lib/memo-views";
-import { ROUTES, resolveCollectionRoute } from "@/router/routes";
-
-export type MemoOriginScope = "all" | "preserve";
+import { getProfileUsername } from "@/lib/memo-views";
+import { ROUTES } from "@/router/routes";
export interface MemoNavigationState {
from: string;
- fromScope: MemoOriginScope;
}
interface ResolveMemoDetailOriginOptions {
@@ -14,7 +11,6 @@ interface ResolveMemoDetailOriginOptions {
interface ResolveMemoParentPageOptions {
explicitParentPage?: string;
- explicitParentScope?: MemoOriginScope;
pathname: string;
search: string;
memoName: string;
@@ -40,32 +36,13 @@ export const isMemoResourcePath = (pathname: string): boolean => {
return memoID.length > 0 && !memoID.includes("/");
};
-/** Whether this origin represents a collection whose complete URL should be preserved. */
-export const isMemoCollectionOrigin = (page: string): boolean => {
- const pathname = page.split(/[?#]/, 1)[0] || ROUTES.HOME;
- return isMemoScopeRoute(pathname) || resolveCollectionRoute(pathname).isCollection;
-};
+export const createMemoNavigationState = (from: string): MemoNavigationState => ({ from });
-export const createMemoNavigationState = (from: string, fromScope: MemoOriginScope): MemoNavigationState => ({ from, fromScope });
-
-/** Reads router state while remaining compatible with older `{ from }` links. */
-export const resolveMemoDetailOrigin = (
- state: unknown,
- options: ResolveMemoDetailOriginOptions = {},
-): { parentPage: string; parentScope: MemoOriginScope } => {
- const value = state && typeof state === "object" ? (state as { from?: unknown; fromScope?: unknown }) : undefined;
+/** Reads the origin page out of router state; without one, a detail returns to its collection. */
+export const resolveMemoDetailOrigin = (state: unknown, options: ResolveMemoDetailOriginOptions = {}): string => {
+ const value = state && typeof state === "object" ? (state as { from?: unknown }) : undefined;
const explicitParentPage = typeof value?.from === "string" ? value.from : undefined;
- const hasExplicitParent = explicitParentPage !== undefined;
- const parentPage = explicitParentPage || (options.memoArchived ? ROUTES.ARCHIVED : ROUTES.HOME);
- const parentScope =
- value?.fromScope === "all" || value?.fromScope === "preserve"
- ? value.fromScope
- : hasExplicitParent && isMemoCollectionOrigin(parentPage)
- ? "preserve"
- : options.memoArchived
- ? "preserve"
- : "all";
- return { parentPage, parentScope };
+ return explicitParentPage || (options.memoArchived ? ROUTES.ARCHIVED : ROUTES.HOME);
};
/** Whether the current route is the canonical detail page for this memo. */
@@ -79,33 +56,12 @@ export const isMemoDetailPath = (pathname: string, memoName: string): boolean =>
* Captures the list page that opened a memo so canonical detail routes can
* return to the real collection lens instead of assuming Home.
*/
-export const resolveMemoOrigin = ({
- explicitParentPage,
- explicitParentScope,
- pathname,
- search,
- memoName,
-}: ResolveMemoParentPageOptions): { parentPage: string; parentScope: MemoOriginScope } => {
- if (explicitParentPage !== undefined) {
- const parentPage = explicitParentPage || ROUTES.HOME;
- return {
- parentPage,
- parentScope: explicitParentScope ?? (isMemoCollectionOrigin(parentPage) ? "preserve" : "all"),
- };
- }
-
- if (isMemoDetailPath(pathname, memoName)) {
- return { parentPage: ROUTES.HOME, parentScope: "all" };
- }
-
- return {
- parentPage: `${pathname}${search}`,
- parentScope: isMemoCollectionOrigin(pathname) ? "preserve" : "all",
- };
+export const resolveMemoParentPage = ({ explicitParentPage, pathname, search, memoName }: ResolveMemoParentPageOptions): string => {
+ if (explicitParentPage !== undefined) return explicitParentPage || ROUTES.HOME;
+ if (isMemoDetailPath(pathname, memoName)) return ROUTES.HOME;
+ return `${pathname}${search}`;
};
-export const resolveMemoParentPage = (options: ResolveMemoParentPageOptions): string => resolveMemoOrigin(options).parentPage;
-
/** Replaces only the memo filter while preserving the rest of the origin query. */
export const withMemoFilter = (page: string, filter: string): string => {
const [pathAndSearch] = page.split("#", 1);
diff --git a/web/src/components/MemoView/types.ts b/web/src/components/MemoView/types.ts
index 4857ebbe..487b992f 100644
--- a/web/src/components/MemoView/types.ts
+++ b/web/src/components/MemoView/types.ts
@@ -1,5 +1,4 @@
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
-import type { MemoOriginScope } from "./navigation";
/** How the header names the memo's time: relative to now, or just the clock time for lists that already name the day. */
export type MemoTimeDisplay = "relative" | "time";
@@ -14,7 +13,6 @@ export interface MemoViewProps {
showSpace?: boolean;
className?: string;
parentPage?: string;
- parentScope?: MemoOriginScope;
shareImageDialogOpen?: boolean;
onShareImageDialogOpenChange?: (open: boolean) => void;
}
diff --git a/web/src/components/UserMemoMap/UserMemoMap.tsx b/web/src/components/UserMemoMap/UserMemoMap.tsx
index 857913c9..a14467d6 100644
--- a/web/src/components/UserMemoMap/UserMemoMap.tsx
+++ b/web/src/components/UserMemoMap/UserMemoMap.tsx
@@ -157,7 +157,7 @@ const UserMemoMap = ({ creator, className }: Props) => {
Open
diff --git a/web/src/contexts/AppSidebarContext.tsx b/web/src/contexts/AppSidebarContext.tsx
index 9cb25f3c..64213502 100644
--- a/web/src/contexts/AppSidebarContext.tsx
+++ b/web/src/contexts/AppSidebarContext.tsx
@@ -1,6 +1,5 @@
import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { useLocation } from "react-router-dom";
-import type { MemoOriginScope } from "@/components/MemoView/navigation";
import type { PrimaryMemoScope } from "@/lib/memo-views";
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
@@ -11,7 +10,6 @@ export interface MemoDetailSidebarDescriptor {
memo: Memo;
parentMemo?: Memo;
from?: string;
- fromScope?: MemoOriginScope;
hasExplicitOrigin?: boolean;
commentCount?: number;
readonly?: boolean;
diff --git a/web/src/lib/memo-views.ts b/web/src/lib/memo-views.ts
index d162fcc1..52c31ea6 100644
--- a/web/src/lib/memo-views.ts
+++ b/web/src/lib/memo-views.ts
@@ -41,8 +41,6 @@ export const isCalendarRoute = (pathname: string): boolean => {
/**
* Routes that render a memo collection the sidebar can narrow: the scope routes, a user
* profile and the calendar. Views, calendar days and tags apply in place on all of them.
- * This is a different question from `isMemoCollectionOrigin` (MemoView/navigation.ts),
- * which asks whether a memo origin is a collection whose full URL is worth returning to.
*/
export const isMemoCollectionRoute = (pathname: string): boolean =>
isMemoScopeRoute(pathname) || getProfileUsername(pathname) !== undefined || isCalendarRoute(pathname);
diff --git a/web/src/pages/MemoDetail.tsx b/web/src/pages/MemoDetail.tsx
index 630e4db0..9a569f4a 100644
--- a/web/src/pages/MemoDetail.tsx
+++ b/web/src/pages/MemoDetail.tsx
@@ -6,7 +6,7 @@ import MemoCommentSection, { type MemoCommentSectionHandle } from "@/components/
import { MentionResolutionProvider } from "@/components/MemoContent/MentionResolutionContext";
import MemoView, { type MemoViewHandle } from "@/components/MemoView";
import { computeCommentAmount } from "@/components/MemoView/MemoViewContext";
-import { createMemoNavigationState, type MemoOriginScope, resolveMemoDetailOrigin } from "@/components/MemoView/navigation";
+import { createMemoNavigationState, resolveMemoDetailOrigin } from "@/components/MemoView/navigation";
import { useAppSidebar } from "@/contexts/AppSidebarContext";
import { useAuth } from "@/contexts/AuthContext";
import { useInstance } from "@/contexts/InstanceContext";
@@ -25,7 +25,6 @@ const MemoSidebarRegistration = ({
memo,
parentMemo,
from,
- fromScope,
hasExplicitOrigin,
commentCount,
readonly,
@@ -37,7 +36,6 @@ const MemoSidebarRegistration = ({
memo: Memo;
parentMemo?: Memo;
from: string;
- fromScope: MemoOriginScope;
hasExplicitOrigin: boolean;
commentCount?: number;
readonly: boolean;
@@ -53,7 +51,6 @@ const MemoSidebarRegistration = ({
memo,
parentMemo,
from,
- fromScope,
hasExplicitOrigin,
commentCount,
readonly,
@@ -65,7 +62,6 @@ const MemoSidebarRegistration = ({
}, [
commentCount,
from,
- fromScope,
hasExplicitOrigin,
memo,
onCommentCreate,
@@ -113,8 +109,7 @@ const MemoDetail = () => {
const hasExplicitOrigin =
!!locationState && typeof locationState === "object" && typeof (locationState as { from?: unknown }).from === "string";
const resolvedOrigin = resolveMemoDetailOrigin(locationState, { memoArchived: memo?.state === State.ARCHIVED });
- const parentPage = !hasExplicitOrigin && !currentUser && memo?.state !== State.ARCHIVED ? ROUTES.EXPLORE : resolvedOrigin.parentPage;
- const parentScope = resolvedOrigin.parentScope;
+ const parentPage = !hasExplicitOrigin && !currentUser && memo?.state !== State.ARCHIVED ? ROUTES.EXPLORE : resolvedOrigin;
const memoName = memo?.name ?? memoNameFromParams;
const displayMemo = useReactMemo(() => {
if (!memo) return undefined;
@@ -197,7 +192,6 @@ const MemoDetail = () => {
memo={displayMemo}
parentMemo={parentMemo}
from={parentPage}
- fromScope={parentScope}
hasExplicitOrigin={hasExplicitOrigin}
commentCount={isShareMode ? undefined : commentCount}
readonly={isShareMode}
@@ -213,7 +207,7 @@ const MemoDetail = () => {
@@ -227,7 +221,6 @@ const MemoDetail = () => {
memo={displayMemo}
compact={false}
parentPage={parentPage}
- parentScope={parentScope}
shareImageDialogOpen={shareImageDialogOpen}
showCreator
showVisibility
@@ -242,7 +235,6 @@ const MemoDetail = () => {
comments={comments}
commentCount={commentCount}
parentPage={parentPage}
- parentScope={parentScope}
hasMoreComments={hasNextComments}
isFetchingMoreComments={isFetchingNextComments}
onLoadMoreComments={fetchNextComments}
diff --git a/web/tests/attachment-library-source-link.test.tsx b/web/tests/attachment-library-source-link.test.tsx
index 8af19305..884be2bf 100644
--- a/web/tests/attachment-library-source-link.test.tsx
+++ b/web/tests/attachment-library-source-link.test.tsx
@@ -67,9 +67,7 @@ describe("", () => {
fireEvent.click(memoLink);
expect(onPreview).not.toHaveBeenCalled();
- expect(screen.getByTestId("location-state")).toHaveTextContent(
- '{"from":"/spaces/product/attachments?filter=tagSearch%3Awork","fromScope":"preserve"}',
- );
+ expect(screen.getByTestId("location-state")).toHaveTextContent('{"from":"/spaces/product/attachments?filter=tagSearch%3Awork"}');
});
it("keeps video duration inside the existing play badge", () => {
diff --git a/web/tests/memo-content-footnotes.test.tsx b/web/tests/memo-content-footnotes.test.tsx
index a4db6103..eed1ede1 100644
--- a/web/tests/memo-content-footnotes.test.tsx
+++ b/web/tests/memo-content-footnotes.test.tsx
@@ -24,7 +24,6 @@ const renderFootnote = (compact = false, parentPage?: string) =>
resolvedMentionUsernames={new Set()}
memoName="memos/abc123"
parentPage={parentPage}
- parentScope={parentPage ? "preserve" : undefined}
compact={compact}
/>
diff --git a/web/tests/memo-detail-sidebar.test.tsx b/web/tests/memo-detail-sidebar.test.tsx
index 73bf6bf2..f9c2bdf0 100644
--- a/web/tests/memo-detail-sidebar.test.tsx
+++ b/web/tests/memo-detail-sidebar.test.tsx
@@ -83,7 +83,6 @@ describe("MemoDetailSidebar", () => {
memo={memo}
parentMemo={parentMemo}
parentPage="/explore?filter=tagSearch%3Awork"
- parentScope="preserve"
hasExplicitOrigin
commentCount={3}
onEdit={onEdit}
@@ -144,9 +143,7 @@ describe("MemoDetailSidebar", () => {
relations: [createIncomingReference("memos/public")],
});
- renderSidebar(
- ,
- );
+ renderSidebar();
expect(screen.queryByRole("button", { name: "common.edit" })).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: "memo.comment.write-a-comment" })).toBeInTheDocument();
@@ -163,9 +160,7 @@ describe("MemoDetailSidebar", () => {
content: "Body",
});
- renderSidebar(
- ,
- );
+ renderSidebar();
expect(screen.getByRole("link", { name: "memo.go-to:common.home" })).toHaveAttribute("href", "/");
expect(screen.queryByText("memo.on-this-memo")).not.toBeInTheDocument();
@@ -187,7 +182,7 @@ describe("MemoDetailSidebar", () => {
content: "Body",
});
- renderSidebar();
+ renderSidebar();
fireEvent.click(screen.getByRole("button", { name: "common.share" }));
expect(await screen.findByRole("menuitem", { name: "memo.copy-link" })).toBeInTheDocument();
@@ -202,16 +197,7 @@ describe("MemoDetailSidebar", () => {
content: "Archived body",
});
- renderSidebar(
- ,
- );
+ renderSidebar();
expect(screen.getByRole("link", { name: "memo.go-to:common.archived" })).toBeInTheDocument();
expect(screen.getByRole("link", { name: /memo.comment.self/ })).toHaveTextContent("2");
@@ -237,7 +223,6 @@ describe("MemoDetailSidebar", () => {
memo={memo}
parentMemo={parentMemo}
parentPage="/explore"
- parentScope="all"
hasExplicitOrigin
commentCount={4}
forceReadonly
diff --git a/web/tests/memo-filter-context.test.tsx b/web/tests/memo-filter-context.test.tsx
index a0d96283..9490ee28 100644
--- a/web/tests/memo-filter-context.test.tsx
+++ b/web/tests/memo-filter-context.test.tsx
@@ -81,7 +81,7 @@ describe("MemoFilterProvider", () => {
);
render();
fireEvent.click(screen.getByRole("button", { name: "Select Tasks" }));
- const state = { from: origin, fromScope: "preserve" };
+ const state = { from: origin };
await act(() => router.navigate("/memos/1", { state }));
await waitFor(() => expect(screen.getByTestId("filters")).toHaveTextContent("[]"));
expect(router.state.location.search).toBe("");
diff --git a/web/tests/memo-header-navigation.test.tsx b/web/tests/memo-header-navigation.test.tsx
index 04fd1f83..e0b83f00 100644
--- a/web/tests/memo-header-navigation.test.tsx
+++ b/web/tests/memo-header-navigation.test.tsx
@@ -62,7 +62,6 @@ vi.mock("@/components/MemoView/MemoViewContext", () => ({
creator: state.creator,
currentUser: state.currentUser,
parentPage: "/explore?filter=tagSearch%3Awork",
- parentScope: "preserve",
isArchived: false,
readonly: false,
openEditor: vi.fn(),
@@ -142,9 +141,7 @@ describe("MemoHeader navigation", () => {
expect(timestamp).toHaveFocus();
fireEvent.click(timestamp);
- expect(screen.getByTestId("location")).toHaveTextContent(
- '/memos/123|{"from":"/explore?filter=tagSearch%3Awork","fromScope":"preserve"}',
- );
+ expect(screen.getByTestId("location")).toHaveTextContent('/memos/123|{"from":"/explore?filter=tagSearch%3Awork"}');
});
it.each([false, true])("keeps the Space pill beside the timestamp when showCreator=%s", (showCreator) => {
diff --git a/web/tests/memo-view-navigation.test.ts b/web/tests/memo-view-navigation.test.ts
index c574ffc2..a0f9e162 100644
--- a/web/tests/memo-view-navigation.test.ts
+++ b/web/tests/memo-view-navigation.test.ts
@@ -1,11 +1,9 @@
import { describe, expect, it } from "vitest";
import {
createMemoNavigationState,
- isMemoCollectionOrigin,
isMemoDetailPath,
isMemoResourcePath,
resolveMemoDetailOrigin,
- resolveMemoOrigin,
resolveMemoParentPage,
withMemoFilter,
} from "@/components/MemoView/navigation";
@@ -32,53 +30,25 @@ describe("memo view navigation", () => {
).toBe("/explore?filter=contentSearch%3Aroadmap");
});
- it("marks collection cards as preserving the remembered scope", () => {
- expect(resolveMemoOrigin({ pathname: "/explore", search: "?filter=tagSearch%3Awork", memoName: "memos/123" })).toEqual({
- parentPage: "/explore?filter=tagSearch%3Awork",
- parentScope: "preserve",
- });
+ it("keeps a Profile map origin intact", () => {
+ expect(resolveMemoParentPage({ pathname: "/u/alice", search: "?view=map", memoName: "memos/123" })).toBe("/u/alice?view=map");
});
- it("marks Profile cards as global without losing their Profile origin", () => {
- expect(resolveMemoOrigin({ pathname: "/u/alice", search: "?view=map", memoName: "memos/123" })).toEqual({
- parentPage: "/u/alice?view=map",
- parentScope: "all",
- });
+ it("uses Home for direct and shared resource entries", () => {
+ expect(resolveMemoDetailOrigin(undefined)).toBe("/");
+ expect(resolveMemoDetailOrigin({ unrelated: true })).toBe("/");
});
- it("uses an All Home origin for direct and shared resource entries", () => {
- expect(resolveMemoDetailOrigin(undefined)).toEqual({ parentPage: "/", parentScope: "all" });
- expect(resolveMemoDetailOrigin({ unrelated: true })).toEqual({ parentPage: "/", parentScope: "all" });
- });
-
- it("uses a user-level Archived origin without changing the remembered Space", () => {
- expect(resolveMemoDetailOrigin(undefined, { memoArchived: true })).toEqual({ parentPage: "/archived", parentScope: "preserve" });
+ it("uses the user archive for a direct entry to an archived memo", () => {
+ expect(resolveMemoDetailOrigin(undefined, { memoArchived: true })).toBe("/archived");
});
it("keeps an explicit origin ahead of the archived fallback", () => {
- expect(resolveMemoDetailOrigin(createMemoNavigationState("/u/alice", "all"), { memoArchived: true })).toEqual({
- parentPage: "/u/alice",
- parentScope: "all",
- });
+ expect(resolveMemoDetailOrigin(createMemoNavigationState("/u/alice"), { memoArchived: true })).toBe("/u/alice");
});
- it("round-trips an explicit origin policy through router state", () => {
- const state = createMemoNavigationState("/", "preserve");
- expect(resolveMemoDetailOrigin(state)).toEqual({ parentPage: "/", parentScope: "preserve" });
- });
-
- it("infers policy for legacy from-only router state", () => {
- expect(resolveMemoDetailOrigin({ from: "/archived" })).toEqual({ parentPage: "/archived", parentScope: "preserve" });
- expect(resolveMemoDetailOrigin({ from: "/u/alice" })).toEqual({ parentPage: "/u/alice", parentScope: "all" });
- });
-
- it.each([
- "/",
- "/explore?filter=tagSearch%3Awork",
- "/archived",
- "/attachments",
- ])("recognizes %s as a remembered collection origin", (page) => {
- expect(isMemoCollectionOrigin(page)).toBe(true);
+ it("round-trips an explicit origin through router state", () => {
+ expect(resolveMemoDetailOrigin(createMemoNavigationState("/explore?filter=tagSearch%3Awork"))).toBe("/explore?filter=tagSearch%3Awork");
});
it.each([
diff --git a/web/tests/space-routes.test.tsx b/web/tests/space-routes.test.tsx
index e6fab18a..78552782 100644
--- a/web/tests/space-routes.test.tsx
+++ b/web/tests/space-routes.test.tsx
@@ -5,7 +5,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import { resolveQuickFindSubmission } from "@/components/AppSidebar/QuickFindDialog";
import { getRouteActionPolicy, getSidebarRouteKind } from "@/components/AppSidebar/routes";
import { CalendarHeader } from "@/components/CalendarView/CalendarHeader";
-import { resolveMemoDetailOrigin, resolveMemoOrigin } from "@/components/MemoView/navigation";
+import { resolveMemoDetailOrigin, resolveMemoParentPage } from "@/components/MemoView/navigation";
import Calendar from "@/pages/Calendar";
import { buildCollectionPath, getSpaceSwitchPath, resolveCollectionRoute } from "@/router/routes";
import { SpaceRoute } from "@/router/SpaceRoute";
@@ -59,10 +59,10 @@ describe("Space route contract", () => {
it("carries the full calendar origin through a permanent memo link", () => {
const parentPage = "/spaces/a/calendar/2026/09/06?filter=tagSearch%3Awork";
expect(
- resolveMemoOrigin({ pathname: "/spaces/a/calendar/2026/09/06", search: "?filter=tagSearch%3Awork", memoName: "memos/x" }),
- ).toEqual({ parentPage, parentScope: "preserve" });
- expect(resolveMemoDetailOrigin({ from: parentPage })).toEqual({ parentPage, parentScope: "preserve" });
- expect(resolveMemoDetailOrigin(undefined)).toEqual({ parentPage: "/", parentScope: "all" });
+ resolveMemoParentPage({ pathname: "/spaces/a/calendar/2026/09/06", search: "?filter=tagSearch%3Awork", memoName: "memos/x" }),
+ ).toBe(parentPage);
+ expect(resolveMemoDetailOrigin({ from: parentPage })).toBe(parentPage);
+ expect(resolveMemoDetailOrigin(undefined)).toBe("/");
});
});