Compare commits

..

No commits in common. "4734c809d75538d24508a9d1f9f681d164c07db1" and "7f2bd8bb82c52e46fc3d6a2470b325c1f1f70d58" have entirely different histories.

4 changed files with 61 additions and 60 deletions

View File

@ -6,11 +6,13 @@ import { Button } from '@/components/ui/button';
interface CodeBlockProps { interface CodeBlockProps {
code: string; code: string;
type: 'bash' | 'sql' | 'text' | 'branch'; type: 'bash' | 'sql' | 'text';
showLineNumbers?: boolean; showLineNumbers?: boolean;
} }
export function CodeBlock({ code, type, showLineNumbers = true }: CodeBlockProps) { export function CodeBlock({ code, type, showLineNumbers = true }: CodeBlockProps) {
console.log('[CodeBlock] Render - code length:', code?.length, 'code preview:', code?.substring(0, 50), 'type:', type);
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
// Compute highlighted code synchronously // Compute highlighted code synchronously
@ -28,14 +30,17 @@ export function CodeBlock({ code, type, showLineNumbers = true }: CodeBlockProps
require('prismjs/components/prism-bash'); require('prismjs/components/prism-bash');
} }
const language = type === 'text' || type === 'branch' ? 'text' : type; const language = type === 'text' ? 'text' : type;
const grammar = Prism.languages[language] || Prism.languages.text; const grammar = Prism.languages[language] || Prism.languages.text;
return Prism.highlight(code, grammar, language); return Prism.highlight(code, grammar, language);
} catch (e) { } catch (e) {
console.error('[CodeBlock] Prism error:', e);
return code.replace(/</g, '&lt;').replace(/>/g, '&gt;'); return code.replace(/</g, '&lt;').replace(/>/g, '&gt;');
} }
}, [code, type]); }, [code, type]);
console.log('[CodeBlock] highlighted length:', highlighted?.length);
const handleCopy = async () => { const handleCopy = async () => {
await navigator.clipboard.writeText(code); await navigator.clipboard.writeText(code);
setCopied(true); setCopied(true);
@ -47,37 +52,48 @@ export function CodeBlock({ code, type, showLineNumbers = true }: CodeBlockProps
const highlightedLines = highlighted ? highlighted.split('\n') : []; const highlightedLines = highlighted ? highlighted.split('\n') : [];
return ( return (
<div <div className="relative group">
className="rounded-lg bg-slate-900 p-4 text-sm overflow-x-auto" <Button
style={{ maxWidth: '100%' }} variant="ghost"
> size="sm"
<pre className={`language-${type} m-0`} style={{ minWidth: '100%' }}> className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10"
{showLineNumbers ? ( onClick={handleCopy}
<div className="flex" style={{ minWidth: 0 }}> >
{/* Line numbers column - fixed width */} {copied ? (
<div className="flex flex-col text-slate-500 text-right pr-4 select-none w-12 shrink-0"> <Check className="w-4 h-4 text-green-500" />
{lines.map((_: string, i: number) => (
<span key={i}>{i + 1}</span>
))}
</div>
{/* Code column */}
<div className="flex flex-col" style={{ minWidth: 0 }}>
{highlightedLines.map((line: string, i: number) => (
<span
key={i}
className="text-slate-100 whitespace-pre"
dangerouslySetInnerHTML={{ __html: line || ' ' }}
/>
))}
</div>
</div>
) : ( ) : (
<code <Copy className="w-4 h-4" />
className="text-slate-100 whitespace-pre"
dangerouslySetInnerHTML={{ __html: highlighted || code || '' }}
/>
)} )}
</pre> </Button>
<div className="rounded-lg bg-slate-900 p-4 overflow-x-auto text-sm">
<pre className={`language-${type} m-0`}>
{showLineNumbers ? (
<div className="flex">
{/* Line numbers column */}
<div className="flex flex-col text-slate-500 text-right pr-4 select-none min-w-[3rem]">
{lines.map((_: string, i: number) => (
<span key={i}>{i + 1}</span>
))}
</div>
{/* Code column */}
<div className="flex flex-col">
{highlightedLines.map((line: string, i: number) => (
<span
key={i}
className="text-slate-100 whitespace-pre"
dangerouslySetInnerHTML={{ __html: line || ' ' }}
/>
))}
</div>
</div>
) : (
<code
className="text-slate-100 whitespace-pre"
dangerouslySetInnerHTML={{ __html: highlighted || code || '' }}
/>
)}
</pre>
</div>
</div> </div>
); );
} }

View File

@ -73,7 +73,13 @@ export function StepDetailPanel({
// Sync state when step changes // Sync state when step changes
useEffect(() => { useEffect(() => {
console.log('[StepDetailPanel] useEffect triggered, step?.id:', step?.id);
if (step) { if (step) {
console.log('[StepDetailPanel] Syncing state with step:', {
stepId: step.id,
stepName: step.name,
stepContent: step.content?.substring(0, 50),
});
setNotes(step.notes || ''); setNotes(step.notes || '');
setEditContent(step.content || ''); setEditContent(step.content || '');
setIsEditing(false); setIsEditing(false);
@ -83,6 +89,8 @@ export function StepDetailPanel({
} }
}, [step?.id]); }, [step?.id]);
console.log('[StepDetailPanel] Render - step:', step?.id, 'step.content:', step?.content?.substring(0, 50), 'editContent:', editContent?.substring(0, 50));
if (!step) return null; if (!step) return null;
const isCustom = step.isCustom; const isCustom = step.isCustom;
@ -181,7 +189,7 @@ export function StepDetailPanel({
return ( return (
<Sheet open={isOpen} onOpenChange={onClose}> <Sheet open={isOpen} onOpenChange={onClose}>
<SheetContent key={step?.id} className="w-[800px] sm:max-w-[800px] overflow-hidden"> <SheetContent key={step?.id} className="w-[600px] sm:max-w-[600px]">
<SheetHeader className="px-6"> <SheetHeader className="px-6">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div> <div>
@ -197,9 +205,8 @@ export function StepDetailPanel({
</div> </div>
</SheetHeader> </SheetHeader>
<div style={{ width: '752px', maxWidth: '752px' }}> <ScrollArea className="h-[calc(100vh-180px)] mt-6 px-6">
<ScrollArea className="h-[calc(100vh-180px)] mt-6 px-6 w-full"> <div className="space-y-6">
<div className="space-y-6" style={{ width: '704px', maxWidth: '704px' }}>
{/* Status & Type */} {/* Status & Type */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -219,7 +226,7 @@ export function StepDetailPanel({
</div> </div>
{/* Content */} {/* Content */}
<div className="min-w-0" style={{ maxWidth: '100%' }}> <div>
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<label className="text-sm font-medium text-slate-500">Content</label> <label className="text-sm font-medium text-slate-500">Content</label>
{!isEditing && ( {!isEditing && (
@ -256,9 +263,7 @@ export function StepDetailPanel({
</div> </div>
</div> </div>
) : ( ) : (
<div className="overflow-hidden" style={{ maxWidth: '100%', width: '100%' }}> <CodeBlock code={step.content} type={step.type} />
<CodeBlock code={step.content} type={step.type} />
</div>
)} )}
{/* Show original content if overridden */} {/* Show original content if overridden */}
@ -370,8 +375,7 @@ export function StepDetailPanel({
</> </>
)} )}
</div> </div>
</ScrollArea> </ScrollArea>
</div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
); );

View File

@ -19,7 +19,6 @@ function ScrollArea({
<ScrollAreaPrimitive.Viewport <ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport" data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1" className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
style={{ maxWidth: '100%' }}
> >
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>

View File

@ -82,23 +82,6 @@ export async function reorderSteps(
category: StepCategory, category: StepCategory,
orderedIds: number[] orderedIds: number[]
) { ) {
// Two-phase update to avoid unique constraint violations
// Phase 1: Move all steps to temporary high values (offset by 10000)
for (let i = 0; i < orderedIds.length; i++) {
await db.update(stepTemplates)
.set({ orderIndex: i + 10000 })
.where(eq(stepTemplates.id, orderedIds[i]));
// Also update customer steps
await db.update(customerSteps)
.set({ orderIndex: i + 10000 })
.where(and(
eq(customerSteps.templateId, orderedIds[i]),
eq(customerSteps.category, category)
));
}
// Phase 2: Move to final positions
for (let i = 0; i < orderedIds.length; i++) { for (let i = 0; i < orderedIds.length; i++) {
await db.update(stepTemplates) await db.update(stepTemplates)
.set({ orderIndex: i }) .set({ orderIndex: i })
@ -112,7 +95,6 @@ export async function reorderSteps(
eq(customerSteps.category, category) eq(customerSteps.category, category)
)); ));
} }
revalidatePath(`/releases/${releaseId}/steps`); revalidatePath(`/releases/${releaseId}/steps`);
} }