fixdog 0.0.1
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.
- package/README.md +478 -0
- package/USAGE.md +77 -0
- package/dist/client/index.d.mts +110 -0
- package/dist/client/index.d.ts +110 -0
- package/dist/client/index.js +1601 -0
- package/dist/client/index.mjs +1582 -0
- package/dist/client/init.d.mts +67 -0
- package/dist/client/init.d.ts +67 -0
- package/dist/client/init.js +1609 -0
- package/dist/client/init.mjs +1593 -0
- package/dist/index.d.mts +158 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +1635 -0
- package/dist/index.mjs +1606 -0
- package/package.json +57 -0
- package/src/api/client.ts +141 -0
- package/src/client/index.ts +75 -0
- package/src/client/init.tsx +78 -0
- package/src/components/ConversationalInputReact.tsx +406 -0
- package/src/components/ElementInfoDisplayReact.tsx +84 -0
- package/src/components/UiDogSidebarReact.tsx +49 -0
- package/src/element-detector.ts +186 -0
- package/src/index.ts +228 -0
- package/src/instrument.ts +171 -0
- package/src/sidebar-initializer.ts +171 -0
- package/src/source-resolver.ts +121 -0
- package/src/styles/sidebarStyles.ts +597 -0
- package/src/types/css.d.ts +9 -0
- package/src/types/sidebar.ts +56 -0
- package/src/types.ts +119 -0
- package/tsconfig.json +23 -0
- package/tsup.config.ts +40 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar initializer - mounts the UiDog sidebar in a shadow DOM for style isolation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createRoot, Root } from "react-dom/client";
|
|
6
|
+
import { createElement } from "react";
|
|
7
|
+
import { UiDogSidebarReact } from "./components/UiDogSidebarReact";
|
|
8
|
+
import sidebarStyles from "./styles/sidebarStyles";
|
|
9
|
+
import type { ElementInfo, SidebarConfig } from "./types";
|
|
10
|
+
|
|
11
|
+
let sidebarRoot: Root | null = null;
|
|
12
|
+
let sidebarContainer: HTMLElement | null = null;
|
|
13
|
+
let shadowRoot: ShadowRoot | null = null;
|
|
14
|
+
let config: SidebarConfig = { apiEndpoint: "https://api.ui.dog" };
|
|
15
|
+
|
|
16
|
+
// Current sidebar state
|
|
17
|
+
let currentState: {
|
|
18
|
+
isOpen: boolean;
|
|
19
|
+
elementInfo: ElementInfo | null;
|
|
20
|
+
editorUrl: string;
|
|
21
|
+
} = {
|
|
22
|
+
isOpen: false,
|
|
23
|
+
elementInfo: null,
|
|
24
|
+
editorUrl: "",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initialize the sidebar with configuration
|
|
29
|
+
*/
|
|
30
|
+
export function initializeSidebar(sidebarConfig: SidebarConfig): void {
|
|
31
|
+
if (typeof window === "undefined") return;
|
|
32
|
+
|
|
33
|
+
config = sidebarConfig;
|
|
34
|
+
|
|
35
|
+
// Check if already initialized
|
|
36
|
+
if (document.getElementById("uidog-next-sidebar-root")) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Create container
|
|
41
|
+
sidebarContainer = document.createElement("div");
|
|
42
|
+
sidebarContainer.id = "uidog-next-sidebar-root";
|
|
43
|
+
document.body.appendChild(sidebarContainer);
|
|
44
|
+
|
|
45
|
+
// Create shadow DOM for style isolation
|
|
46
|
+
shadowRoot = sidebarContainer.attachShadow({ mode: "open" });
|
|
47
|
+
|
|
48
|
+
// Inject styles
|
|
49
|
+
const styleElement = document.createElement("style");
|
|
50
|
+
styleElement.textContent = sidebarStyles;
|
|
51
|
+
shadowRoot.appendChild(styleElement);
|
|
52
|
+
|
|
53
|
+
// Create mount point
|
|
54
|
+
const mountPoint = document.createElement("div");
|
|
55
|
+
mountPoint.id = "uidog-sidebar-mount";
|
|
56
|
+
shadowRoot.appendChild(mountPoint);
|
|
57
|
+
|
|
58
|
+
// Create React root
|
|
59
|
+
sidebarRoot = createRoot(mountPoint);
|
|
60
|
+
|
|
61
|
+
// Initial render (empty)
|
|
62
|
+
render();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Render the sidebar based on current state
|
|
67
|
+
*/
|
|
68
|
+
function render(): void {
|
|
69
|
+
if (!sidebarRoot) return;
|
|
70
|
+
|
|
71
|
+
const { isOpen, elementInfo, editorUrl } = currentState;
|
|
72
|
+
|
|
73
|
+
if (!isOpen || !elementInfo) {
|
|
74
|
+
sidebarRoot.render(null);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
sidebarRoot.render(
|
|
79
|
+
createElement(UiDogSidebarReact, {
|
|
80
|
+
elementInfo,
|
|
81
|
+
editorUrl,
|
|
82
|
+
onClose: closeSidebar,
|
|
83
|
+
apiEndpoint: config.apiEndpoint,
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Open the sidebar with element information
|
|
90
|
+
*/
|
|
91
|
+
export function openSidebar(elementInfo: ElementInfo, editorUrl: string): void {
|
|
92
|
+
currentState = {
|
|
93
|
+
isOpen: true,
|
|
94
|
+
elementInfo,
|
|
95
|
+
editorUrl,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Update global state for debugging
|
|
99
|
+
if (typeof window !== "undefined") {
|
|
100
|
+
window.__UIDOG_SIDEBAR__ = {
|
|
101
|
+
isOpen: true,
|
|
102
|
+
elementInfo,
|
|
103
|
+
editorUrl,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
render();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Close the sidebar
|
|
112
|
+
*/
|
|
113
|
+
export function closeSidebar(): void {
|
|
114
|
+
currentState = {
|
|
115
|
+
isOpen: false,
|
|
116
|
+
elementInfo: null,
|
|
117
|
+
editorUrl: "",
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Update global state
|
|
121
|
+
if (typeof window !== "undefined") {
|
|
122
|
+
window.__UIDOG_SIDEBAR__ = {
|
|
123
|
+
isOpen: false,
|
|
124
|
+
elementInfo: null,
|
|
125
|
+
editorUrl: null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
render();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if the sidebar is currently open
|
|
134
|
+
*/
|
|
135
|
+
export function isSidebarOpen(): boolean {
|
|
136
|
+
return currentState.isOpen;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get current sidebar state
|
|
141
|
+
*/
|
|
142
|
+
export function getSidebarState(): typeof currentState {
|
|
143
|
+
return { ...currentState };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Cleanup sidebar (for testing or unmounting)
|
|
148
|
+
*/
|
|
149
|
+
export function cleanupSidebar(): void {
|
|
150
|
+
if (sidebarRoot) {
|
|
151
|
+
sidebarRoot.unmount();
|
|
152
|
+
sidebarRoot = null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (sidebarContainer && sidebarContainer.parentNode) {
|
|
156
|
+
sidebarContainer.parentNode.removeChild(sidebarContainer);
|
|
157
|
+
sidebarContainer = null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
shadowRoot = null;
|
|
161
|
+
|
|
162
|
+
currentState = {
|
|
163
|
+
isOpen: false,
|
|
164
|
+
elementInfo: null,
|
|
165
|
+
editorUrl: "",
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (typeof window !== "undefined") {
|
|
169
|
+
delete window.__UIDOG_SIDEBAR__;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source resolver - builds editor URLs from source location information
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { SourceLocation, EditorType } from "./types";
|
|
6
|
+
|
|
7
|
+
const EDITOR_SCHEMES: Record<EditorType, string> = {
|
|
8
|
+
vscode: "vscode://file/{path}:{line}:{column}",
|
|
9
|
+
"vscode-insiders": "vscode-insiders://file/{path}:{line}:{column}",
|
|
10
|
+
cursor: "cursor://file/{path}:{line}:{column}",
|
|
11
|
+
webstorm: "webstorm://open?file={path}&line={line}&column={column}",
|
|
12
|
+
atom: "atom://core/open/file?filename={path}&line={line}&column={column}",
|
|
13
|
+
sublime: "subl://open?url=file://{path}&line={line}&column={column}",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build an editor URL from source location information
|
|
18
|
+
*/
|
|
19
|
+
export function buildEditorUrl(
|
|
20
|
+
source: SourceLocation,
|
|
21
|
+
editor: EditorType = "cursor",
|
|
22
|
+
projectPath: string = ""
|
|
23
|
+
): string {
|
|
24
|
+
const template = EDITOR_SCHEMES[editor] || EDITOR_SCHEMES.cursor;
|
|
25
|
+
|
|
26
|
+
// Normalize and build full path
|
|
27
|
+
let fullPath = normalizeFileName(source.fileName);
|
|
28
|
+
|
|
29
|
+
// If path is relative and we have a project path, make it absolute
|
|
30
|
+
if (projectPath && !fullPath.startsWith("/")) {
|
|
31
|
+
const normalizedProjectPath = projectPath.endsWith("/")
|
|
32
|
+
? projectPath.slice(0, -1)
|
|
33
|
+
: projectPath;
|
|
34
|
+
fullPath = `${normalizedProjectPath}/${fullPath}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return template
|
|
38
|
+
.replace("{path}", fullPath)
|
|
39
|
+
.replace("{line}", String(source.lineNumber || 1))
|
|
40
|
+
.replace("{column}", String(source.columnNumber || 1));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Normalize file name by removing bundler prefixes and query strings
|
|
45
|
+
*/
|
|
46
|
+
export function normalizeFileName(fileName: string): string {
|
|
47
|
+
if (!fileName) return "";
|
|
48
|
+
|
|
49
|
+
let normalized = fileName;
|
|
50
|
+
|
|
51
|
+
// Remove common bundler prefixes
|
|
52
|
+
const prefixPatterns = [
|
|
53
|
+
/^webpack:\/\/[^/]*\//,
|
|
54
|
+
/^webpack-internal:\/\/\//,
|
|
55
|
+
/^file:\/\//,
|
|
56
|
+
/^about:react/,
|
|
57
|
+
/^\.\//,
|
|
58
|
+
/^https?:\/\/localhost:\d+\//,
|
|
59
|
+
/^https?:\/\/[^/]+\/@fs\//,
|
|
60
|
+
/^https?:\/\/[^/]+\//,
|
|
61
|
+
/^\/@fs\//,
|
|
62
|
+
/^@fs\//,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
for (const pattern of prefixPatterns) {
|
|
66
|
+
normalized = normalized.replace(pattern, "");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Remove query strings and hash fragments
|
|
70
|
+
normalized = normalized.split("?")[0].split("#")[0];
|
|
71
|
+
|
|
72
|
+
// Remove leading slashes if duplicated
|
|
73
|
+
normalized = normalized.replace(/^\/+/, "/");
|
|
74
|
+
|
|
75
|
+
return normalized;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if a file name represents actual source code (not bundled/internal)
|
|
80
|
+
*/
|
|
81
|
+
export function isSourceFile(fileName: string): boolean {
|
|
82
|
+
if (!fileName) return false;
|
|
83
|
+
|
|
84
|
+
const normalized = normalizeFileName(fileName);
|
|
85
|
+
|
|
86
|
+
// Exclude patterns
|
|
87
|
+
const excludePatterns = [
|
|
88
|
+
/node_modules/,
|
|
89
|
+
/react-dom/,
|
|
90
|
+
/^react\//,
|
|
91
|
+
/\.next\//,
|
|
92
|
+
/_next\//,
|
|
93
|
+
/webpack/,
|
|
94
|
+
/@vite\//,
|
|
95
|
+
/vite\/client/,
|
|
96
|
+
/\[eval\]/,
|
|
97
|
+
/<anonymous>/,
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
for (const pattern of excludePatterns) {
|
|
101
|
+
if (pattern.test(normalized)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Include patterns (common source file extensions)
|
|
107
|
+
const includeExtensions = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
|
|
108
|
+
|
|
109
|
+
return includeExtensions.some((ext) =>
|
|
110
|
+
normalized.toLowerCase().endsWith(ext)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get the file name without path for display
|
|
116
|
+
*/
|
|
117
|
+
export function getShortFileName(filePath: string): string {
|
|
118
|
+
const normalized = normalizeFileName(filePath);
|
|
119
|
+
const parts = normalized.split("/");
|
|
120
|
+
return parts[parts.length - 1] || normalized;
|
|
121
|
+
}
|