@usesidekick/react 0.1.0
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 +246 -0
- package/dist/index.d.mts +358 -0
- package/dist/index.d.ts +358 -0
- package/dist/index.js +2470 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2403 -0
- package/dist/index.mjs.map +1 -0
- package/dist/jsx-dev-runtime.d.mts +21 -0
- package/dist/jsx-dev-runtime.d.ts +21 -0
- package/dist/jsx-dev-runtime.js +160 -0
- package/dist/jsx-dev-runtime.js.map +1 -0
- package/dist/jsx-dev-runtime.mjs +122 -0
- package/dist/jsx-dev-runtime.mjs.map +1 -0
- package/dist/jsx-runtime.d.mts +26 -0
- package/dist/jsx-runtime.d.ts +26 -0
- package/dist/jsx-runtime.js +150 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/jsx-runtime.mjs +109 -0
- package/dist/jsx-runtime.mjs.map +1 -0
- package/dist/server/index.d.mts +235 -0
- package/dist/server/index.d.ts +235 -0
- package/dist/server/index.js +642 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +597 -0
- package/dist/server/index.mjs.map +1 -0
- package/package.json +64 -0
- package/src/components/SidekickPanel.tsx +868 -0
- package/src/components/index.ts +1 -0
- package/src/context.tsx +157 -0
- package/src/flags.ts +47 -0
- package/src/index.ts +71 -0
- package/src/jsx-dev-runtime.ts +138 -0
- package/src/jsx-runtime.ts +159 -0
- package/src/loader.ts +35 -0
- package/src/primitives/behavior.ts +70 -0
- package/src/primitives/data.ts +91 -0
- package/src/primitives/index.ts +3 -0
- package/src/primitives/ui.ts +268 -0
- package/src/provider.tsx +1264 -0
- package/src/runtime-loader.ts +106 -0
- package/src/server/drizzle-adapter.ts +53 -0
- package/src/server/drizzle-schema.ts +16 -0
- package/src/server/generate.ts +578 -0
- package/src/server/handler.ts +343 -0
- package/src/server/index.ts +20 -0
- package/src/server/storage.ts +1 -0
- package/src/server/types.ts +49 -0
- package/src/types.ts +295 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import type { NextRequest } from 'next/server';
|
|
4
|
+
import { NextResponse } from 'next/server';
|
|
5
|
+
import type { SidekickHandlerOptions, SidekickStorage } from './types';
|
|
6
|
+
import { callAI, validateCode } from './generate';
|
|
7
|
+
|
|
8
|
+
interface GenerateRequestBody {
|
|
9
|
+
request: string;
|
|
10
|
+
overrideId?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ToggleRequestBody {
|
|
14
|
+
overrideId: string;
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface DeleteRequestBody {
|
|
19
|
+
overrideId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getAction(req: NextRequest): string {
|
|
23
|
+
const url = new URL(req.url);
|
|
24
|
+
const segments = url.pathname.split('/').filter(Boolean);
|
|
25
|
+
// Last segment is the action: overrides, generate, toggle, delete
|
|
26
|
+
return segments[segments.length - 1] || '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function readSchema(schemaPath?: string): Promise<Record<string, unknown>> {
|
|
30
|
+
const candidates = schemaPath
|
|
31
|
+
? [schemaPath]
|
|
32
|
+
: [
|
|
33
|
+
path.join(process.cwd(), 'src/sidekick/schema.json'),
|
|
34
|
+
path.join(process.cwd(), 'sidekick/schema.json'),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
try {
|
|
39
|
+
const content = await fs.readFile(candidate, 'utf-8');
|
|
40
|
+
return JSON.parse(content);
|
|
41
|
+
} catch {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
throw new Error('Failed to read schema.json');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function safeParsePrimitives(primitives: string): string[] {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(primitives);
|
|
51
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function handleListOverrides(storage: SidekickStorage) {
|
|
58
|
+
const overrides = await storage.listOverrides();
|
|
59
|
+
return NextResponse.json(
|
|
60
|
+
{
|
|
61
|
+
success: true,
|
|
62
|
+
overrides: overrides.map((o) => ({
|
|
63
|
+
id: o.id,
|
|
64
|
+
name: o.name,
|
|
65
|
+
description: o.description,
|
|
66
|
+
version: o.version,
|
|
67
|
+
primitives: safeParsePrimitives(o.primitives),
|
|
68
|
+
code: o.code,
|
|
69
|
+
enabled: o.enabled,
|
|
70
|
+
createdAt: o.createdAt,
|
|
71
|
+
updatedAt: o.updatedAt,
|
|
72
|
+
})),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
headers: {
|
|
76
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function safeParseBody<T>(req: NextRequest): Promise<T | null> {
|
|
83
|
+
try {
|
|
84
|
+
return (await req.json()) as T;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function handleGenerate(
|
|
91
|
+
req: NextRequest,
|
|
92
|
+
storage: SidekickStorage,
|
|
93
|
+
schemaPath?: string
|
|
94
|
+
) {
|
|
95
|
+
const body = await safeParseBody<GenerateRequestBody>(req);
|
|
96
|
+
if (!body) {
|
|
97
|
+
return NextResponse.json(
|
|
98
|
+
{ success: false, error: 'Invalid JSON in request body' },
|
|
99
|
+
{ status: 400 }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const { request, overrideId } = body;
|
|
103
|
+
|
|
104
|
+
if (!request || typeof request !== 'string') {
|
|
105
|
+
return NextResponse.json(
|
|
106
|
+
{ success: false, error: 'Missing request parameter' },
|
|
107
|
+
{ status: 400 }
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let existingCode: string | undefined;
|
|
112
|
+
if (overrideId) {
|
|
113
|
+
const existing = await storage.getOverride(overrideId);
|
|
114
|
+
if (!existing) {
|
|
115
|
+
return NextResponse.json(
|
|
116
|
+
{ success: false, error: `Override "${overrideId}" not found` },
|
|
117
|
+
{ status: 404 }
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
existingCode = existing.code;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
124
|
+
if (!apiKey) {
|
|
125
|
+
return NextResponse.json(
|
|
126
|
+
{ success: false, error: 'ANTHROPIC_API_KEY not configured' },
|
|
127
|
+
{ status: 500 }
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let schema: Record<string, unknown>;
|
|
132
|
+
try {
|
|
133
|
+
schema = await readSchema(schemaPath);
|
|
134
|
+
} catch {
|
|
135
|
+
return NextResponse.json(
|
|
136
|
+
{ success: false, error: 'Failed to read schema.json' },
|
|
137
|
+
{ status: 500 }
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const MAX_RETRIES = 3;
|
|
142
|
+
let lastError: string | undefined;
|
|
143
|
+
let lastValidationErrors: string[] | undefined;
|
|
144
|
+
|
|
145
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
146
|
+
try {
|
|
147
|
+
const generated = await callAI(request, schema, apiKey, lastValidationErrors, existingCode);
|
|
148
|
+
|
|
149
|
+
if (overrideId) {
|
|
150
|
+
generated.manifest.id = overrideId;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const validation = validateCode(generated.code);
|
|
154
|
+
|
|
155
|
+
if (!validation.valid) {
|
|
156
|
+
lastValidationErrors = validation.errors;
|
|
157
|
+
lastError = `Validation failed: ${validation.errors.join(', ')}`;
|
|
158
|
+
|
|
159
|
+
if (attempt < MAX_RETRIES) {
|
|
160
|
+
console.log(`[Sidekick] Attempt ${attempt} failed validation, retrying...`);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return NextResponse.json({
|
|
165
|
+
success: false,
|
|
166
|
+
error: lastError,
|
|
167
|
+
validationErrors: lastValidationErrors,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const existing = await storage.getOverride(generated.manifest.id);
|
|
172
|
+
|
|
173
|
+
if (existing) {
|
|
174
|
+
await storage.updateOverride(generated.manifest.id, {
|
|
175
|
+
name: generated.manifest.name,
|
|
176
|
+
description: generated.manifest.description,
|
|
177
|
+
version: generated.manifest.version,
|
|
178
|
+
primitives: JSON.stringify(generated.manifest.primitives),
|
|
179
|
+
code: generated.code,
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
await storage.createOverride({
|
|
183
|
+
id: generated.manifest.id,
|
|
184
|
+
name: generated.manifest.name,
|
|
185
|
+
description: generated.manifest.description,
|
|
186
|
+
version: generated.manifest.version,
|
|
187
|
+
primitives: JSON.stringify(generated.manifest.primitives),
|
|
188
|
+
code: generated.code,
|
|
189
|
+
enabled: true,
|
|
190
|
+
createdAt: new Date(),
|
|
191
|
+
updatedAt: new Date(),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return NextResponse.json({
|
|
196
|
+
success: true,
|
|
197
|
+
overrideId: generated.manifest.id,
|
|
198
|
+
name: generated.manifest.name,
|
|
199
|
+
description: generated.manifest.description,
|
|
200
|
+
});
|
|
201
|
+
} catch (e) {
|
|
202
|
+
lastError = e instanceof Error ? e.message : String(e);
|
|
203
|
+
if (attempt === MAX_RETRIES) {
|
|
204
|
+
return NextResponse.json({
|
|
205
|
+
success: false,
|
|
206
|
+
error: `Failed after ${MAX_RETRIES} attempts: ${lastError}`,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return NextResponse.json({
|
|
213
|
+
success: false,
|
|
214
|
+
error: lastError || 'Unknown error',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function handleToggle(req: NextRequest, storage: SidekickStorage) {
|
|
219
|
+
const body = await safeParseBody<ToggleRequestBody>(req);
|
|
220
|
+
if (!body) {
|
|
221
|
+
return NextResponse.json(
|
|
222
|
+
{ success: false, error: 'Invalid JSON in request body' },
|
|
223
|
+
{ status: 400 }
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const { overrideId, enabled } = body;
|
|
227
|
+
|
|
228
|
+
if (!overrideId || typeof overrideId !== 'string') {
|
|
229
|
+
return NextResponse.json(
|
|
230
|
+
{ success: false, error: 'Missing overrideId parameter' },
|
|
231
|
+
{ status: 400 }
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (typeof enabled !== 'boolean') {
|
|
236
|
+
return NextResponse.json(
|
|
237
|
+
{ success: false, error: 'Missing or invalid enabled parameter' },
|
|
238
|
+
{ status: 400 }
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const existing = await storage.getOverride(overrideId);
|
|
243
|
+
if (!existing) {
|
|
244
|
+
return NextResponse.json(
|
|
245
|
+
{ success: false, error: 'Override not found' },
|
|
246
|
+
{ status: 404 }
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
await storage.updateOverride(overrideId, { enabled });
|
|
251
|
+
|
|
252
|
+
// Re-read to confirm persisted value
|
|
253
|
+
const updated = await storage.getOverride(overrideId);
|
|
254
|
+
|
|
255
|
+
return NextResponse.json({
|
|
256
|
+
success: true,
|
|
257
|
+
overrideId,
|
|
258
|
+
enabled: updated?.enabled ?? enabled,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function handleDelete(req: NextRequest, storage: SidekickStorage) {
|
|
263
|
+
const body = await safeParseBody<DeleteRequestBody>(req);
|
|
264
|
+
if (!body) {
|
|
265
|
+
return NextResponse.json(
|
|
266
|
+
{ success: false, error: 'Invalid JSON in request body' },
|
|
267
|
+
{ status: 400 }
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
const { overrideId } = body;
|
|
271
|
+
|
|
272
|
+
if (!overrideId || typeof overrideId !== 'string') {
|
|
273
|
+
return NextResponse.json(
|
|
274
|
+
{ success: false, error: 'Missing overrideId parameter' },
|
|
275
|
+
{ status: 400 }
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const existing = await storage.getOverride(overrideId);
|
|
280
|
+
if (!existing) {
|
|
281
|
+
return NextResponse.json(
|
|
282
|
+
{ success: false, error: 'Override not found' },
|
|
283
|
+
{ status: 404 }
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await storage.deleteOverride(overrideId);
|
|
288
|
+
|
|
289
|
+
return NextResponse.json({
|
|
290
|
+
success: true,
|
|
291
|
+
message: `Override "${overrideId}" deleted successfully`,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function createSidekickHandler(options: SidekickHandlerOptions) {
|
|
296
|
+
const { storage, schemaPath } = options;
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
async GET(req: NextRequest): Promise<NextResponse> {
|
|
300
|
+
try {
|
|
301
|
+
const action = getAction(req);
|
|
302
|
+
if (action === 'overrides') {
|
|
303
|
+
return handleListOverrides(storage);
|
|
304
|
+
}
|
|
305
|
+
return NextResponse.json(
|
|
306
|
+
{ success: false, error: `Unknown GET action: ${action}` },
|
|
307
|
+
{ status: 404 }
|
|
308
|
+
);
|
|
309
|
+
} catch (e) {
|
|
310
|
+
console.error('[Sidekick] Handler error:', e);
|
|
311
|
+
return NextResponse.json(
|
|
312
|
+
{ success: false, error: e instanceof Error ? e.message : 'Unknown error' },
|
|
313
|
+
{ status: 500 }
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
async POST(req: NextRequest): Promise<NextResponse> {
|
|
319
|
+
try {
|
|
320
|
+
const action = getAction(req);
|
|
321
|
+
switch (action) {
|
|
322
|
+
case 'generate':
|
|
323
|
+
return handleGenerate(req, storage, schemaPath);
|
|
324
|
+
case 'toggle':
|
|
325
|
+
return handleToggle(req, storage);
|
|
326
|
+
case 'delete':
|
|
327
|
+
return handleDelete(req, storage);
|
|
328
|
+
default:
|
|
329
|
+
return NextResponse.json(
|
|
330
|
+
{ success: false, error: `Unknown POST action: ${action}` },
|
|
331
|
+
{ status: 404 }
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
} catch (e) {
|
|
335
|
+
console.error('[Sidekick] Handler error:', e);
|
|
336
|
+
return NextResponse.json(
|
|
337
|
+
{ success: false, error: e instanceof Error ? e.message : 'Unknown error' },
|
|
338
|
+
{ status: 500 }
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { createSidekickHandler } from './handler';
|
|
2
|
+
export { createDrizzleStorage } from './drizzle-adapter';
|
|
3
|
+
export { sidekickOverrides } from './drizzle-schema';
|
|
4
|
+
export type { SidekickOverride, NewSidekickOverride } from './drizzle-schema';
|
|
5
|
+
export type {
|
|
6
|
+
SidekickStorage,
|
|
7
|
+
SidekickHandlerOptions,
|
|
8
|
+
OverrideRecord,
|
|
9
|
+
NewOverrideRecord,
|
|
10
|
+
OverrideManifest,
|
|
11
|
+
GeneratedOverride,
|
|
12
|
+
} from './types';
|
|
13
|
+
export {
|
|
14
|
+
buildSystemPrompt,
|
|
15
|
+
buildUserPrompt,
|
|
16
|
+
callAI,
|
|
17
|
+
parseAIResponse,
|
|
18
|
+
validateCode,
|
|
19
|
+
formatDesignSystem,
|
|
20
|
+
} from './generate';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { SidekickStorage, OverrideRecord, NewOverrideRecord } from './types';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface OverrideRecord {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
version: string;
|
|
6
|
+
primitives: string; // JSON array stored as text
|
|
7
|
+
code: string;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
createdAt: Date;
|
|
10
|
+
updatedAt: Date;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface NewOverrideRecord {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
version: string;
|
|
18
|
+
primitives: string;
|
|
19
|
+
code: string;
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
createdAt?: Date;
|
|
22
|
+
updatedAt?: Date;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface OverrideManifest {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
description: string;
|
|
29
|
+
version: string;
|
|
30
|
+
primitives: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GeneratedOverride {
|
|
34
|
+
manifest: OverrideManifest;
|
|
35
|
+
code: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SidekickHandlerOptions {
|
|
39
|
+
storage: SidekickStorage;
|
|
40
|
+
schemaPath?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SidekickStorage {
|
|
44
|
+
listOverrides(): Promise<OverrideRecord[]>;
|
|
45
|
+
getOverride(id: string): Promise<OverrideRecord | null>;
|
|
46
|
+
createOverride(data: NewOverrideRecord): Promise<void>;
|
|
47
|
+
updateOverride(id: string, data: Partial<OverrideRecord>): Promise<void>;
|
|
48
|
+
deleteOverride(id: string): Promise<void>;
|
|
49
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface Override {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
version: string;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface OverrideManifest {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
version: string;
|
|
16
|
+
primitives: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OverrideModule {
|
|
20
|
+
manifest: OverrideManifest;
|
|
21
|
+
activate: (sdk: SDK) => void | Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface WrappedComponent {
|
|
25
|
+
id: string;
|
|
26
|
+
overrideId: string;
|
|
27
|
+
wrapper: (Component: ComponentType) => ComponentType;
|
|
28
|
+
priority?: number;
|
|
29
|
+
where?: (props: Record<string, unknown>) => boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AddedColumn {
|
|
33
|
+
id: string;
|
|
34
|
+
overrideId: string;
|
|
35
|
+
tableId: string;
|
|
36
|
+
header: string;
|
|
37
|
+
accessor: string | ((row: Record<string, unknown>) => unknown);
|
|
38
|
+
render?: (value: unknown, row: Record<string, unknown>) => ReactNode;
|
|
39
|
+
priority?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ColumnRename {
|
|
43
|
+
id: string;
|
|
44
|
+
overrideId: string;
|
|
45
|
+
tableId: string;
|
|
46
|
+
originalHeader: string;
|
|
47
|
+
newHeader: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface HiddenColumn {
|
|
51
|
+
id: string;
|
|
52
|
+
overrideId: string;
|
|
53
|
+
tableId: string;
|
|
54
|
+
header: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ColumnOrder {
|
|
58
|
+
id: string;
|
|
59
|
+
overrideId: string;
|
|
60
|
+
tableId: string;
|
|
61
|
+
order: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface RowFilter {
|
|
65
|
+
id: string;
|
|
66
|
+
overrideId: string;
|
|
67
|
+
tableId: string;
|
|
68
|
+
filter: (row: Record<string, unknown>) => boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface MenuItem {
|
|
72
|
+
id: string;
|
|
73
|
+
overrideId: string;
|
|
74
|
+
menuId: string;
|
|
75
|
+
label: string;
|
|
76
|
+
icon?: string;
|
|
77
|
+
onClick: () => void;
|
|
78
|
+
priority?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface TabItem {
|
|
82
|
+
id: string;
|
|
83
|
+
overrideId: string;
|
|
84
|
+
tabGroupId: string;
|
|
85
|
+
label: string;
|
|
86
|
+
content: ComponentType<Record<string, unknown>>;
|
|
87
|
+
priority?: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface PropsModifier {
|
|
91
|
+
id: string;
|
|
92
|
+
overrideId: string;
|
|
93
|
+
componentId: string;
|
|
94
|
+
modifier: (props: Record<string, unknown>) => Record<string, unknown>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ActionItem {
|
|
98
|
+
id: string;
|
|
99
|
+
overrideId: string;
|
|
100
|
+
actionBarId: string;
|
|
101
|
+
label: string;
|
|
102
|
+
icon?: string;
|
|
103
|
+
onClick: (context: Record<string, unknown>) => void;
|
|
104
|
+
variant?: 'primary' | 'secondary' | 'danger';
|
|
105
|
+
priority?: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ValidationRule {
|
|
109
|
+
id: string;
|
|
110
|
+
overrideId: string;
|
|
111
|
+
formId: string;
|
|
112
|
+
fieldName: string;
|
|
113
|
+
validate: (value: unknown, formData: Record<string, unknown>) => string | null;
|
|
114
|
+
priority?: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ApiInterceptor {
|
|
118
|
+
id: string;
|
|
119
|
+
overrideId: string;
|
|
120
|
+
pathPattern: string | RegExp;
|
|
121
|
+
handler: (response: unknown, request: { path: string; method: string }) => unknown;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface SortOption {
|
|
125
|
+
id: string;
|
|
126
|
+
overrideId: string;
|
|
127
|
+
tableId: string;
|
|
128
|
+
label: string;
|
|
129
|
+
field: string;
|
|
130
|
+
comparator: (a: Record<string, unknown>, b: Record<string, unknown>) => number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface GroupByOption {
|
|
134
|
+
id: string;
|
|
135
|
+
overrideId: string;
|
|
136
|
+
tableId: string;
|
|
137
|
+
label: string;
|
|
138
|
+
grouper: (item: Record<string, unknown>) => string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface DOMModification {
|
|
142
|
+
id: string;
|
|
143
|
+
overrideId: string;
|
|
144
|
+
selector: string;
|
|
145
|
+
type: 'setText' | 'setAttribute' | 'setStyle' | 'addClass' | 'removeClass';
|
|
146
|
+
// setText: value = new text content
|
|
147
|
+
// setAttribute: value = { attr: string; value: string }
|
|
148
|
+
// setStyle: value = Record<string, string>
|
|
149
|
+
// addClass/removeClass: value = className string
|
|
150
|
+
value: unknown;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface InjectionPoint {
|
|
154
|
+
id: string;
|
|
155
|
+
overrideId: string;
|
|
156
|
+
selector: string;
|
|
157
|
+
position: 'before' | 'after' | 'prepend' | 'append';
|
|
158
|
+
component: ComponentType<Record<string, unknown>>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface DOMEventListener {
|
|
162
|
+
id: string;
|
|
163
|
+
overrideId: string;
|
|
164
|
+
selector: string;
|
|
165
|
+
eventType: string;
|
|
166
|
+
handler: (event: Event, element: Element) => void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface EventHandler {
|
|
170
|
+
id: string;
|
|
171
|
+
overrideId: string;
|
|
172
|
+
eventName: string;
|
|
173
|
+
handler: (payload: Record<string, unknown>) => void;
|
|
174
|
+
priority?: number;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface KeyboardShortcut {
|
|
178
|
+
id: string;
|
|
179
|
+
overrideId: string;
|
|
180
|
+
keys: string;
|
|
181
|
+
action: () => void;
|
|
182
|
+
description?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface RouteModifier {
|
|
186
|
+
id: string;
|
|
187
|
+
overrideId: string;
|
|
188
|
+
pathPattern: string | RegExp;
|
|
189
|
+
handler: (context: { path: string; params: Record<string, string> }) => void | string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface ComputedField {
|
|
193
|
+
id: string;
|
|
194
|
+
overrideId: string;
|
|
195
|
+
fieldName: string;
|
|
196
|
+
compute: (data: Record<string, unknown>) => unknown;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface DataFilter {
|
|
200
|
+
id: string;
|
|
201
|
+
overrideId: string;
|
|
202
|
+
name: string;
|
|
203
|
+
filter: (items: unknown[]) => unknown[];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface DataTransform {
|
|
207
|
+
id: string;
|
|
208
|
+
overrideId: string;
|
|
209
|
+
dataKey: string;
|
|
210
|
+
transform: (data: unknown) => unknown;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface InjectedStyles {
|
|
214
|
+
id: string;
|
|
215
|
+
overrideId: string;
|
|
216
|
+
css: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// SDK Primitives Interface
|
|
220
|
+
export interface UIPrimitives {
|
|
221
|
+
wrap: (componentName: string, wrapper: (Component: ComponentType) => ComponentType, options?: { priority?: number; where?: (props: Record<string, unknown>) => boolean }) => void;
|
|
222
|
+
replace: (componentName: string, replacement: ComponentType) => void;
|
|
223
|
+
addStyles: (css: string) => void;
|
|
224
|
+
addColumn: (tableId: string, config: Omit<AddedColumn, 'id' | 'overrideId' | 'tableId'>) => void;
|
|
225
|
+
renameColumn: (tableId: string, originalHeader: string, newHeader: string) => void;
|
|
226
|
+
hideColumn: (tableId: string, header: string) => void;
|
|
227
|
+
reorderColumns: (tableId: string, order: string[]) => void;
|
|
228
|
+
filterRows: (tableId: string, filter: (row: Record<string, unknown>) => boolean) => void;
|
|
229
|
+
addMenuItem: (menuId: string, config: Omit<MenuItem, 'id' | 'overrideId' | 'menuId'>) => void;
|
|
230
|
+
addTab: (tabGroupId: string, config: Omit<TabItem, 'id' | 'overrideId' | 'tabGroupId'>) => void;
|
|
231
|
+
modifyProps: (componentId: string, modifier: (props: Record<string, unknown>) => Record<string, unknown>) => void;
|
|
232
|
+
addAction: (actionBarId: string, config: Omit<ActionItem, 'id' | 'overrideId' | 'actionBarId'>) => void;
|
|
233
|
+
addValidation: (formId: string, fieldName: string, validate: (value: unknown, formData: Record<string, unknown>) => string | null) => void;
|
|
234
|
+
setText: (selector: string, text: string) => void;
|
|
235
|
+
setAttribute: (selector: string, attr: string, value: string) => void;
|
|
236
|
+
setStyle: (selector: string, styles: Record<string, string>) => void;
|
|
237
|
+
addClass: (selector: string, className: string) => void;
|
|
238
|
+
removeClass: (selector: string, className: string) => void;
|
|
239
|
+
inject: (selector: string, component: ComponentType<Record<string, unknown>>, position?: 'before' | 'after' | 'prepend' | 'append') => void;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface DataPrimitives {
|
|
243
|
+
computed: (fieldName: string, compute: (data: Record<string, unknown>) => unknown) => void;
|
|
244
|
+
addFilter: (name: string, filter: (items: unknown[]) => unknown[]) => void;
|
|
245
|
+
transform: (dataKey: string, transformFn: (data: unknown) => unknown) => void;
|
|
246
|
+
intercept: (pathPattern: string | RegExp, handler: (response: unknown, request: { path: string; method: string }) => unknown) => void;
|
|
247
|
+
addSortOption: (tableId: string, config: Omit<SortOption, 'id' | 'overrideId' | 'tableId'>) => void;
|
|
248
|
+
addGroupBy: (tableId: string, config: Omit<GroupByOption, 'id' | 'overrideId' | 'tableId'>) => void;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface BehaviorPrimitives {
|
|
252
|
+
onEvent: (eventName: string, handler: (payload: Record<string, unknown>) => void, options?: { priority?: number }) => void;
|
|
253
|
+
addKeyboardShortcut: (keys: string, action: () => void, description?: string) => void;
|
|
254
|
+
modifyRoute: (pathPattern: string | RegExp, handler: (context: { path: string; params: Record<string, string> }) => void | string) => void;
|
|
255
|
+
onDOMEvent: (selector: string, eventType: string, handler: (event: Event, element: Element) => void) => void;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface SDK {
|
|
259
|
+
ui: UIPrimitives;
|
|
260
|
+
data: DataPrimitives;
|
|
261
|
+
behavior: BehaviorPrimitives;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface SidekickState {
|
|
265
|
+
overrides: Override[];
|
|
266
|
+
// UI state
|
|
267
|
+
wrappers: Map<string, WrappedComponent[]>;
|
|
268
|
+
replacements: Map<string, ComponentType>;
|
|
269
|
+
styles: InjectedStyles[];
|
|
270
|
+
columns: Map<string, AddedColumn[]>;
|
|
271
|
+
columnRenames: Map<string, ColumnRename[]>;
|
|
272
|
+
hiddenColumns: Map<string, HiddenColumn[]>;
|
|
273
|
+
columnOrders: Map<string, ColumnOrder>;
|
|
274
|
+
rowFilters: Map<string, RowFilter[]>;
|
|
275
|
+
menuItems: Map<string, MenuItem[]>;
|
|
276
|
+
tabs: Map<string, TabItem[]>;
|
|
277
|
+
propsModifiers: Map<string, PropsModifier[]>;
|
|
278
|
+
actions: Map<string, ActionItem[]>;
|
|
279
|
+
validations: Map<string, ValidationRule[]>;
|
|
280
|
+
// Data state
|
|
281
|
+
computedFields: Map<string, ComputedField>;
|
|
282
|
+
filters: Map<string, DataFilter>;
|
|
283
|
+
transforms: Map<string, DataTransform>;
|
|
284
|
+
interceptors: ApiInterceptor[];
|
|
285
|
+
sortOptions: Map<string, SortOption[]>;
|
|
286
|
+
groupByOptions: Map<string, GroupByOption[]>;
|
|
287
|
+
// Behavior state
|
|
288
|
+
eventHandlers: Map<string, EventHandler[]>;
|
|
289
|
+
keyboardShortcuts: KeyboardShortcut[];
|
|
290
|
+
routeModifiers: RouteModifier[];
|
|
291
|
+
// DOM state
|
|
292
|
+
domModifications: DOMModification[];
|
|
293
|
+
injections: InjectionPoint[];
|
|
294
|
+
domEventListeners: DOMEventListener[];
|
|
295
|
+
}
|