import { agentNativePath } from "@agent-native/core/client/api-path"; import { useT } from "@agent-native/core/client/i18n"; import { AGENT_ACCESS_PARAM, verifyScopedAgentAccessToken, } from "@agent-native/core/server"; import { getConfiguredAppBasePath, getRequestUserEmail, } from "@agent-native/core/server"; import { AGENT_READABLE_RESOURCE_SCRIPT_TYPE, safeJsonForHtml, } from "@agent-native/core/shared"; import { resolveAccess } from "@agent-native/core/sharing"; import { buildPublicDocumentDescription } from "@shared/og-description"; import { IconLock, IconMessageCircle } from "@tabler/icons-react"; import { eq } from "drizzle-orm"; import { useEffect, useState } from "react"; import type { HeadersArgs, LoaderFunctionArgs, MetaFunction, } from "react-router"; import { data, redirect, useLoaderData } from "react-router"; import { VisualEditor } from "@/components/editor/VisualEditor"; import { getDb, schema } from "../../server/db"; import { buildContentDocumentAgentDiscovery, buildContentPublicDocumentUrl, DOCUMENT_AGENT_RESOURCE_KIND, } from "../../shared/agent-readable"; type PublicDocumentLoaderData = | { document: { id: string; title: string; content: string; updatedAt: string; visibility: string; }; agentAccessToken: string | null; basePath: string; unavailable?: undefined; } | { document: null; agentAccessToken: null; basePath: string; unavailable: { reason: "private"; id: string; basePath: string }; }; const PRIVATE_AGENT_DOCUMENT_HEADERS = { "Cache-Control": "private, max-age=0, no-store", "Referrer-Policy": "no-referrer", }; function publicDocumentLoaderData( payload: PublicDocumentLoaderData, privateAgentAccess = false, ) { if (!privateAgentAccess) return payload; return data(payload, { headers: PRIVATE_AGENT_DOCUMENT_HEADERS, }); } export function headers({ loaderHeaders }: HeadersArgs) { return loaderHeaders; } export async function loader({ params, request }: LoaderFunctionArgs) { const id = params.id; if (!id) throw new Response("Not found", { status: 404 }); const agentAccessToken = new URL(request.url).searchParams.get( AGENT_ACCESS_PARAM, ); // This is a server loader; use the server-side base-path helper // (reads APP_BASE_PATH / VITE_APP_BASE_PATH at request time) // instead of the client `appPath()` which relies on // `import.meta.env` and is meant for browser code. const basePath = getConfiguredAppBasePath(); const withBase = (path: string) => `${basePath}${path}`; const userEmail = getRequestUserEmail(); if (userEmail) { const access = await resolveAccess("document", id); if (access) throw redirect(withBase(`/page/${id}`)); } const [doc] = await getDb() .select({ id: schema.documents.id, title: schema.documents.title, content: schema.documents.content, updatedAt: schema.documents.updatedAt, visibility: schema.documents.visibility, }) .from(schema.documents) .where(eq(schema.documents.id, id)) .limit(1); if (!doc) throw new Response("Not found", { status: 404 }); const tokenAccess = agentAccessToken ? verifyScopedAgentAccessToken(agentAccessToken, { resourceKind: DOCUMENT_AGENT_RESOURCE_KIND, resourceId: id, }).ok : false; if (doc.visibility === "public" || tokenAccess) { return publicDocumentLoaderData( { document: doc, agentAccessToken: tokenAccess ? agentAccessToken : null, basePath, }, tokenAccess, ); } // Doc exists but isn't public. SSR renders impersonally (no session is read // server-side, so the page can be CDN-cached for everyone), which means we // must NOT redirect to sign-in from here: a signed-in viewer would loop // (sign-in sees their valid session and bounces back to /p/, which // re-runs this anonymous loader and redirects again). Instead return the // private placeholder and resolve access on the client — PrivateDocumentNotice // routes the viewer to the auth-guarded `/page/` editor, where the real // per-user access check runs (signed-in-with-access sees the doc; everyone // else gets the standard sign-in / no-access handling). return publicDocumentLoaderData({ document: null, agentAccessToken: null, basePath, unavailable: { reason: "private" as const, id, basePath }, }); } export const meta: MetaFunction = ({ loaderData }) => { const title = loaderData?.document?.title ?? "Public document"; const description = buildPublicDocumentDescription({ title, content: loaderData?.document?.content, }); return [ { title }, { name: "description", content: description, }, { property: "og:title", content: title, }, { property: "og:description", content: description, }, { name: "twitter:title", content: title, }, { name: "twitter:description", content: description, }, ]; }; function formatUpdatedAt(value: string) { const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric", }); } function renderMarkdownBlocks(content: string) { return content.split(/\n{2,}/).map((block, index) => { const trimmed = block.trim(); if (!trimmed) return null; if (trimmed.startsWith("## ")) { return (

{trimmed.slice(3)}

); } if (trimmed.startsWith("- ")) { return (
    {trimmed.split("\n").map((item) => (
  • {item.replace(/^- /, "")}
  • ))}
); } return (

{trimmed}

); }); } function PublicDocumentContextSync({ document, basePath, }: { document: { id: string; title: string; content: string; updatedAt: string; }; basePath?: string; }) { useEffect(() => { fetch(agentNativePath("/_agent-native/application-state/navigation"), { method: "PUT", keepalive: true, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ view: "public-document", documentId: document.id, title: document.title, publicUrl: buildContentPublicDocumentUrl(document.id, { basePath }), }), }).catch(() => {}); }, [basePath, document.id, document.title]); return null; } function ReadOnlyMarkdownContent({ content }: { content: string }) { const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); if (!mounted) { return
{renderMarkdownBlocks(content)}
; } return ( {}} editable={false} /> ); } function AgentReadableDocumentDiscovery({ document, token, basePath, }: { document: { id: string; title: string }; token?: string | null; basePath?: string; }) { const discovery = buildContentDocumentAgentDiscovery({ document, token, basePath, }); return (