Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/app-shell/src/console/ai/AiChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,23 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
[apiBase],
);

// Public share-link landing base. SharedRecordPage lives UNDER the console
// SPA basename (e.g. `/_console/s/:token`), so the ShareDialog default of
// `${origin}/s/:token` 404s for recipients. Derive the base from the SPA's
// BASE_URL so the copyable link points at the actually-served route.
const publicShareBase = useMemo(() => {
if (typeof window === 'undefined' || typeof document === 'undefined') return undefined;
// Mirror the console's own basename resolution (App.tsx resolveBasename):
// the published SPA uses a relative Vite base, so the mount path is carried
// by the injected `<base href>` tag, NOT import.meta.env.BASE_URL.
let base = '';
try {
const href = document.querySelector('base')?.getAttribute('href');
if (href) base = new URL(href, window.location.origin).pathname.replace(/\/+$/, '');
} catch { /* no <base> → root-mounted SPA */ }
return `${window.location.origin}${base}/s`;
}, []);

// New-conversation race guard. On an IN-SPA `/ai?new=1` navigation the
// URL-mirroring effect below fires in the SAME commit as the hook's effect,
// with this render's (stale) `conversationId` still in its closure — the
Expand Down Expand Up @@ -630,6 +647,7 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
recordId={conversationId}
recordLabel="this conversation"
apiBase={restApiBase}
publicBaseUrl={publicShareBase}
/>
)}
<div className="flex min-h-0 flex-1 w-full bg-muted/20">
Expand Down
9 changes: 6 additions & 3 deletions packages/components/src/share/ShareDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { Badge } from '../ui/badge';
import { cn } from '../lib/utils';

export type ShareLinkPermission = 'view' | 'comment' | 'edit';
export type ShareLinkAudience = 'link_only' | 'signed_in' | 'workspace' | 'email_allowlist';
export type ShareLinkAudience = 'public' | 'link_only' | 'signed_in' | 'email';

export interface ShareLink {
id: string;
Expand Down Expand Up @@ -106,11 +106,14 @@ const EXPIRY_OPTIONS: { label: string; value: number | null }[] = [
{ label: 'Never', value: null },
];

// Audience values match the framework's ShareLinkAudience contract
// (@objectstack/spec). Only the two that need no extra input are offered;
// 'email' (allowlist) / 'public' require UI not yet built. The target
// object's `publicSharing.allowedAudiences` further constrains this — the
// server returns 422 AUDIENCE_NOT_ALLOWED if a value is not permitted.
const AUDIENCE_OPTIONS: { value: ShareLinkAudience; label: string; help: string }[] = [
{ value: 'link_only', label: 'Anyone with the link', help: 'No sign-in required' },
{ value: 'signed_in', label: 'Signed-in users', help: 'Must be logged in to view' },
{ value: 'workspace', label: 'Workspace members', help: 'Same tenant only' },
{ value: 'email_allowlist', label: 'Specific emails', help: 'Restricted to listed addresses' },
];

function buildPublicUrl(base: string | undefined, token: string): string {
Expand Down
Loading