@anthusai/papyrus 1.0.0-next.22 → 1.0.0-next.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { notFound, redirect } from "next/navigation";
2
+ import { toPublicNewsroomPath } from "../../../../lib/newsroom-base-path";
2
3
  import { NewsDeskPage } from "../../../../components/news-desk-page";
3
4
  import type { NewsDeskTab } from "../../../../components/topic-steering-workspace";
4
5
 
@@ -37,13 +38,13 @@ const NEWS_DESK_ROUTE_SECTIONS = new Set<NewsDeskTab>([
37
38
 
38
39
  export default async function NewsDeskNestedSectionPage({ params, searchParams }: NewsDeskNestedSectionPageProps) {
39
40
  const { section, selection } = await params;
40
- if (section === "users") redirect("/newsroom/administration/users");
41
- if (section === "doctrine") redirect("/newsroom/administration/policies");
42
- if (section === "administration" && selection[0] === "analysis") redirect("/newsroom/assignments");
41
+ if (section === "users") redirect(toPublicNewsroomPath("/newsroom/administration/users"));
42
+ if (section === "doctrine") redirect(toPublicNewsroomPath("/newsroom/administration/policies"));
43
+ if (section === "administration" && selection[0] === "analysis") redirect(toPublicNewsroomPath("/newsroom/assignments"));
43
44
  if (section === "desks") {
44
45
  const category = selection[0] ? decodeURIComponent(selection[0]) : null;
45
- if (category) redirect(`/newsroom/topics?category=${encodeURIComponent(category)}`);
46
- redirect("/newsroom/topics");
46
+ if (category) redirect(toPublicNewsroomPath(`/newsroom/topics?category=${encodeURIComponent(category)}`));
47
+ redirect(toPublicNewsroomPath("/newsroom/topics"));
47
48
  }
48
49
  if (!NEWS_DESK_ROUTE_SECTIONS.has(section as NewsDeskTab)) notFound();
49
50
  return <NewsDeskPage section={section} selectionPath={selection} searchParams={searchParams} />;
@@ -1,4 +1,5 @@
1
1
  import { notFound, redirect } from "next/navigation";
2
+ import { toPublicNewsroomPath } from "../../../lib/newsroom-base-path";
2
3
  import { NewsDeskPage } from "../../../components/news-desk-page";
3
4
  import type { NewsDeskTab } from "../../../components/topic-steering-workspace";
4
5
 
@@ -39,9 +40,9 @@ const NEWS_DESK_ROUTE_SECTIONS = new Set<NewsDeskTab>([
39
40
 
40
41
  export default async function NewsDeskSectionPage({ params, searchParams }: NewsDeskSectionPageProps) {
41
42
  const { section } = await params;
42
- if (section === "users") redirect("/newsroom/administration/users");
43
- if (section === "doctrine") redirect("/newsroom/administration/policies");
44
- if (section === "desks") redirect("/newsroom/topics");
43
+ if (section === "users") redirect(toPublicNewsroomPath("/newsroom/administration/users"));
44
+ if (section === "doctrine") redirect(toPublicNewsroomPath("/newsroom/administration/policies"));
45
+ if (section === "desks") redirect(toPublicNewsroomPath("/newsroom/topics"));
45
46
  if (!NEWS_DESK_ROUTE_SECTIONS.has(section as NewsDeskTab)) notFound();
46
47
  return <NewsDeskPage section={section} searchParams={searchParams} />;
47
48
  }
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { newsroomHref } from "../lib/newsroom-base-path";
3
4
  import Link from "next/link";
4
5
  import { SITE_BRAND } from "../lib/site-brand";
5
6
  import type { NewsDeskShellState } from "../lib/news-desk-session";
@@ -32,7 +33,7 @@ export function NewsDeskAccessGate({ shell, showSectionTabs = false }: { shell:
32
33
  <p className="m-0 text-sm text-foreground">{formatAccessActionDetail(shell)}</p>
33
34
  {shell?.phase === "signedOut" || shell?.phase === "error" ? (
34
35
  <div className="news-desk-access-panel__auth">
35
- <ReaderAuthControl postAuthPath="/newsroom" showIdentity />
36
+ <ReaderAuthControl postAuthPath={newsroomHref()} showIdentity />
36
37
  </div>
37
38
  ) : null}
38
39
  </div>
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { browserPathToInternalNewsroomPath } from "../lib/newsroom-base-path";
3
4
  import type { BotAvatarState } from "anthus-vultus";
4
5
  import dynamic from "next/dynamic";
5
6
  import { usePathname, useRouter, useSearchParams } from "next/navigation";
@@ -152,7 +153,8 @@ export function PapyrusConsoleShell({ children }: PapyrusConsoleShellProps) {
152
153
  const [session, setSession] = useState<ReaderSessionSnapshot | null>(null);
153
154
  const [open, setOpen] = useState(false);
154
155
  const [openingReferenceChat, setOpeningReferenceChat] = useState(false);
155
- const isNewsroomPath = pathname === "/newsroom" || pathname.startsWith("/newsroom/");
156
+ const internalPathname = browserPathToInternalNewsroomPath(pathname);
157
+ const isNewsroomPath = internalPathname === "/newsroom" || internalPathname.startsWith("/newsroom/");
156
158
  const canUseConsole = Boolean(session?.hasSession && session.groups.some((group) => group === "editor" || group === "admin"));
157
159
  const shouldOfferConsole = isNewsroomPath && canUseConsole;
158
160
 
@@ -433,7 +435,7 @@ function ConsolePanel({ actorEmail, actorLabel, onClose }: { actorEmail: string
433
435
  const handledNavigationMessageIdsRef = useRef<Set<string>>(new Set());
434
436
 
435
437
  const currentWebPath = useMemo(() => {
436
- const path = pathname ?? "/newsroom";
438
+ const path = browserPathToInternalNewsroomPath(pathname ?? "/newsroom");
437
439
  const query = searchParams.toString();
438
440
  return query ? `${path}?${query}` : path;
439
441
  }, [pathname, searchParams]);
@@ -76,6 +76,7 @@ import { buildNewsroomKnowledgeQueryInput, type NewsroomKnowledgeQueryAnchor as
76
76
  import { NewsroomConsoleProgressToggle, PapyrusConsoleChatIcon, usePapyrusConsole } from "./papyrus-console-shell";
77
77
  import { useResolvedPapyrusTheme } from "./use-resolved-papyrus-theme";
78
78
  import { SITE_BRAND } from "../lib/site-brand";
79
+ import { browserPathToInternalNewsroomPath, toPublicNewsroomPath } from "../lib/newsroom-base-path";
79
80
  import { getNewsroomNavHref } from "../lib/newsroom-nav";
80
81
  import { cn } from "../lib/utils";
81
82
  import { useOptionalNewsDeskClient } from "./news-desk-client-provider";
@@ -15187,14 +15188,14 @@ function normalizeKnowledgeQueryAnchorKind(value: string | null | undefined): Kn
15187
15188
 
15188
15189
  function normalizeNewsroomFromHref(value: string | null | undefined): string | null {
15189
15190
  if (!value) return null;
15190
- const trimmed = value.trim();
15191
+ const trimmed = browserPathToInternalNewsroomPath(value.trim());
15191
15192
  if (!trimmed.startsWith("/newsroom")) return null;
15192
- return trimmed;
15193
+ return toPublicNewsroomPath(trimmed);
15193
15194
  }
15194
15195
 
15195
15196
  function currentNewsroomOriginHref(fallback: string | null): string | null {
15196
15197
  if (typeof window === "undefined") return fallback;
15197
- const current = `${window.location.pathname}${window.location.search}`;
15198
+ const current = browserPathToInternalNewsroomPath(`${window.location.pathname}${window.location.search}`);
15198
15199
  return current.startsWith("/newsroom/search") ? fallback : normalizeNewsroomFromHref(current);
15199
15200
  }
15200
15201
 
@@ -15228,7 +15229,7 @@ function buildNewsroomSearchHref(request: NewsroomSearchRequest, demo?: boolean)
15228
15229
  if (request.from) params.set("from", request.from);
15229
15230
  if (demo) params.set("demo", "1");
15230
15231
  const queryString = params.toString();
15231
- return queryString ? `/newsroom/search?${queryString}` : "/newsroom/search";
15232
+ return toPublicNewsroomPath(queryString ? `/newsroom/search?${queryString}` : "/newsroom/search");
15232
15233
  }
15233
15234
 
15234
15235
  function focusNewsroomSearchForm() {
@@ -19,13 +19,22 @@ export function usesNewsroomRootPaths(): boolean {
19
19
  export function toPublicNewsroomPath(internalPath: string): string {
20
20
  const publicBase = getPublicNewsroomBasePath();
21
21
  if (publicBase !== "") return internalPath;
22
- if (internalPath === "/newsroom" || internalPath === "/newsroom/") return "/";
23
- if (internalPath.startsWith("/newsroom/")) {
24
- return internalPath.slice("/newsroom".length) || "/";
22
+ const suffixStart = internalPath.search(/[?#]/);
23
+ const pathOnly = suffixStart === -1 ? internalPath : internalPath.slice(0, suffixStart);
24
+ const suffix = suffixStart === -1 ? "" : internalPath.slice(suffixStart);
25
+ if (pathOnly === "/newsroom" || pathOnly === "/newsroom/") return `/${suffix}`;
26
+ if (pathOnly.startsWith("/newsroom/")) {
27
+ return `${pathOnly.slice("/newsroom".length) || "/"}${suffix}`;
25
28
  }
26
29
  return internalPath;
27
30
  }
28
31
 
32
+ /** Maps a browser pathname to the internal `/newsroom/...` form on CMS-only hosts; identity elsewhere. */
33
+ export function browserPathToInternalNewsroomPath(browserPath: string): string {
34
+ if (!usesNewsroomRootPaths()) return browserPath;
35
+ return toInternalNewsroomPath(browserPath);
36
+ }
37
+
29
38
  export function toInternalNewsroomPath(publicPath: string): string {
30
39
  const publicBase = getPublicNewsroomBasePath();
31
40
  const normalized = publicPath.startsWith("/") ? publicPath : `/${publicPath}`;
@@ -1,3 +1,4 @@
1
+ import { toPublicNewsroomPath } from "./newsroom-base-path";
1
2
  import type {
2
3
  CategorySteeringCategory,
3
4
  SemanticRelationRecord,
@@ -93,11 +94,11 @@ export function uniqueNeighborGroupsForCategoryContext(graph: CategoryGraph, con
93
94
  export function topicHref(rootCategoryKey: string, selectedCategoryKey?: string | null): string {
94
95
  const encodedRoot = encodeURIComponent(rootCategoryKey);
95
96
  const encodedSelected = selectedCategoryKey && selectedCategoryKey !== rootCategoryKey ? `/${encodeURIComponent(selectedCategoryKey)}` : "";
96
- return `/newsroom/topics/${encodedRoot}${encodedSelected}`;
97
+ return toPublicNewsroomPath(`/newsroom/topics/${encodedRoot}${encodedSelected}`);
97
98
  }
98
99
 
99
100
  export function categoryDrilldownHref(section: "references" | "concepts", categoryKey: string): string {
100
- return `/newsroom/${section}/${encodeURIComponent(categoryKey)}`;
101
+ return toPublicNewsroomPath(`/newsroom/${section}/${encodeURIComponent(categoryKey)}`);
101
102
  }
102
103
 
103
104
  function matchesCategorySelection(category: CategorySteeringCategory, selection: string): boolean {
@@ -1,3 +1,5 @@
1
+ import { toPublicNewsroomPath } from "./newsroom-base-path";
2
+
1
3
  export const FORUM_THREAD_ROUTE_PREFIX = "/newsroom/forum/";
2
4
  export const INSIGHT_THREAD_ROUTE_PREFIX = "/newsroom/insights/";
3
5
  export const FORUM_MESSAGE_HASH_PREFIX = "message-";
@@ -81,7 +83,7 @@ export function buildForumThreadUrl(
81
83
  if (options?.demo) params.set("demo", "1");
82
84
  const query = params.toString();
83
85
  const hash = options?.messageId ? getForumMessageAnchorId(options.messageId) : "";
84
- return `/newsroom/forum/${encodedThreadId}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`;
86
+ return toPublicNewsroomPath(`/newsroom/forum/${encodedThreadId}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`);
85
87
  }
86
88
 
87
89
  export function buildInsightThreadUrl(
@@ -93,7 +95,7 @@ export function buildInsightThreadUrl(
93
95
  if (options?.demo) params.set("demo", "1");
94
96
  const query = params.toString();
95
97
  const hash = options?.messageId ? getForumMessageAnchorId(options.messageId) : "";
96
- return `${INSIGHT_THREAD_ROUTE_PREFIX}${encodedThreadId}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`;
98
+ return toPublicNewsroomPath(`${INSIGHT_THREAD_ROUTE_PREFIX}${encodedThreadId}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`);
97
99
  }
98
100
 
99
101
  export function buildThreadUrlForSurface(
@@ -141,7 +143,7 @@ export function buildNewsroomForumIndexUrl(demo?: boolean): string {
141
143
  const params = new URLSearchParams();
142
144
  if (demo) params.set("demo", "1");
143
145
  const query = params.toString();
144
- return query ? `/newsroom?${query}` : "/newsroom";
146
+ return toPublicNewsroomPath(query ? `/newsroom?${query}` : "/newsroom");
145
147
  }
146
148
 
147
149
  export function buildNewsroomInsightsIndexUrl(demo?: boolean, domain?: string): string {
@@ -149,5 +151,5 @@ export function buildNewsroomInsightsIndexUrl(demo?: boolean, domain?: string):
149
151
  if (demo) params.set("demo", "1");
150
152
  if (domain?.trim()) params.set("domain", domain.trim());
151
153
  const query = params.toString();
152
- return query ? `/newsroom/insights?${query}` : "/newsroom/insights";
154
+ return toPublicNewsroomPath(query ? `/newsroom/insights?${query}` : "/newsroom/insights");
153
155
  }
@@ -1,3 +1,4 @@
1
+ import { browserPathToInternalNewsroomPath, toPublicNewsroomPath } from "./newsroom-base-path";
1
2
  export type NewsroomIndexTab = "references" | "messages" | "assignments" | "insights";
2
3
 
3
4
  export type ReferenceIndexOrder = "published" | "imported";
@@ -147,10 +148,10 @@ export function buildNewsroomIndexWebPath(
147
148
  tab: NewsroomIndexTab,
148
149
  filters: ReferencesIndexFilters | MessagesIndexFilters | AssignmentsIndexFilters | InsightsIndexFilters,
149
150
  ): string {
150
- if (tab === "references") return `/newsroom/references${buildReferencesIndexQuery(filters as ReferencesIndexFilters)}`;
151
- if (tab === "messages") return `/newsroom/messages${buildMessagesIndexQuery(filters as MessagesIndexFilters)}`;
152
- if (tab === "insights") return `/newsroom/insights${buildInsightsIndexQuery(filters as InsightsIndexFilters)}`;
153
- return `/newsroom/assignments${buildAssignmentsIndexQuery(filters as AssignmentsIndexFilters)}`;
151
+ if (tab === "references") return toPublicNewsroomPath(`/newsroom/references${buildReferencesIndexQuery(filters as ReferencesIndexFilters)}`);
152
+ if (tab === "messages") return toPublicNewsroomPath(`/newsroom/messages${buildMessagesIndexQuery(filters as MessagesIndexFilters)}`);
153
+ if (tab === "insights") return toPublicNewsroomPath(`/newsroom/insights${buildInsightsIndexQuery(filters as InsightsIndexFilters)}`);
154
+ return toPublicNewsroomPath(`/newsroom/assignments${buildAssignmentsIndexQuery(filters as AssignmentsIndexFilters)}`);
154
155
  }
155
156
 
156
157
  export function syncBrowserNewsroomIndexUrl(
@@ -168,8 +169,9 @@ export function syncBrowserNewsroomIndexUrl(
168
169
 
169
170
  /** Lineage id from `/newsroom/references/<id>` (not index-only URLs). */
170
171
  export function parseReferenceLineageIdFromNewsroomPathname(pathname: string | null | undefined): string | null {
171
- if (!pathname?.startsWith("/newsroom/references/")) return null;
172
- const segment = pathname.slice("/newsroom/references/".length).split("/")[0]?.trim() ?? "";
172
+ const internalPathname = pathname ? browserPathToInternalNewsroomPath(pathname) : null;
173
+ if (!internalPathname?.startsWith("/newsroom/references/")) return null;
174
+ const segment = internalPathname.slice("/newsroom/references/".length).split("/")[0]?.trim() ?? "";
173
175
  if (!segment) return null;
174
176
  try {
175
177
  return decodeURIComponent(segment);
@@ -180,8 +182,9 @@ export function parseReferenceLineageIdFromNewsroomPathname(pathname: string | n
180
182
 
181
183
  /** Assignment id from `/newsroom/assignments/<id>` (not index-only URLs). */
182
184
  export function parseAssignmentIdFromNewsroomPathname(pathname: string | null | undefined): string | null {
183
- if (!pathname?.startsWith("/newsroom/assignments/")) return null;
184
- const segment = pathname.slice("/newsroom/assignments/".length).split("/")[0]?.trim() ?? "";
185
+ const internalPathname = pathname ? browserPathToInternalNewsroomPath(pathname) : null;
186
+ if (!internalPathname?.startsWith("/newsroom/assignments/")) return null;
187
+ const segment = internalPathname.slice("/newsroom/assignments/".length).split("/")[0]?.trim() ?? "";
185
188
  if (!segment) return null;
186
189
  try {
187
190
  return decodeURIComponent(segment);
@@ -191,8 +194,9 @@ export function parseAssignmentIdFromNewsroomPathname(pathname: string | null |
191
194
  }
192
195
 
193
196
  export function parseInsightThreadIdFromNewsroomPathname(pathname: string | null | undefined): string | null {
194
- if (!pathname?.startsWith("/newsroom/insights/")) return null;
195
- const segment = pathname.slice("/newsroom/insights/".length).split("/")[0]?.trim() ?? "";
197
+ const internalPathname = pathname ? browserPathToInternalNewsroomPath(pathname) : null;
198
+ if (!internalPathname?.startsWith("/newsroom/insights/")) return null;
199
+ const segment = internalPathname.slice("/newsroom/insights/".length).split("/")[0]?.trim() ?? "";
196
200
  if (!segment) return null;
197
201
  try {
198
202
  return decodeURIComponent(segment);
@@ -1,3 +1,4 @@
1
+ import { toPublicNewsroomPath } from "./newsroom-base-path";
1
2
  import type {
2
3
  AssignmentRecord,
3
4
  CategorySteeringCategory,
@@ -175,6 +176,10 @@ export function predicateLabel(predicate: string, direction: "outgoing" | "incom
175
176
  }
176
177
 
177
178
  export function newsDeskHrefForSemanticObject(kind: string, lineageId: string): string {
179
+ return toPublicNewsroomPath(internalNewsDeskHrefForSemanticObject(kind, lineageId));
180
+ }
181
+
182
+ function internalNewsDeskHrefForSemanticObject(kind: string, lineageId: string): string {
178
183
  const encoded = encodeURIComponent(lineageId);
179
184
  if (kind === "reference") return `/newsroom/references/${encoded}`;
180
185
  if (kind === "category") return `/newsroom/topics?category=${encoded}`;
@@ -12,7 +12,8 @@ function isStaticOrApiPath(pathname: string): boolean {
12
12
 
13
13
  export { isStaticOrApiPath };
14
14
 
15
- export function isStagingGatedPath(pathname: string, readerBasePath: string = ""): boolean {
15
+ export function isStagingGatedPath(pathname: string, readerBasePath: string = "", newsroomAtRoot: boolean = false): boolean {
16
+ if (newsroomAtRoot) return false;
16
17
  if (isStaticOrApiPath(pathname)) return false;
17
18
  if (pathname === "/robots.txt") return false;
18
19
  if (pathname === "/newsroom" || pathname.startsWith("/newsroom/")) return false;
package/middleware.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { NextRequest } from "next/server";
2
2
  import { NextResponse } from "next/server";
3
3
  import { getSiteBrand } from "./lib/site-brand";
4
+ import { newsroomHref } from "./lib/newsroom-base-path";
4
5
  import { getReaderBasePath } from "./lib/reader-base-path";
5
6
  import { isStagingGatedPath, isStaticOrApiPath } from "./lib/staging-gated-path";
6
7
  import { getSiteEnv, isIndexable } from "./lib/site-env";
@@ -18,7 +19,7 @@ function usesNewsroomRootPaths(): boolean {
18
19
  }
19
20
 
20
21
  function isStagingGatedPathForBrand(pathname: string): boolean {
21
- return isStagingGatedPath(pathname, getReaderBasePath());
22
+ return isStagingGatedPath(pathname, getReaderBasePath(), usesNewsroomRootPaths());
22
23
  }
23
24
 
24
25
  function routeRequest(request: NextRequest): NextResponse {
@@ -37,7 +38,7 @@ function routeRequest(request: NextRequest): NextResponse {
37
38
  }
38
39
 
39
40
  if (pathname === "/newsroom" || pathname === "/newsroom/") {
40
- return NextResponse.redirect(new URL("/", request.url));
41
+ return NextResponse.redirect(new URL(`/${request.nextUrl.search}`, request.url));
41
42
  }
42
43
  if (pathname.startsWith("/newsroom/")) {
43
44
  const target = pathname.slice("/newsroom".length) || "/";
@@ -60,12 +61,14 @@ function routeRequest(request: NextRequest): NextResponse {
60
61
  export async function middleware(request: NextRequest): Promise<NextResponse> {
61
62
  let sessionResponse: NextResponse | null = null;
62
63
  const { pathname } = request.nextUrl;
63
- const gated = isStaticPreviewEnabled() ? isPreviewGatedPath(pathname) : isStagingGatedPathForBrand(pathname);
64
+ const gated = isStaticPreviewEnabled() && !usesNewsroomRootPaths()
65
+ ? isPreviewGatedPath(pathname)
66
+ : isStagingGatedPathForBrand(pathname);
64
67
  if (getSiteEnv() === "staging" && gated) {
65
68
  sessionResponse = NextResponse.next();
66
69
  const access = await getStagingAccess(request, sessionResponse);
67
70
  if (access === "anonymous") {
68
- return withRobotsHeader(NextResponse.redirect(new URL("/newsroom", request.url)));
71
+ return withRobotsHeader(NextResponse.redirect(new URL(newsroomHref(), request.url)));
69
72
  }
70
73
  if (access === "forbidden") {
71
74
  return withRobotsHeader(new NextResponse("Staging is limited to editors and admins.", { status: 403 }));
package/package.json CHANGED
@@ -293,7 +293,7 @@
293
293
  "optional": true
294
294
  }
295
295
  },
296
- "version": "1.0.0-next.22",
296
+ "version": "1.0.0-next.24",
297
297
  "dependencies": {
298
298
  "@aws-amplify/adapter-nextjs": "^1.7.3",
299
299
  "@aws-sdk/client-cognito-identity-provider": "3.1045.0",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.0-next.22",
2
+ "version": "1.0.0-next.24",
3
3
  "routes": [
4
4
  {
5
5
  "file": "app/%5F_preview/[[...path]]/route.ts",