Files
supabase/apps/studio/components/interfaces/Explorer/NotebookPreview/NotebookPreview.tsx
T
Charis 071a07118a feat(studio): add NotebookPreview diff component (#49112)
## Summary

Stacked on #49109 (PR 1 — `deriveNotebookDiff`). This is PR 3 of the
notebook approval-preview stack: a pure presentational component that
renders the cell-level diff for a proposed notebook create/update, for
use in the assistant approval UI (wired in a later PR).

- `NotebookPreview` — header summary (`"6 cells"` for create, `"+2 −1 ~1
↕1"` for update) + entry list + "Show N more cells" for long notebooks.
- `NotebookPreviewCell` — dispatches per entry tag:
`unchanged`/`removed`/`moved` collapse to a muted badge row; `added`
renders source via `CodeBlock` (with a max-height/expand toggle);
`replaced` renders a `DiffEditor` diff, plus a before → after metadata
line when only `database_identifier`/`time_range` changed (SQL/text
identical).
- `NotebookPreview.utils` — pure helpers (labels, source/metadata
extraction, language mapping, summary formatting), unit tested.
- **Safety property**: cell content only ever renders through
`CodeBlock`/`DiffEditor` (literal source), never through a markdown
renderer — agent-authored text can't trigger image loads or link
navigation before the user approves. Covered by an adversarial test
(`![x](evil)`, `[y](evil)`, `<img onerror>` → zero
`img`/`[href]`/`[src]` DOM nodes).
- Adds `'markdown'` as a supported `CodeBlock` language (small, additive
change to `packages/ui-patterns`).

Towards FE-4143

## Test plan

- [x] `pnpm --filter studio test` — NotebookPreview suite (21 tests)
passes
- [x] `pnpm --filter studio exec eslint
components/interfaces/Explorer/NotebookPreview` — clean
- [x] `pnpm --filter studio exec tsc --noEmit` — no new errors
- [x] `pnpm exec prettier --check` — clean

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added notebook previews showing create and update summaries.
* Displayed added, removed, moved, unchanged, and replaced cells with
metadata and source diffs.
* Added expandable previews with truncation and a “Show more cells”
option.
  * Added Markdown syntax highlighting to code blocks.

* **Bug Fixes**
* Safely render adversarial agent-authored Markdown as literal content.

* **Tests**
* Added comprehensive coverage for notebook previews, summaries,
metadata, formatting, and truncation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-18 11:44:23 -04:00

49 lines
1.6 KiB
TypeScript

import { useState } from 'react'
import { Button } from 'ui'
import {
formatNotebookDiffSummary,
getEntryKey,
summarizeNotebookDiff,
} from './NotebookPreview.utils'
import { NotebookPreviewCell } from './NotebookPreviewCell'
import type { NotebookCellDiffEntry } from '@/data/content/notebooks/notebook-operations'
export interface NotebookPreviewProps {
entries: NotebookCellDiffEntry[]
mode: 'create' | 'update'
}
const VISIBLE_ENTRY_LIMIT = 5
/**
* Read-only preview of a proposed notebook create/update, rendered from a pre-computed diff.
* Pure presentational component: no data fetching, no approval or notebook-editor state — see
* `deriveNotebookDiff` for how `entries` is produced.
*/
export const NotebookPreview = ({ entries, mode }: NotebookPreviewProps) => {
const [isExpanded, setIsExpanded] = useState(false)
const summary = summarizeNotebookDiff(entries, mode)
const visibleEntries = isExpanded ? entries : entries.slice(0, VISIBLE_ENTRY_LIMIT)
const hiddenCount = entries.length - visibleEntries.length
return (
<div className="flex flex-col gap-2">
<p className="text-xs text-foreground-light font-mono">
{formatNotebookDiffSummary(summary)}
</p>
<div className="flex flex-col gap-1.5">
{visibleEntries.map((entry) => (
<NotebookPreviewCell key={getEntryKey(entry)} entry={entry} />
))}
</div>
{hiddenCount > 0 && (
<Button variant="text" className="w-full" onClick={() => setIsExpanded(true)}>
Show {hiddenCount} more cell{hiddenCount === 1 ? '' : 's'}
</Button>
)}
</div>
)
}