sonance-brand-mcp 1.3.14 → 1.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,199 @@
1
+ "use client";
2
+
3
+ import React, { useState, useEffect } from "react";
4
+ import { X, Eye, Loader2, Save, ChevronDown, ChevronRight, FileCode, AlertCircle } from "lucide-react";
5
+ import { cn } from "../../../lib/utils";
6
+ import { VisionPendingEdit, VisionFileModification } from "../types";
7
+
8
+ export interface VisionDiffPreviewProps {
9
+ pendingEdit: VisionPendingEdit;
10
+ onSave: () => void;
11
+ onCancel: () => void;
12
+ saveStatus?: "idle" | "saving" | "success" | "error";
13
+ }
14
+
15
+ function FileModificationCard({
16
+ modification,
17
+ isExpanded,
18
+ onToggle,
19
+ }: {
20
+ modification: VisionFileModification;
21
+ isExpanded: boolean;
22
+ onToggle: () => void;
23
+ }) {
24
+ return (
25
+ <div className="border border-gray-200 rounded bg-white overflow-hidden">
26
+ {/* File Header */}
27
+ <button
28
+ onClick={onToggle}
29
+ className="w-full flex items-center gap-2 p-2 hover:bg-gray-50 transition-colors text-left"
30
+ >
31
+ {isExpanded ? (
32
+ <ChevronDown className="h-3.5 w-3.5 text-gray-400 flex-shrink-0" />
33
+ ) : (
34
+ <ChevronRight className="h-3.5 w-3.5 text-gray-400 flex-shrink-0" />
35
+ )}
36
+ <FileCode className="h-3.5 w-3.5 text-purple-500 flex-shrink-0" />
37
+ <span className="text-xs font-mono text-gray-700 truncate flex-1">
38
+ {modification.filePath}
39
+ </span>
40
+ </button>
41
+
42
+ {/* Explanation */}
43
+ <div className="px-2 pb-2">
44
+ <p className="text-[10px] text-gray-500">{modification.explanation}</p>
45
+ </div>
46
+
47
+ {/* Expanded Diff */}
48
+ {isExpanded && (
49
+ <div className="border-t border-gray-100">
50
+ <div className="max-h-60 overflow-auto">
51
+ <pre className="p-2 text-[10px] font-mono whitespace-pre-wrap">
52
+ {modification.diff.split("\n").map((line, i) => (
53
+ <div
54
+ key={i}
55
+ className={cn(
56
+ line.startsWith("+") && !line.startsWith("@@")
57
+ ? "bg-green-50 text-green-700"
58
+ : line.startsWith("-") && !line.startsWith("@@")
59
+ ? "bg-red-50 text-red-700"
60
+ : line.startsWith("@@")
61
+ ? "bg-blue-50 text-blue-600 font-semibold"
62
+ : "text-gray-600"
63
+ )}
64
+ >
65
+ {line || " "}
66
+ </div>
67
+ ))}
68
+ </pre>
69
+ </div>
70
+ </div>
71
+ )}
72
+ </div>
73
+ );
74
+ }
75
+
76
+ export function VisionDiffPreview({
77
+ pendingEdit,
78
+ onSave,
79
+ onCancel,
80
+ saveStatus = "idle",
81
+ }: VisionDiffPreviewProps) {
82
+ const [expandedFiles, setExpandedFiles] = useState<Set<string>>(new Set());
83
+
84
+ // Expand first file by default
85
+ useEffect(() => {
86
+ if (pendingEdit.modifications.length > 0) {
87
+ setExpandedFiles(new Set([pendingEdit.modifications[0].filePath]));
88
+ }
89
+ }, [pendingEdit]);
90
+
91
+ const toggleFile = (filePath: string) => {
92
+ setExpandedFiles((prev) => {
93
+ const next = new Set(prev);
94
+ if (next.has(filePath)) {
95
+ next.delete(filePath);
96
+ } else {
97
+ next.add(filePath);
98
+ }
99
+ return next;
100
+ });
101
+ };
102
+
103
+ const fileCount = pendingEdit.modifications.length;
104
+
105
+ return (
106
+ <div className="space-y-3 p-3 rounded border border-purple-300 bg-purple-50">
107
+ {/* Header */}
108
+ <div className="flex items-center justify-between">
109
+ <div className="flex items-center gap-2">
110
+ <Eye className="h-4 w-4 text-purple-600" />
111
+ <span className="text-xs font-semibold text-gray-900">
112
+ Vision Mode Changes Ready
113
+ </span>
114
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-purple-200 text-purple-700 font-medium">
115
+ {fileCount} file{fileCount !== 1 ? "s" : ""}
116
+ </span>
117
+ </div>
118
+ <button
119
+ onClick={onCancel}
120
+ className="text-gray-400 hover:text-gray-600"
121
+ >
122
+ <X className="h-4 w-4" />
123
+ </button>
124
+ </div>
125
+
126
+ {/* Overall Explanation */}
127
+ <p className="text-xs text-gray-600">{pendingEdit.explanation}</p>
128
+
129
+ {/* File Modifications List */}
130
+ <div className="space-y-2 max-h-80 overflow-y-auto">
131
+ {pendingEdit.modifications.map((mod) => (
132
+ <FileModificationCard
133
+ key={mod.filePath}
134
+ modification={mod}
135
+ isExpanded={expandedFiles.has(mod.filePath)}
136
+ onToggle={() => toggleFile(mod.filePath)}
137
+ />
138
+ ))}
139
+ </div>
140
+
141
+ {/* Live Preview Indicator */}
142
+ <div className="flex items-center gap-2 p-2 rounded bg-purple-100 border border-purple-200">
143
+ <Eye className="h-3.5 w-3.5 text-purple-600" />
144
+ <span className="text-xs text-purple-700">
145
+ Live preview active - scroll to see changes on the page
146
+ </span>
147
+ </div>
148
+
149
+ {/* Warning about multiple files */}
150
+ {fileCount > 1 && (
151
+ <div className="flex items-start gap-2 p-2 rounded bg-amber-50 border border-amber-200">
152
+ <AlertCircle className="h-3.5 w-3.5 text-amber-600 mt-0.5 flex-shrink-0" />
153
+ <span className="text-xs text-amber-700">
154
+ Multiple files will be modified. Review each file before saving.
155
+ </span>
156
+ </div>
157
+ )}
158
+
159
+ {/* Action Buttons */}
160
+ <div className="flex gap-2">
161
+ {/* Save All Button */}
162
+ <button
163
+ onClick={onSave}
164
+ disabled={saveStatus === "saving"}
165
+ className={cn(
166
+ "flex-1 flex items-center justify-center gap-2 py-2.5",
167
+ "text-xs font-medium rounded transition-colors",
168
+ "bg-purple-600 text-white hover:bg-purple-700",
169
+ "disabled:opacity-50 disabled:cursor-not-allowed"
170
+ )}
171
+ >
172
+ {saveStatus === "saving" ? (
173
+ <>
174
+ <Loader2 className="h-3.5 w-3.5 animate-spin" />
175
+ Saving {fileCount} file{fileCount !== 1 ? "s" : ""}...
176
+ </>
177
+ ) : (
178
+ <>
179
+ <Save className="h-3.5 w-3.5" />
180
+ Save All Changes
181
+ </>
182
+ )}
183
+ </button>
184
+ {/* Cancel Button */}
185
+ <button
186
+ onClick={onCancel}
187
+ disabled={saveStatus === "saving"}
188
+ className={cn(
189
+ "px-4 py-2.5 text-xs font-medium rounded transition-colors",
190
+ "border border-gray-200 text-gray-600 hover:bg-gray-50",
191
+ "disabled:opacity-50 disabled:cursor-not-allowed"
192
+ )}
193
+ >
194
+ Cancel
195
+ </button>
196
+ </div>
197
+ </div>
198
+ );
199
+ }
@@ -0,0 +1,116 @@
1
+ "use client";
2
+
3
+ import React, { useEffect, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { Eye } from "lucide-react";
6
+
7
+ interface VisionModeBorderProps {
8
+ active: boolean;
9
+ focusedCount?: number;
10
+ }
11
+
12
+ export function VisionModeBorder({ active, focusedCount = 0 }: VisionModeBorderProps) {
13
+ const [mounted, setMounted] = useState(false);
14
+
15
+ useEffect(() => {
16
+ setMounted(true);
17
+ return () => setMounted(false);
18
+ }, []);
19
+
20
+ if (!active || !mounted) return null;
21
+
22
+ return createPortal(
23
+ <>
24
+ {/* CSS animation keyframes */}
25
+ <style>{`
26
+ @keyframes vision-pulse {
27
+ 0%, 100% { opacity: 1; }
28
+ 50% { opacity: 0.7; }
29
+ }
30
+ @keyframes vision-glow {
31
+ 0%, 100% {
32
+ box-shadow: inset 0 0 30px rgba(139, 92, 246, 0.3),
33
+ 0 0 30px rgba(139, 92, 246, 0.3);
34
+ }
35
+ 50% {
36
+ box-shadow: inset 0 0 50px rgba(139, 92, 246, 0.4),
37
+ 0 0 50px rgba(139, 92, 246, 0.4);
38
+ }
39
+ }
40
+ `}</style>
41
+
42
+ {/* Main border overlay */}
43
+ <div
44
+ data-vision-mode-border="true"
45
+ style={{
46
+ position: "fixed",
47
+ top: 0,
48
+ left: 0,
49
+ right: 0,
50
+ bottom: 0,
51
+ pointerEvents: "none",
52
+ zIndex: 9996, // Below DevTools (9999) but above content
53
+ border: "4px solid #8B5CF6",
54
+ boxShadow: "inset 0 0 30px rgba(139, 92, 246, 0.3), 0 0 30px rgba(139, 92, 246, 0.3)",
55
+ animation: "vision-pulse 2s ease-in-out infinite, vision-glow 2s ease-in-out infinite",
56
+ }}
57
+ >
58
+ {/* Corner indicator */}
59
+ <div
60
+ style={{
61
+ position: "absolute",
62
+ top: "12px",
63
+ left: "12px",
64
+ display: "flex",
65
+ alignItems: "center",
66
+ gap: "6px",
67
+ backgroundColor: "#8B5CF6",
68
+ color: "white",
69
+ padding: "6px 12px",
70
+ borderRadius: "6px",
71
+ fontSize: "12px",
72
+ fontWeight: 600,
73
+ fontFamily: "system-ui, -apple-system, sans-serif",
74
+ boxShadow: "0 2px 8px rgba(139, 92, 246, 0.4)",
75
+ }}
76
+ >
77
+ <Eye size={14} />
78
+ <span>Vision Mode</span>
79
+ {focusedCount > 0 && (
80
+ <span
81
+ style={{
82
+ backgroundColor: "rgba(255, 255, 255, 0.2)",
83
+ padding: "2px 6px",
84
+ borderRadius: "4px",
85
+ marginLeft: "4px",
86
+ }}
87
+ >
88
+ {focusedCount} focused
89
+ </span>
90
+ )}
91
+ </div>
92
+
93
+ {/* Instruction hint at bottom */}
94
+ <div
95
+ style={{
96
+ position: "absolute",
97
+ bottom: "12px",
98
+ left: "50%",
99
+ transform: "translateX(-50%)",
100
+ backgroundColor: "rgba(139, 92, 246, 0.9)",
101
+ color: "white",
102
+ padding: "8px 16px",
103
+ borderRadius: "8px",
104
+ fontSize: "13px",
105
+ fontFamily: "system-ui, -apple-system, sans-serif",
106
+ boxShadow: "0 2px 12px rgba(139, 92, 246, 0.5)",
107
+ whiteSpace: "nowrap",
108
+ }}
109
+ >
110
+ Click elements to focus AI attention, then describe your changes in the chat
111
+ </div>
112
+ </div>
113
+ </>,
114
+ document.body
115
+ );
116
+ }
@@ -0,0 +1,94 @@
1
+ import React from "react";
2
+ import { Check, ChevronDown } from "lucide-react";
3
+ import { cn } from "../../../lib/utils";
4
+ import { isLightColor } from "../../../lib/brand-system";
5
+
6
+ export function Section({
7
+ title,
8
+ children,
9
+ }: {
10
+ title: string;
11
+ children: React.ReactNode;
12
+ }) {
13
+ return (
14
+ <div className="space-y-2">
15
+ <h3 id="section-h3-title" className="text-xs font-medium uppercase tracking-wide text-gray-400">
16
+ {title}
17
+ </h3>
18
+ {children}
19
+ </div>
20
+ );
21
+ }
22
+
23
+ export function ColorSwatch({
24
+ color,
25
+ name,
26
+ selected,
27
+ onClick,
28
+ }: {
29
+ color: string;
30
+ name: string;
31
+ selected: boolean;
32
+ onClick: () => void;
33
+ }) {
34
+ return (
35
+ <button
36
+ onClick={onClick}
37
+ className={cn(
38
+ "relative h-7 w-7 rounded-full transition-all duration-150",
39
+ "focus:outline-none focus:ring-2 focus:ring-[#00A3E1] focus:ring-offset-1",
40
+ "focus:ring-offset-1",
41
+ selected && "ring-2 ring-[#333F48] ring-offset-2"
42
+ )}
43
+ style={{ backgroundColor: color }}
44
+ aria-label={name}
45
+ title={name}
46
+ >
47
+ {selected && (
48
+ <Check
49
+ className="absolute inset-0 m-auto h-3.5 w-3.5"
50
+ style={{
51
+ color: isLightColor(color) ? "#000" : "#fff",
52
+ }}
53
+ />
54
+ )}
55
+ </button>
56
+ );
57
+ }
58
+
59
+ export function SelectField({
60
+ label,
61
+ value,
62
+ options,
63
+ onChange,
64
+ }: {
65
+ label: string;
66
+ value: string;
67
+ options: { value: string; label: string }[];
68
+ onChange: (value: string) => void;
69
+ }) {
70
+ return (
71
+ <div className="flex items-center justify-between">
72
+ <span id="select-field-span-label" className="text-xs text-gray-500">{label}</span>
73
+ <div className="relative">
74
+ <select
75
+ value={value}
76
+ onChange={(e) => onChange(e.target.value)}
77
+ className={cn(
78
+ "h-7 pl-2 pr-7 text-xs font-medium rounded",
79
+ "border border-gray-200 bg-white text-gray-700",
80
+ "focus:outline-none focus:ring-1 focus:ring-[#00A3E1]",
81
+ "cursor-pointer appearance-none"
82
+ )}
83
+ >
84
+ {options.map((opt) => (
85
+ <option key={opt.value} value={opt.value}>
86
+ {opt.label}
87
+ </option>
88
+ ))}
89
+ </select>
90
+ <ChevronDown className="absolute right-2 top-1/2 -translate-y-1/2 h-3 w-3 text-gray-400 pointer-events-none" />
91
+ </div>
92
+ </div>
93
+ );
94
+ }