/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * `BundlePreview` โ€” read-only viewer for the files inside a bundle. * * Surfaces every file in the install bundle so the user can audit the * code before approving. Plays nicely with `CapabilityReview` โ€” the * review modal exposes a "Show source" tab that mounts this. * * Spec: docs/architecture/ai-customization/01-extension-model.md ยง6. */ import { useMemo, useState } from 'react'; import { Copy } from 'lucide-react'; import type { Bundle, BundleFile } from '@ifc-lite/extensions'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; import { toast } from '@/components/ui/toast'; import { cn } from '@/lib/utils'; const DECODER = new TextDecoder(); interface BundlePreviewProps { bundle: Bundle; } export function BundlePreview({ bundle }: BundlePreviewProps) { const paths = useMemo(() => Array.from(bundle.files.keys()).sort(), [bundle]); const [selected, setSelected] = useState(paths[0] ?? 'manifest.json'); const file = bundle.files.get(selected); const text = useMemo(() => fileToText(file), [file]); const handleCopy = async () => { try { await navigator.clipboard.writeText(text); toast.success(`Copied ${selected} to clipboard`); } catch (err) { toast.error(`Copy failed: ${err instanceof Error ? err.message : String(err)}`); } }; return (
{/* File list */} {/* Source */}
{selected}
            {text}
          
); } function fileToText(file: BundleFile | undefined): string { if (!file) return '(file missing)'; if (file.text) return file.text; try { return DECODER.decode(file.bytes); } catch { return `(${file.bytes.byteLength} bytes โ€” binary)`; } }