@recursica/adapter-tester 1.1.0 → 2.0.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 +68 -78
- package/dist/adapter-tester.schema.json.d.ts +116 -0
- package/dist/cli.cjs +39 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +4923 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-B0Eop8Az.cjs.map +1 -1
- package/dist/{config-CKgyDyNF.js → config-CDxTeSAY.js} +2 -4
- package/dist/{config-CKgyDyNF.js.map → config-CDxTeSAY.js.map} +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/devServer.d.ts +8 -0
- package/dist/devServer.d.ts.map +1 -0
- package/dist/fileConfig.d.ts +60 -0
- package/dist/fileConfig.d.ts.map +1 -0
- package/dist/harness/mantineSourceOfTruth.d.ts.map +1 -1
- package/dist/index.cjs +1 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.html +29 -24
- package/dist/index.js +6 -117
- package/dist/index.js.map +1 -1
- package/dist/mantineSourceOfTruth-BiSfXKff.js +121 -0
- package/dist/mantineSourceOfTruth-BiSfXKff.js.map +1 -0
- package/dist/mantineSourceOfTruth-DCoJ2dU-.cjs +54 -0
- package/dist/mantineSourceOfTruth-DCoJ2dU-.cjs.map +1 -0
- package/dist/testing/runVisualRegression.d.ts.map +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +85 -81
- package/dist/testing.js.map +1 -1
- package/dist/validateFileConfig.d.ts +7 -0
- package/dist/validateFileConfig.d.ts.map +1 -0
- package/package.json +17 -7
- package/public/index.html +466 -0
- package/report-header.txt +8 -0
- package/src/adapter-tester.schema.json +113 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Recursica UI Kit - Adapter Dev Mode</title>
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
display: flex;
|
|
10
|
+
height: 100vh;
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
12
|
+
background-color: #1e1e1e;
|
|
13
|
+
color: #fff;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
.pane {
|
|
17
|
+
flex: 1;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
position: relative;
|
|
21
|
+
}
|
|
22
|
+
.header {
|
|
23
|
+
padding: 10px 15px;
|
|
24
|
+
background-color: #252526;
|
|
25
|
+
border-bottom: 1px solid #333;
|
|
26
|
+
font-size: 13px;
|
|
27
|
+
font-weight: 600;
|
|
28
|
+
text-transform: uppercase;
|
|
29
|
+
letter-spacing: 1px;
|
|
30
|
+
display: flex;
|
|
31
|
+
justify-content: space-between;
|
|
32
|
+
align-items: center;
|
|
33
|
+
}
|
|
34
|
+
.status {
|
|
35
|
+
font-size: 11px;
|
|
36
|
+
color: #aaa;
|
|
37
|
+
text-transform: none;
|
|
38
|
+
letter-spacing: normal;
|
|
39
|
+
font-weight: normal;
|
|
40
|
+
}
|
|
41
|
+
.status.connected {
|
|
42
|
+
color: #4CAF50;
|
|
43
|
+
}
|
|
44
|
+
iframe {
|
|
45
|
+
flex: 1;
|
|
46
|
+
border: none;
|
|
47
|
+
width: 100%;
|
|
48
|
+
background: white;
|
|
49
|
+
}
|
|
50
|
+
.divider {
|
|
51
|
+
width: 4px;
|
|
52
|
+
background: #333;
|
|
53
|
+
cursor: col-resize;
|
|
54
|
+
z-index: 10;
|
|
55
|
+
transition: background 0.2s;
|
|
56
|
+
}
|
|
57
|
+
.divider:hover {
|
|
58
|
+
background: #007acc;
|
|
59
|
+
}
|
|
60
|
+
.h-divider {
|
|
61
|
+
height: 4px;
|
|
62
|
+
background: #333;
|
|
63
|
+
cursor: row-resize;
|
|
64
|
+
z-index: 10;
|
|
65
|
+
transition: background 0.2s;
|
|
66
|
+
}
|
|
67
|
+
.h-divider:hover {
|
|
68
|
+
background: #007acc;
|
|
69
|
+
}
|
|
70
|
+
.right-top {
|
|
71
|
+
flex: 0 0 70%;
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
position: relative;
|
|
75
|
+
}
|
|
76
|
+
.right-bottom {
|
|
77
|
+
flex: 1;
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
position: relative;
|
|
81
|
+
background-color: #1e1e1e;
|
|
82
|
+
}
|
|
83
|
+
.notes-area {
|
|
84
|
+
flex: 1;
|
|
85
|
+
padding: 15px;
|
|
86
|
+
background: transparent;
|
|
87
|
+
border: none;
|
|
88
|
+
color: #d4d4d4;
|
|
89
|
+
font-family: monospace;
|
|
90
|
+
font-size: 13px;
|
|
91
|
+
resize: none;
|
|
92
|
+
outline: none;
|
|
93
|
+
}
|
|
94
|
+
.btn {
|
|
95
|
+
background: #0e639c;
|
|
96
|
+
color: white;
|
|
97
|
+
border: none;
|
|
98
|
+
padding: 4px 10px;
|
|
99
|
+
border-radius: 3px;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
font-size: 11px;
|
|
102
|
+
font-weight: bold;
|
|
103
|
+
text-transform: uppercase;
|
|
104
|
+
}
|
|
105
|
+
.btn:hover {
|
|
106
|
+
background: #1177bb;
|
|
107
|
+
}
|
|
108
|
+
/* Modal Styles */
|
|
109
|
+
.modal-overlay {
|
|
110
|
+
display: none;
|
|
111
|
+
position: fixed;
|
|
112
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
113
|
+
background: rgba(0,0,0,0.7);
|
|
114
|
+
z-index: 1000;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
align-items: center;
|
|
117
|
+
}
|
|
118
|
+
.modal-content {
|
|
119
|
+
background: #252526;
|
|
120
|
+
border: 1px solid #444;
|
|
121
|
+
width: 60%;
|
|
122
|
+
height: 70%;
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
border-radius: 6px;
|
|
126
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
|
127
|
+
}
|
|
128
|
+
.modal-header {
|
|
129
|
+
padding: 15px;
|
|
130
|
+
border-bottom: 1px solid #444;
|
|
131
|
+
display: flex;
|
|
132
|
+
justify-content: space-between;
|
|
133
|
+
align-items: center;
|
|
134
|
+
}
|
|
135
|
+
.modal-header h2 {
|
|
136
|
+
margin: 0;
|
|
137
|
+
font-size: 16px;
|
|
138
|
+
}
|
|
139
|
+
.close-btn {
|
|
140
|
+
background: transparent;
|
|
141
|
+
border: none;
|
|
142
|
+
color: #ccc;
|
|
143
|
+
font-size: 20px;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
}
|
|
146
|
+
.close-btn:hover { color: white; }
|
|
147
|
+
.report-area {
|
|
148
|
+
flex: 1;
|
|
149
|
+
padding: 15px;
|
|
150
|
+
background: #1e1e1e;
|
|
151
|
+
color: #d4d4d4;
|
|
152
|
+
border: none;
|
|
153
|
+
font-family: monospace;
|
|
154
|
+
resize: none;
|
|
155
|
+
outline: none;
|
|
156
|
+
}
|
|
157
|
+
.modal-footer {
|
|
158
|
+
padding: 10px 15px;
|
|
159
|
+
border-top: 1px solid #444;
|
|
160
|
+
display: flex;
|
|
161
|
+
justify-content: flex-end;
|
|
162
|
+
}
|
|
163
|
+
</style>
|
|
164
|
+
</head>
|
|
165
|
+
<body>
|
|
166
|
+
<div class="pane" id="leftPane">
|
|
167
|
+
<div class="header">
|
|
168
|
+
<span id="sourceOfTruthLabel">Source of Truth</span>
|
|
169
|
+
<span class="status connected" id="mantineStatus">Connected & Syncing</span>
|
|
170
|
+
</div>
|
|
171
|
+
<!-- Load the source of truth via the SAME ORIGIN proxy -->
|
|
172
|
+
<iframe id="mantine" src="/?path=/story/ui-kit-slider--default"></iframe>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div class="divider" id="vDivider"></div>
|
|
176
|
+
|
|
177
|
+
<div class="pane" id="rightPane">
|
|
178
|
+
<div class="right-top" id="rightTop">
|
|
179
|
+
<div class="header">
|
|
180
|
+
<span id="targetLabel">Target</span>
|
|
181
|
+
<span class="status connected" id="muiStatus">Ready</span>
|
|
182
|
+
</div>
|
|
183
|
+
<!-- Spacer to align the view with the source of truth's Storybook header (40px) -->
|
|
184
|
+
<div style="height: 40px; flex: 0 0 40px; background-color: #fff; border-bottom: 1px solid #e6e6e6; box-sizing: border-box;"></div>
|
|
185
|
+
<!-- Load the bare target component preview -->
|
|
186
|
+
<iframe id="mui" src=""></iframe>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="h-divider" id="hDivider"></div>
|
|
190
|
+
|
|
191
|
+
<div class="right-bottom" id="rightBottom">
|
|
192
|
+
<div class="header">
|
|
193
|
+
<span>Component Notes <span id="currentNoteId" style="color:#aaa; text-transform:none; font-weight:normal; margin-left: 10px;"></span></span>
|
|
194
|
+
<button class="btn" id="fullReportBtn">Full Report</button>
|
|
195
|
+
</div>
|
|
196
|
+
<textarea id="notesArea" class="notes-area" placeholder="Take notes on visual differences here... They will be automatically saved locally for this specific component state."></textarea>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Full Report Modal -->
|
|
201
|
+
<div class="modal-overlay" id="reportModal">
|
|
202
|
+
<div class="modal-content">
|
|
203
|
+
<div class="modal-header">
|
|
204
|
+
<h2>Component Discrepancy Report</h2>
|
|
205
|
+
<button class="close-btn" id="closeModalBtn">×</button>
|
|
206
|
+
</div>
|
|
207
|
+
<textarea id="reportArea" class="report-area" readonly></textarea>
|
|
208
|
+
<div class="modal-footer">
|
|
209
|
+
<button class="btn" id="clearNotesBtn" style="padding: 8px 15px; margin-right: 10px; background-color: #d32f2f;">Clear All Notes</button>
|
|
210
|
+
<button class="btn" id="copyReportBtn" style="padding: 8px 15px;">Copy to Clipboard</button>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<script>
|
|
216
|
+
// Injected by the dev server — see src/devServer.ts.
|
|
217
|
+
const devConfig = window.__ADAPTER_TESTER__ || { sourceOfTruthName: 'Source of Truth', targetName: 'Target', targetPort: 6012 };
|
|
218
|
+
document.getElementById('sourceOfTruthLabel').textContent = `${devConfig.sourceOfTruthName} (Source of Truth)`;
|
|
219
|
+
document.getElementById('targetLabel').textContent = devConfig.targetName;
|
|
220
|
+
|
|
221
|
+
const muiIframe = document.getElementById('mui');
|
|
222
|
+
const mantineIframe = document.getElementById('mantine');
|
|
223
|
+
const muiStatus = document.getElementById('muiStatus');
|
|
224
|
+
|
|
225
|
+
// Sync initial iframe sources with outer URL if params exist, else fall
|
|
226
|
+
// back to a sensible default story so both panes load something on open.
|
|
227
|
+
const outerParams = new URLSearchParams(window.location.search);
|
|
228
|
+
const initialId = outerParams.get('id') || 'ui-kit-slider--default';
|
|
229
|
+
const initialArgs = outerParams.get('args');
|
|
230
|
+
|
|
231
|
+
let mSrc = `/?path=/story/${initialId}`;
|
|
232
|
+
if (initialArgs) {
|
|
233
|
+
mSrc += `&args=${initialArgs}`;
|
|
234
|
+
}
|
|
235
|
+
mantineIframe.src = mSrc;
|
|
236
|
+
|
|
237
|
+
let uSrc = `http://localhost:${devConfig.targetPort}/iframe.html?id=${initialId}&viewMode=story`;
|
|
238
|
+
if (initialArgs) {
|
|
239
|
+
uSrc += `&args=${initialArgs}`;
|
|
240
|
+
}
|
|
241
|
+
muiIframe.src = uSrc;
|
|
242
|
+
|
|
243
|
+
// Resizers
|
|
244
|
+
const vDivider = document.getElementById('vDivider');
|
|
245
|
+
const leftPane = document.getElementById('leftPane');
|
|
246
|
+
const hDivider = document.getElementById('hDivider');
|
|
247
|
+
const rightTop = document.getElementById('rightTop');
|
|
248
|
+
|
|
249
|
+
// Notes
|
|
250
|
+
const notesArea = document.getElementById('notesArea');
|
|
251
|
+
const currentNoteIdLabel = document.getElementById('currentNoteId');
|
|
252
|
+
let currentStoryId = '';
|
|
253
|
+
|
|
254
|
+
// Modal
|
|
255
|
+
const reportModal = document.getElementById('reportModal');
|
|
256
|
+
const fullReportBtn = document.getElementById('fullReportBtn');
|
|
257
|
+
const closeModalBtn = document.getElementById('closeModalBtn');
|
|
258
|
+
const reportArea = document.getElementById('reportArea');
|
|
259
|
+
const copyReportBtn = document.getElementById('copyReportBtn');
|
|
260
|
+
|
|
261
|
+
// Resizing Logic - Vertical
|
|
262
|
+
let isVResizing = false;
|
|
263
|
+
vDivider.addEventListener('mousedown', (e) => {
|
|
264
|
+
isVResizing = true;
|
|
265
|
+
document.body.style.cursor = 'col-resize';
|
|
266
|
+
mantineIframe.style.pointerEvents = 'none';
|
|
267
|
+
muiIframe.style.pointerEvents = 'none';
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
document.addEventListener('mousemove', (e) => {
|
|
271
|
+
if (isVResizing) {
|
|
272
|
+
const totalWidth = document.body.offsetWidth;
|
|
273
|
+
const percentage = (e.clientX / totalWidth) * 100;
|
|
274
|
+
if (percentage > 20 && percentage < 80) {
|
|
275
|
+
leftPane.style.flex = `0 0 ${percentage}%`;
|
|
276
|
+
}
|
|
277
|
+
} else if (isHResizing) {
|
|
278
|
+
const totalHeight = document.body.offsetHeight;
|
|
279
|
+
const percentage = (e.clientY / totalHeight) * 100;
|
|
280
|
+
if (percentage > 20 && percentage < 90) {
|
|
281
|
+
rightTop.style.flex = `0 0 ${percentage}%`;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
document.addEventListener('mouseup', () => {
|
|
287
|
+
if (isVResizing || isHResizing) {
|
|
288
|
+
isVResizing = false;
|
|
289
|
+
isHResizing = false;
|
|
290
|
+
document.body.style.cursor = 'default';
|
|
291
|
+
mantineIframe.style.pointerEvents = 'auto';
|
|
292
|
+
muiIframe.style.pointerEvents = 'auto';
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Resizing Logic - Horizontal
|
|
297
|
+
let isHResizing = false;
|
|
298
|
+
hDivider.addEventListener('mousedown', (e) => {
|
|
299
|
+
isHResizing = true;
|
|
300
|
+
document.body.style.cursor = 'row-resize';
|
|
301
|
+
mantineIframe.style.pointerEvents = 'none';
|
|
302
|
+
muiIframe.style.pointerEvents = 'none';
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Auto-save notes logic
|
|
306
|
+
notesArea.addEventListener('input', (e) => {
|
|
307
|
+
if (currentStoryId) {
|
|
308
|
+
const val = e.target.value;
|
|
309
|
+
if (val.trim() === '') {
|
|
310
|
+
localStorage.removeItem(`notes_${currentStoryId}`);
|
|
311
|
+
} else {
|
|
312
|
+
localStorage.setItem(`notes_${currentStoryId}`, val);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
function loadNotes(storyId) {
|
|
318
|
+
if (currentStoryId !== storyId) {
|
|
319
|
+
currentStoryId = storyId;
|
|
320
|
+
currentNoteIdLabel.textContent = `(${storyId})`;
|
|
321
|
+
const savedNote = localStorage.getItem(`notes_${storyId}`);
|
|
322
|
+
notesArea.value = savedNote || '';
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Modal Logic
|
|
327
|
+
const clearNotesBtn = document.getElementById('clearNotesBtn');
|
|
328
|
+
|
|
329
|
+
fullReportBtn.addEventListener('click', async () => {
|
|
330
|
+
let reportText = '';
|
|
331
|
+
try {
|
|
332
|
+
const response = await fetch('/report-header.txt');
|
|
333
|
+
reportText = await response.text();
|
|
334
|
+
} catch (err) {
|
|
335
|
+
reportText = '# UI Component Discrepancy Report\n\n*(Failed to load custom header)*\n\n';
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let hasNotes = false;
|
|
339
|
+
|
|
340
|
+
// Iterate through local storage
|
|
341
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
342
|
+
const key = localStorage.key(i);
|
|
343
|
+
if (key && key.startsWith('notes_')) {
|
|
344
|
+
hasNotes = true;
|
|
345
|
+
const configState = key.replace('notes_', '');
|
|
346
|
+
const note = localStorage.getItem(key);
|
|
347
|
+
|
|
348
|
+
// Parse a human readable name from the config string (e.g., ui-kit-slider--default)
|
|
349
|
+
const baseId = configState.split(' | ')[0];
|
|
350
|
+
const parts = baseId.split('--');
|
|
351
|
+
let compName = (parts[0] || '').replace('ui-kit-', '').split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
352
|
+
let storyName = (parts[1] || '').split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
353
|
+
|
|
354
|
+
const humanReadableHeader = `${compName}${storyName ? ` (${storyName})` : ''}`;
|
|
355
|
+
|
|
356
|
+
reportText += `## Component: ${humanReadableHeader}\n**Storybook Config State:** \`${configState}\`\n\n${note}\n\n---\n\n`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!hasNotes) {
|
|
361
|
+
reportText += '*No notes taken yet.*';
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
reportArea.value = reportText;
|
|
365
|
+
reportModal.style.display = 'flex';
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
closeModalBtn.addEventListener('click', () => {
|
|
369
|
+
reportModal.style.display = 'none';
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Close modal on click outside
|
|
373
|
+
reportModal.addEventListener('click', (e) => {
|
|
374
|
+
if (e.target === reportModal) {
|
|
375
|
+
reportModal.style.display = 'none';
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
copyReportBtn.addEventListener('click', () => {
|
|
380
|
+
reportArea.select();
|
|
381
|
+
document.execCommand('copy');
|
|
382
|
+
const originalText = copyReportBtn.textContent;
|
|
383
|
+
copyReportBtn.textContent = 'Copied!';
|
|
384
|
+
setTimeout(() => {
|
|
385
|
+
copyReportBtn.textContent = originalText;
|
|
386
|
+
}, 2000);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
clearNotesBtn.addEventListener('click', () => {
|
|
390
|
+
if (confirm('Are you sure you want to delete all your component notes? This cannot be undone.')) {
|
|
391
|
+
const keysToRemove = [];
|
|
392
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
393
|
+
const key = localStorage.key(i);
|
|
394
|
+
if (key && key.startsWith('notes_')) {
|
|
395
|
+
keysToRemove.push(key);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
keysToRemove.forEach(k => localStorage.removeItem(k));
|
|
399
|
+
|
|
400
|
+
notesArea.value = '';
|
|
401
|
+
reportArea.value = '*No notes taken yet.*';
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Same-Origin Polling Syncing
|
|
406
|
+
let lastUrl = '';
|
|
407
|
+
setInterval(() => {
|
|
408
|
+
try {
|
|
409
|
+
const currentUrl = mantineIframe.contentWindow.location.href;
|
|
410
|
+
|
|
411
|
+
if (currentUrl && currentUrl !== lastUrl && currentUrl !== 'about:blank') {
|
|
412
|
+
lastUrl = currentUrl;
|
|
413
|
+
|
|
414
|
+
const url = new URL(currentUrl);
|
|
415
|
+
const args = url.searchParams.get('args');
|
|
416
|
+
const id = url.searchParams.get('id') || url.searchParams.get('path')?.replace('/story/', '');
|
|
417
|
+
|
|
418
|
+
if (id) {
|
|
419
|
+
// Determine the full state string to use as the notes key
|
|
420
|
+
const stateId = args ? `${id} | ${args}` : id;
|
|
421
|
+
loadNotes(stateId);
|
|
422
|
+
|
|
423
|
+
// Sync search parameters to the outer browser URL in the address bar
|
|
424
|
+
const outerUrl = new URL(window.location.href);
|
|
425
|
+
let urlChanged = false;
|
|
426
|
+
if (outerUrl.searchParams.get('id') !== id) {
|
|
427
|
+
outerUrl.searchParams.set('id', id);
|
|
428
|
+
urlChanged = true;
|
|
429
|
+
}
|
|
430
|
+
if (args) {
|
|
431
|
+
if (outerUrl.searchParams.get('args') !== args) {
|
|
432
|
+
outerUrl.searchParams.set('args', args);
|
|
433
|
+
urlChanged = true;
|
|
434
|
+
}
|
|
435
|
+
} else if (outerUrl.searchParams.has('args')) {
|
|
436
|
+
outerUrl.searchParams.delete('args');
|
|
437
|
+
urlChanged = true;
|
|
438
|
+
}
|
|
439
|
+
if (urlChanged) {
|
|
440
|
+
window.history.replaceState({}, '', outerUrl.pathname + outerUrl.search);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let muiUrl = `http://localhost:${devConfig.targetPort}/iframe.html?id=${id}&viewMode=story`;
|
|
444
|
+
if (args) {
|
|
445
|
+
muiUrl += `&args=${args}`;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (muiIframe.src !== muiUrl) {
|
|
449
|
+
muiStatus.textContent = 'Updating...';
|
|
450
|
+
muiStatus.classList.remove('connected');
|
|
451
|
+
muiIframe.src = muiUrl;
|
|
452
|
+
|
|
453
|
+
setTimeout(() => {
|
|
454
|
+
muiStatus.textContent = 'Synced';
|
|
455
|
+
muiStatus.classList.add('connected');
|
|
456
|
+
}, 500);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} catch (e) {
|
|
461
|
+
// Ignore cross-origin errors if they briefly occur during navigation
|
|
462
|
+
}
|
|
463
|
+
}, 100);
|
|
464
|
+
</script>
|
|
465
|
+
</body>
|
|
466
|
+
</html>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# UI Component Discrepancy Report
|
|
2
|
+
|
|
3
|
+
*You are an expert AI frontend developer. Below is a report of visual discrepancies between the Source of Truth (`@recursica/mantine-adapter` running in Storybook) and the target implementation (`@recursica/mui-adapter` running in Storybook).*
|
|
4
|
+
|
|
5
|
+
*Each section below represents a specific component story and its active control arguments. The section header provides the human-readable component name along with the exact Storybook URL configuration string. Use this context to apply the necessary CSS/TypeScript fixes to the MUI adapter to perfectly match the Mantine Source of Truth.*
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/borderux/recursica/tree/main/packages/adapter-tester/src/adapter-tester.schema.json",
|
|
4
|
+
"title": "adapter-tester.config.json",
|
|
5
|
+
"description": "Configuration for @recursica/adapter-tester. All fields are optional — an adapter repo with a normal `storybook` npm script needs no config file at all.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Optional pointer to this schema for editor autocomplete, e.g. \"./node_modules/@recursica/adapter-tester/src/adapter-tester.schema.json\"."
|
|
12
|
+
},
|
|
13
|
+
"name": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Label for this project's own target. Defaults to the unscoped name in this project's package.json (e.g. \"@recursica/mui-adapter\" -> \"mui-adapter\")."
|
|
16
|
+
},
|
|
17
|
+
"storybook": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"description": "This project's own Storybook target.",
|
|
21
|
+
"properties": {
|
|
22
|
+
"port": {
|
|
23
|
+
"type": "integer",
|
|
24
|
+
"minimum": 1,
|
|
25
|
+
"maximum": 65535,
|
|
26
|
+
"description": "Port the target's Storybook is served on. Auto-detected from this project's own `scripts.storybook` (a `-p <port>`/`--port <port>` flag) when omitted, falling back to Storybook's own default of 6006."
|
|
27
|
+
},
|
|
28
|
+
"command": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Command that boots the target's Storybook. Defaults to `npm run storybook`."
|
|
31
|
+
},
|
|
32
|
+
"cwd": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "Directory the command runs in, relative to the config file. Defaults to \".\"."
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"sourceOfTruth": {
|
|
39
|
+
"description": "The comparison target. Defaults to `{ \"type\": \"mantine-harness\" }`.",
|
|
40
|
+
"oneOf": [
|
|
41
|
+
{
|
|
42
|
+
"type": "object",
|
|
43
|
+
"additionalProperties": false,
|
|
44
|
+
"description": "Default mode: boots a throwaway harness that installs the published @recursica/mantine-adapter from npm — no monorepo checkout required.",
|
|
45
|
+
"properties": {
|
|
46
|
+
"type": { "const": "mantine-harness" },
|
|
47
|
+
"port": {
|
|
48
|
+
"type": "integer",
|
|
49
|
+
"minimum": 1,
|
|
50
|
+
"maximum": 65535,
|
|
51
|
+
"description": "Port the throwaway harness's Storybook is served on. Defaults to 6011."
|
|
52
|
+
},
|
|
53
|
+
"mantineAdapterVersion": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "npm version/tag of @recursica/mantine-adapter to install in the harness. Defaults to `latest`."
|
|
56
|
+
},
|
|
57
|
+
"storybookTemplateVersion": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "npm version/tag of @recursica/storybook-template to install in the harness. Defaults to `latest`."
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"type": "object",
|
|
65
|
+
"additionalProperties": false,
|
|
66
|
+
"description": "Non-standard mode: points at an already-addressable Storybook — e.g. a sibling workspace package's own Storybook inside this monorepo.",
|
|
67
|
+
"required": ["type", "port"],
|
|
68
|
+
"properties": {
|
|
69
|
+
"type": { "const": "url" },
|
|
70
|
+
"name": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"description": "Label for the source-of-truth target. Defaults to \"Mantine\"."
|
|
73
|
+
},
|
|
74
|
+
"port": {
|
|
75
|
+
"type": "integer",
|
|
76
|
+
"minimum": 1,
|
|
77
|
+
"maximum": 65535,
|
|
78
|
+
"description": "Port the source-of-truth Storybook is served on."
|
|
79
|
+
},
|
|
80
|
+
"command": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "Command that boots the source-of-truth Storybook. Defaults to `npm run storybook`."
|
|
83
|
+
},
|
|
84
|
+
"cwd": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"description": "Directory the command runs in, relative to the config file. Defaults to \".\"."
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
"diffThresholdPixels": {
|
|
93
|
+
"type": "number",
|
|
94
|
+
"minimum": 0,
|
|
95
|
+
"description": "Global visual diff threshold, in mismatched pixels, before a story is considered a failure. Defaults to 3500. AI agents must not modify this field — see AGENT.md."
|
|
96
|
+
},
|
|
97
|
+
"relaxedThresholdStoryIds": {
|
|
98
|
+
"type": "array",
|
|
99
|
+
"items": { "type": "string" },
|
|
100
|
+
"description": "Story id prefixes that use `relaxedThresholdPixels` instead of the global `diffThresholdPixels`."
|
|
101
|
+
},
|
|
102
|
+
"relaxedThresholdPixels": {
|
|
103
|
+
"type": "number",
|
|
104
|
+
"minimum": 0,
|
|
105
|
+
"description": "Diff threshold applied to stories matching `relaxedThresholdStoryIds`."
|
|
106
|
+
},
|
|
107
|
+
"excludeTitlePrefixes": {
|
|
108
|
+
"type": "array",
|
|
109
|
+
"items": { "type": "string" },
|
|
110
|
+
"description": "Storybook title prefixes to exclude from comparison, in addition to the built-in denylist (`Theme/*`, `Tokens/*`, `Introduction`)."
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|