[content] src/extension.ts (typescript)
	const editor = vscode.window.activeTextEditor;
	if (!editor) { return; }
	disposePreviewDecorations();

	// Only preview the next text edit action (insert/delete/replace)
	const next = plan.find(a => a.kind === 'editInsert' || a.kind === 'editDelete' || a.kind === 'editReplace');
	if (!next) {
		previewVisible = false;
		vscode.commands.executeCommand('setContext', UI_CONTEXT_KEY, false);
		currentPlan = plan;
		return;
	}

	const trimText = (t: string) => {
		const oneLine = t.replace(/
?
/g, '\
');
		return oneLine.length > 80 ? oneLine.slice(0, 77) + '…' : oneLine;
	};

	if (next.kind === 'editInsert') {
		const pos = new vscode.Position(next.position[0], next.position[1]);
		decorationInsertType = vscode.window.createTextEditorDecorationType({
			after: {
				contentText: `${trimText(next.text)}`,
				color: new vscode.ThemeColor('charts.purple'),
				fontStyle: 'italic',
				fontWeight: '600',
			}
		});
		editor.setDecorations(decorationInsertType, [{ range: new vscode.Range(pos, pos) }]);
	} else if (next.kind === 'editDelete') {
		const range = new vscode.Range(
			new vscode.Position(next.range.start[0], next.range.start[1]),
			new vscode.Position(next.range.end[0], next.range.end[1])
		);
		decorationDeleteType = vscode.window.createTextEditorDecorationType({
			backgroundColor: 'rgba(255, 60, 60, 0.18)',
			border: '1px solid rgba(255, 60, 60, 0.35)',
			textDecoration: 'line-through'
		});
		editor.setDecorations(decorationDeleteType, [{ range }]);
	} else if (next.kind === 'editReplace') {
		const range = new vscode.Range(
			new vscode.Position(next.range.start[0], next.range.start[1]),
			new vscode.Position(next.range.end[0], next.range.end[1])
		);
		// Highlight original range (to be replaced)
		decorationReplaceType = vscode.window.createTextEditorDecorationType({
			backgroundColor: 'rgba(255,165,0,0.15)',
			border: '1px dashed rgba(255,165,0,0.45)',
			color: new vscode.ThemeColor('disabledForeground'),
			textDecoration: 'line-through'
		});
		editor.setDecorations(decorationReplaceType, [{ range }]);

		// Show replacement block before the range
		decorationReplaceBlockType = vscode.window.createTextEditorDecorationType({
			before: {
				contentText: next.text, // This won't work for multiline text directly in contentText
				color: new vscode.ThemeColor('charts.purple'),
				fontStyle: 'italic',
				fontWeight: '600'
			}
		});
		
		// We need to handle multiline replacement carefully. 
		// VScode decorations don't support multiline 'contentText' well for block rendering.
		// Instead, we split lines and use a block decoration or multiple line decorations.
		// A cleaner way for "block" preview is to use a separate decoration that injects content.
		// However, simple 'contentText' strips newlines.
		// Let's use a hack: for multiline, we just show "Replacement:
<content>" 
		// but since we want it visible without hover, we use a loop to render lines if needed,
		// or just use the first line with ellipsis if it's too long?
		// No, user wants to see the replacement.
		
		// Actually, standard approach for "virtual text" blocks is using `before` on the first line 
		// but passing the full text string only works if it doesn't contain newlines or if we use multiple decorations.
		// Let's try rendering each line of the replacement as a virtual line above the target.
		
		const lines = next.text.split(/
?
/);
		const blockDeco: vscode.DecorationOptions[] = [];
		// We insert the block before the start line of the range
		const startLine = range.start.line;
		
		// We can't easily "push" lines down with decorations. 
		// But we can show the replacement text *overlaying* or *before* the text.
		// If we want a "pop up" feel that is always visible, maybe we can use a large 'before' attachment on the first line?
		// VS Code allows `innerText` to span? No.
		// Let's try iterating and adding a decoration for each line of the replacement text, 
		// effectively stacking them above the start line? 
		// No, multiple 'before' on the same range usually overlap or fail.
		
		// ALTERNATIVE: Use a single decoration with `before` contentText set to the first line, 
		// and if multiline, we might be stuck. 
		// WAIT: We can use `renderOptions` with `after` on the PREVIOUS line? No.
		
		// Let's use the valid approach for multiline virtual text:
		// We can't really inject new lines that shift content.
		// But we can display the replacement text *inline* if it's small, or use a hover.
		// User specifically rejected hover and CodeLens.
		// What if we just display the text in the 'before' content property but replace newlines with `
` symbol?
		// That might be hard to read for blocks.
		
		// Let's try a best-effort block display:
		// We will attach a decoration to the start of the range.
		// Its 'before' content will be the full text, with newlines replaced by a visible newline symbol/spacer?
		// OR we accept that we can only show single-line previews easily.
		
		// Let's go with the "diff view" style:
		// Show the new text in Green (purple here) above the old text.
		// Since we can't inject lines, we'll just format it as a single block string if possible, or truncate.
		// User said: "replacement code lens displays the entire replacement text above the first line". 
		// This implies they saw the block and didn't like the CodeLens UI specifically? 
		// Or they DID like seeing the text but not the CodeLens?
		// "we need to switch from codelense ... to something else".
		
		// Let's use a decoration that puts the text before the range, but format it nicely.
		// Since we can't do real multiline, we'll join with " ⏎ " to show it's multiple lines.
		const oneLineBlock = next.text.replace(/
?
/g, ' ⏎ ');
		decorationReplaceBlockType = vscode.window.createTextEditorDecorationType({
			before: {
				contentText: `[Replacement]: ${oneLineBlock}`,
				color: new vscode.ThemeColor('charts.purple'),
				fontStyle: 'italic',
				fontWeight: '600',
				backgroundColor: 'rgba(100, 0, 100, 0.15)',
				margin: '0 10px 0 0'
			}