- {isContentLoading ? (
-
-
- {t.common.loading}
-
- ) : contentError ? (
-
- {contentError instanceof Error
- ? contentError.message
- : String(contentError)}
-
- ) : (
-
- {contentData?.content}
-
+
+
+
{tx.detailEmpty}
+ {!canManage && (
+
{tx.detailAdminOnlyHint}
)}
)}
+
+ {/* SKILL.md source — publisher / admin only, collapsed by default */}
+ {canManage && (
+
+
+
+
+
+ {tx.contentSectionTitle}
+
+
+
+
+
+ {isContentLoading ? (
+
+
+ {t.common.loading}
+
+ ) : contentError ? (
+
+ {contentError instanceof Error
+ ? contentError.message
+ : String(contentError)}
+
+ ) : (
+
+ {contentData?.content}
+
+ )}
+
+
+
+
+ )}
diff --git a/frontend-web/src/components/workspace/thread-share-dialogs.tsx b/frontend-web/src/components/workspace/thread-share-dialogs.tsx
new file mode 100644
index 00000000..7d4b1737
--- /dev/null
+++ b/frontend-web/src/components/workspace/thread-share-dialogs.tsx
@@ -0,0 +1,328 @@
+"use client";
+
+import {
+ CheckIcon,
+ CopyIcon,
+ DownloadIcon,
+ ExternalLinkIcon,
+ Loader2,
+ Trash2Icon,
+} from "lucide-react";
+import { useEffect, useState } from "react";
+import { toast } from "sonner";
+
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Input } from "@/components/ui/input";
+import { useI18n } from "@/core/i18n/hooks";
+import type {
+ SharePreview,
+ ThreadShare,
+ ThreadShareMode,
+} from "@/core/thread-shares/api";
+import { buildPublicShareUrl, extractShareCode } from "@/core/thread-shares/api";
+import {
+ useCreateShare,
+ useImportShare,
+ usePreviewShare,
+ useRevokeShare,
+} from "@/core/thread-shares/hooks";
+import { cn } from "@/lib/utils";
+
+function errorMessage(err: unknown): string {
+ return err instanceof Error ? err.message : String(err);
+}
+
+/**
+ * Dialog that produces a share link for a single conversation. Two modes:
+ *
+ * - `import` — mints a short code; a recipient enters it in the import dialog
+ * to copy the conversation into their own account.
+ * - `view` — mints a public, read-only link that anyone can open without
+ * logging in (a static page; viewers cannot send new messages).
+ */
+export function ShareConversationDialog({
+ threadId,
+ open,
+ onOpenChange,
+}: {
+ threadId: string | null;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}) {
+ const { t } = useI18n();
+ const tx = t.threadShare;
+ const createShare = useCreateShare();
+ const revokeShare = useRevokeShare();
+ const [mode, setMode] = useState