@mindstudio-ai/remy 0.1.99 → 0.1.101
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.
|
@@ -12,7 +12,9 @@ If you are building a web frontend, consult `visualDesignExpert` for guidance an
|
|
|
12
12
|
|
|
13
13
|
Then, build everything in one turn: methods, tables, interfaces, manifest updates, and scenarios, using the spec as the master plan. Be sure to delete any unnecessary files from the "Hello World" scaffold that already exist in the project - don't forget to update the page metadata on index.html too.
|
|
14
14
|
|
|
15
|
-
When code generation is complete,
|
|
15
|
+
When code generation is complete, do a polish pass before verifying. Re-read the spec annotations and the design expert's guidance, then walk through each frontend file looking for design details that got skipped in the initial build: animations, transitions, hover states, micro-interactions, spring physics, entrance reveals, gesture handling, layout issues, and anything else. The initial build prioritizes getting everything connected and functional, but this pass closes the gap between "it works" and "it feels great." In many ways this is the most important part of the initial build, as the user's first experience of the deliverable will set their expectations for every iteration that follows. Don't mess this up.
|
|
16
|
+
|
|
17
|
+
Then verify:
|
|
16
18
|
- First, run use `runScenario` to seed test data, then use `runMethod` to confirm a method works
|
|
17
19
|
- If the app has a web frontend, check the browser logs to make sure there are no errors rendering it.
|
|
18
20
|
- Ask the `visualDesignExpert` to take a screenshot and verity that the visual design looks correct. Fix any issues it flags - we want the user's first time seeing the finished product to truly wow them.
|
package/dist/headless.js
CHANGED
|
@@ -1051,6 +1051,18 @@ function unifiedDiff(filePath, oldText, newText) {
|
|
|
1051
1051
|
return lines.join("\n");
|
|
1052
1052
|
}
|
|
1053
1053
|
|
|
1054
|
+
// src/tools/_helpers/fileLock.ts
|
|
1055
|
+
var locks = /* @__PURE__ */ new Map();
|
|
1056
|
+
function acquireFileLock(filePath) {
|
|
1057
|
+
let release;
|
|
1058
|
+
const next = new Promise((res) => {
|
|
1059
|
+
release = res;
|
|
1060
|
+
});
|
|
1061
|
+
const wait = locks.get(filePath) ?? Promise.resolve();
|
|
1062
|
+
locks.set(filePath, next);
|
|
1063
|
+
return wait.then(() => release);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1054
1066
|
// src/tools/spec/writeSpec.ts
|
|
1055
1067
|
var writeSpecTool = {
|
|
1056
1068
|
clearable: true,
|
|
@@ -1086,6 +1098,7 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
|
|
|
1086
1098
|
} catch (err) {
|
|
1087
1099
|
return `Error: ${err.message}`;
|
|
1088
1100
|
}
|
|
1101
|
+
const release = await acquireFileLock(input.path);
|
|
1089
1102
|
try {
|
|
1090
1103
|
await fs6.mkdir(path4.dirname(input.path), { recursive: true });
|
|
1091
1104
|
let oldContent = null;
|
|
@@ -1100,6 +1113,8 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
|
|
|
1100
1113
|
${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
1101
1114
|
} catch (err) {
|
|
1102
1115
|
return `Error writing file: ${err.message}`;
|
|
1116
|
+
} finally {
|
|
1117
|
+
release();
|
|
1103
1118
|
}
|
|
1104
1119
|
}
|
|
1105
1120
|
};
|
|
@@ -1151,69 +1166,74 @@ var editSpecTool = {
|
|
|
1151
1166
|
} catch (err) {
|
|
1152
1167
|
return `Error: ${err.message}`;
|
|
1153
1168
|
}
|
|
1154
|
-
|
|
1169
|
+
const release = await acquireFileLock(input.path);
|
|
1155
1170
|
try {
|
|
1156
|
-
originalContent
|
|
1157
|
-
} catch (err) {
|
|
1158
|
-
return `Error reading file: ${err.message}`;
|
|
1159
|
-
}
|
|
1160
|
-
let content = originalContent;
|
|
1161
|
-
for (const edit of input.edits) {
|
|
1162
|
-
let range;
|
|
1171
|
+
let originalContent;
|
|
1163
1172
|
try {
|
|
1164
|
-
|
|
1173
|
+
originalContent = await fs7.readFile(input.path, "utf-8");
|
|
1165
1174
|
} catch (err) {
|
|
1166
|
-
|
|
1167
|
-
|
|
1175
|
+
return `Error reading file: ${err.message}`;
|
|
1176
|
+
}
|
|
1177
|
+
let content = originalContent;
|
|
1178
|
+
for (const edit of input.edits) {
|
|
1179
|
+
let range;
|
|
1180
|
+
try {
|
|
1181
|
+
range = resolveHeadingPath(content, edit.heading);
|
|
1182
|
+
} catch (err) {
|
|
1183
|
+
const tree = getHeadingTree(content);
|
|
1184
|
+
return `Error: ${err.message}
|
|
1168
1185
|
|
|
1169
1186
|
Document structure:
|
|
1170
1187
|
${tree}`;
|
|
1171
|
-
}
|
|
1172
|
-
const lines = content.split("\n");
|
|
1173
|
-
switch (edit.operation) {
|
|
1174
|
-
case "replace": {
|
|
1175
|
-
if (edit.content == null) {
|
|
1176
|
-
return 'Error: "content" is required for replace operations.';
|
|
1177
|
-
}
|
|
1178
|
-
const contentLines = edit.content.split("\n");
|
|
1179
|
-
lines.splice(
|
|
1180
|
-
range.contentStart,
|
|
1181
|
-
range.contentEnd - range.contentStart,
|
|
1182
|
-
...contentLines
|
|
1183
|
-
);
|
|
1184
|
-
break;
|
|
1185
1188
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
+
const lines = content.split("\n");
|
|
1190
|
+
switch (edit.operation) {
|
|
1191
|
+
case "replace": {
|
|
1192
|
+
if (edit.content == null) {
|
|
1193
|
+
return 'Error: "content" is required for replace operations.';
|
|
1194
|
+
}
|
|
1195
|
+
const contentLines = edit.content.split("\n");
|
|
1196
|
+
lines.splice(
|
|
1197
|
+
range.contentStart,
|
|
1198
|
+
range.contentEnd - range.contentStart,
|
|
1199
|
+
...contentLines
|
|
1200
|
+
);
|
|
1201
|
+
break;
|
|
1189
1202
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1203
|
+
case "insert_after": {
|
|
1204
|
+
if (edit.content == null) {
|
|
1205
|
+
return 'Error: "content" is required for insert_after operations.';
|
|
1206
|
+
}
|
|
1207
|
+
const contentLines = edit.content.split("\n");
|
|
1208
|
+
lines.splice(range.contentEnd, 0, ...contentLines);
|
|
1209
|
+
break;
|
|
1197
1210
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1211
|
+
case "insert_before": {
|
|
1212
|
+
if (edit.content == null) {
|
|
1213
|
+
return 'Error: "content" is required for insert_before operations.';
|
|
1214
|
+
}
|
|
1215
|
+
const contentLines = edit.content.split("\n");
|
|
1216
|
+
lines.splice(range.startLine, 0, ...contentLines);
|
|
1217
|
+
break;
|
|
1218
|
+
}
|
|
1219
|
+
case "delete": {
|
|
1220
|
+
lines.splice(range.startLine, range.contentEnd - range.startLine);
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
default:
|
|
1224
|
+
return `Error: Unknown operation "${edit.operation}". Use replace, insert_after, insert_before, or delete.`;
|
|
1205
1225
|
}
|
|
1206
|
-
|
|
1207
|
-
return `Error: Unknown operation "${edit.operation}". Use replace, insert_after, insert_before, or delete.`;
|
|
1226
|
+
content = lines.join("\n");
|
|
1208
1227
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
return
|
|
1228
|
+
try {
|
|
1229
|
+
await fs7.writeFile(input.path, content, "utf-8");
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
return `Error writing file: ${err.message}`;
|
|
1232
|
+
}
|
|
1233
|
+
return unifiedDiff(input.path, originalContent, content);
|
|
1234
|
+
} finally {
|
|
1235
|
+
release();
|
|
1215
1236
|
}
|
|
1216
|
-
return unifiedDiff(input.path, originalContent, content);
|
|
1217
1237
|
}
|
|
1218
1238
|
};
|
|
1219
1239
|
|
|
@@ -1825,6 +1845,7 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
|
1825
1845
|
};
|
|
1826
1846
|
})(),
|
|
1827
1847
|
async execute(input) {
|
|
1848
|
+
const release = await acquireFileLock(input.path);
|
|
1828
1849
|
try {
|
|
1829
1850
|
await fs10.mkdir(path6.dirname(input.path), { recursive: true });
|
|
1830
1851
|
let oldContent = null;
|
|
@@ -1839,6 +1860,8 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
|
1839
1860
|
${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
1840
1861
|
} catch (err) {
|
|
1841
1862
|
return `Error writing file: ${err.message}`;
|
|
1863
|
+
} finally {
|
|
1864
|
+
release();
|
|
1842
1865
|
}
|
|
1843
1866
|
}
|
|
1844
1867
|
};
|
|
@@ -1955,6 +1978,7 @@ var editFileTool = {
|
|
|
1955
1978
|
}
|
|
1956
1979
|
},
|
|
1957
1980
|
async execute(input) {
|
|
1981
|
+
const release = await acquireFileLock(input.path);
|
|
1958
1982
|
try {
|
|
1959
1983
|
const content = await fs11.readFile(input.path, "utf-8");
|
|
1960
1984
|
const { old_string, new_string, replace_all } = input;
|
|
@@ -2006,6 +2030,8 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
2006
2030
|
return `Error: old_string not found in ${input.path}. Make sure you've read the file first and copied the exact text.`;
|
|
2007
2031
|
} catch (err) {
|
|
2008
2032
|
return `Error editing file: ${err.message}`;
|
|
2033
|
+
} finally {
|
|
2034
|
+
release();
|
|
2009
2035
|
}
|
|
2010
2036
|
}
|
|
2011
2037
|
};
|
package/dist/index.js
CHANGED
|
@@ -502,6 +502,24 @@ var init_diff = __esm({
|
|
|
502
502
|
}
|
|
503
503
|
});
|
|
504
504
|
|
|
505
|
+
// src/tools/_helpers/fileLock.ts
|
|
506
|
+
function acquireFileLock(filePath) {
|
|
507
|
+
let release;
|
|
508
|
+
const next = new Promise((res) => {
|
|
509
|
+
release = res;
|
|
510
|
+
});
|
|
511
|
+
const wait = locks.get(filePath) ?? Promise.resolve();
|
|
512
|
+
locks.set(filePath, next);
|
|
513
|
+
return wait.then(() => release);
|
|
514
|
+
}
|
|
515
|
+
var locks;
|
|
516
|
+
var init_fileLock = __esm({
|
|
517
|
+
"src/tools/_helpers/fileLock.ts"() {
|
|
518
|
+
"use strict";
|
|
519
|
+
locks = /* @__PURE__ */ new Map();
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
|
|
505
523
|
// src/tools/spec/writeSpec.ts
|
|
506
524
|
import fs3 from "fs/promises";
|
|
507
525
|
import path from "path";
|
|
@@ -511,6 +529,7 @@ var init_writeSpec = __esm({
|
|
|
511
529
|
"use strict";
|
|
512
530
|
init_helpers();
|
|
513
531
|
init_diff();
|
|
532
|
+
init_fileLock();
|
|
514
533
|
writeSpecTool = {
|
|
515
534
|
clearable: true,
|
|
516
535
|
definition: {
|
|
@@ -545,6 +564,7 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
|
|
|
545
564
|
} catch (err) {
|
|
546
565
|
return `Error: ${err.message}`;
|
|
547
566
|
}
|
|
567
|
+
const release = await acquireFileLock(input.path);
|
|
548
568
|
try {
|
|
549
569
|
await fs3.mkdir(path.dirname(input.path), { recursive: true });
|
|
550
570
|
let oldContent = null;
|
|
@@ -559,6 +579,8 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
|
|
|
559
579
|
${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
560
580
|
} catch (err) {
|
|
561
581
|
return `Error writing file: ${err.message}`;
|
|
582
|
+
} finally {
|
|
583
|
+
release();
|
|
562
584
|
}
|
|
563
585
|
}
|
|
564
586
|
};
|
|
@@ -573,6 +595,7 @@ var init_editSpec = __esm({
|
|
|
573
595
|
"use strict";
|
|
574
596
|
init_helpers();
|
|
575
597
|
init_diff();
|
|
598
|
+
init_fileLock();
|
|
576
599
|
editSpecTool = {
|
|
577
600
|
clearable: true,
|
|
578
601
|
definition: {
|
|
@@ -618,69 +641,74 @@ var init_editSpec = __esm({
|
|
|
618
641
|
} catch (err) {
|
|
619
642
|
return `Error: ${err.message}`;
|
|
620
643
|
}
|
|
621
|
-
|
|
644
|
+
const release = await acquireFileLock(input.path);
|
|
622
645
|
try {
|
|
623
|
-
originalContent
|
|
624
|
-
} catch (err) {
|
|
625
|
-
return `Error reading file: ${err.message}`;
|
|
626
|
-
}
|
|
627
|
-
let content = originalContent;
|
|
628
|
-
for (const edit of input.edits) {
|
|
629
|
-
let range;
|
|
646
|
+
let originalContent;
|
|
630
647
|
try {
|
|
631
|
-
|
|
648
|
+
originalContent = await fs4.readFile(input.path, "utf-8");
|
|
632
649
|
} catch (err) {
|
|
633
|
-
|
|
634
|
-
|
|
650
|
+
return `Error reading file: ${err.message}`;
|
|
651
|
+
}
|
|
652
|
+
let content = originalContent;
|
|
653
|
+
for (const edit of input.edits) {
|
|
654
|
+
let range;
|
|
655
|
+
try {
|
|
656
|
+
range = resolveHeadingPath(content, edit.heading);
|
|
657
|
+
} catch (err) {
|
|
658
|
+
const tree = getHeadingTree(content);
|
|
659
|
+
return `Error: ${err.message}
|
|
635
660
|
|
|
636
661
|
Document structure:
|
|
637
662
|
${tree}`;
|
|
638
|
-
}
|
|
639
|
-
const lines = content.split("\n");
|
|
640
|
-
switch (edit.operation) {
|
|
641
|
-
case "replace": {
|
|
642
|
-
if (edit.content == null) {
|
|
643
|
-
return 'Error: "content" is required for replace operations.';
|
|
644
|
-
}
|
|
645
|
-
const contentLines = edit.content.split("\n");
|
|
646
|
-
lines.splice(
|
|
647
|
-
range.contentStart,
|
|
648
|
-
range.contentEnd - range.contentStart,
|
|
649
|
-
...contentLines
|
|
650
|
-
);
|
|
651
|
-
break;
|
|
652
663
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
664
|
+
const lines = content.split("\n");
|
|
665
|
+
switch (edit.operation) {
|
|
666
|
+
case "replace": {
|
|
667
|
+
if (edit.content == null) {
|
|
668
|
+
return 'Error: "content" is required for replace operations.';
|
|
669
|
+
}
|
|
670
|
+
const contentLines = edit.content.split("\n");
|
|
671
|
+
lines.splice(
|
|
672
|
+
range.contentStart,
|
|
673
|
+
range.contentEnd - range.contentStart,
|
|
674
|
+
...contentLines
|
|
675
|
+
);
|
|
676
|
+
break;
|
|
656
677
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
678
|
+
case "insert_after": {
|
|
679
|
+
if (edit.content == null) {
|
|
680
|
+
return 'Error: "content" is required for insert_after operations.';
|
|
681
|
+
}
|
|
682
|
+
const contentLines = edit.content.split("\n");
|
|
683
|
+
lines.splice(range.contentEnd, 0, ...contentLines);
|
|
684
|
+
break;
|
|
664
685
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
686
|
+
case "insert_before": {
|
|
687
|
+
if (edit.content == null) {
|
|
688
|
+
return 'Error: "content" is required for insert_before operations.';
|
|
689
|
+
}
|
|
690
|
+
const contentLines = edit.content.split("\n");
|
|
691
|
+
lines.splice(range.startLine, 0, ...contentLines);
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
case "delete": {
|
|
695
|
+
lines.splice(range.startLine, range.contentEnd - range.startLine);
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
default:
|
|
699
|
+
return `Error: Unknown operation "${edit.operation}". Use replace, insert_after, insert_before, or delete.`;
|
|
672
700
|
}
|
|
673
|
-
|
|
674
|
-
return `Error: Unknown operation "${edit.operation}". Use replace, insert_after, insert_before, or delete.`;
|
|
701
|
+
content = lines.join("\n");
|
|
675
702
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
return
|
|
703
|
+
try {
|
|
704
|
+
await fs4.writeFile(input.path, content, "utf-8");
|
|
705
|
+
} catch (err) {
|
|
706
|
+
return `Error writing file: ${err.message}`;
|
|
707
|
+
}
|
|
708
|
+
return unifiedDiff(input.path, originalContent, content);
|
|
709
|
+
} finally {
|
|
710
|
+
release();
|
|
682
711
|
}
|
|
683
|
-
return unifiedDiff(input.path, originalContent, content);
|
|
684
712
|
}
|
|
685
713
|
};
|
|
686
714
|
}
|
|
@@ -1335,6 +1363,7 @@ var init_writeFile = __esm({
|
|
|
1335
1363
|
"src/tools/code/writeFile.ts"() {
|
|
1336
1364
|
"use strict";
|
|
1337
1365
|
init_diff();
|
|
1366
|
+
init_fileLock();
|
|
1338
1367
|
writeFileTool = {
|
|
1339
1368
|
clearable: true,
|
|
1340
1369
|
definition: {
|
|
@@ -1378,6 +1407,7 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
|
1378
1407
|
};
|
|
1379
1408
|
})(),
|
|
1380
1409
|
async execute(input) {
|
|
1410
|
+
const release = await acquireFileLock(input.path);
|
|
1381
1411
|
try {
|
|
1382
1412
|
await fs7.mkdir(path3.dirname(input.path), { recursive: true });
|
|
1383
1413
|
let oldContent = null;
|
|
@@ -1392,6 +1422,8 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
|
|
|
1392
1422
|
${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
1393
1423
|
} catch (err) {
|
|
1394
1424
|
return `Error writing file: ${err.message}`;
|
|
1425
|
+
} finally {
|
|
1426
|
+
release();
|
|
1395
1427
|
}
|
|
1396
1428
|
}
|
|
1397
1429
|
};
|
|
@@ -1489,6 +1521,7 @@ var init_editFile = __esm({
|
|
|
1489
1521
|
"src/tools/code/editFile/index.ts"() {
|
|
1490
1522
|
"use strict";
|
|
1491
1523
|
init_diff();
|
|
1524
|
+
init_fileLock();
|
|
1492
1525
|
init_helpers2();
|
|
1493
1526
|
editFileTool = {
|
|
1494
1527
|
clearable: true,
|
|
@@ -1519,6 +1552,7 @@ var init_editFile = __esm({
|
|
|
1519
1552
|
}
|
|
1520
1553
|
},
|
|
1521
1554
|
async execute(input) {
|
|
1555
|
+
const release = await acquireFileLock(input.path);
|
|
1522
1556
|
try {
|
|
1523
1557
|
const content = await fs8.readFile(input.path, "utf-8");
|
|
1524
1558
|
const { old_string, new_string, replace_all } = input;
|
|
@@ -1570,6 +1604,8 @@ ${unifiedDiff(input.path, content, updated)}`;
|
|
|
1570
1604
|
return `Error: old_string not found in ${input.path}. Make sure you've read the file first and copied the exact text.`;
|
|
1571
1605
|
} catch (err) {
|
|
1572
1606
|
return `Error editing file: ${err.message}`;
|
|
1607
|
+
} finally {
|
|
1608
|
+
release();
|
|
1573
1609
|
}
|
|
1574
1610
|
}
|
|
1575
1611
|
};
|
|
@@ -62,7 +62,7 @@ export const Users = db.defineTable<{
|
|
|
62
62
|
|
|
63
63
|
- **`email` / `phone`** — read-only from code. Writing via `update()` or `push()` throws a `MindStudioError`. Use the auth API to change a user's email or phone.
|
|
64
64
|
- **`roles`** — read/write from both code and the dashboard. `Users.update(userId, { roles: ['admin'] })` works and syncs to the platform. Dashboard role changes sync back to the table.
|
|
65
|
-
- All other columns are fully the developer's.
|
|
65
|
+
- All other columns are fully the developer's. When auth creates a user row, only the managed columns (email/phone, roles) are populated. All user-defined columns start as null until the user completes onboarding — type them as optional and guard against null.
|
|
66
66
|
|
|
67
67
|
## Frontend Auth (Interface SDK)
|
|
68
68
|
|
|
@@ -129,6 +129,7 @@ await auth.logout(); // clears session
|
|
|
129
129
|
|
|
130
130
|
```typescript
|
|
131
131
|
auth.phone.countries // ~180 countries with { code, dialCode, name, flag }
|
|
132
|
+
// Key selects by country code (US, CA, BB), not dial code — multiple countries share +1
|
|
132
133
|
auth.phone.detectCountry() // guess from timezone, e.g. 'US'
|
|
133
134
|
auth.phone.toE164('5551234567', 'US') // '+15551234567'
|
|
134
135
|
auth.phone.format('+15551234567') // '+1 (555) 123-4567'
|
|
@@ -25,7 +25,7 @@ Remember: users care about look and feel as much as (and often more than) underl
|
|
|
25
25
|
|
|
26
26
|
Write specs in natural, human language. Describe what the app does the way you'd explain it to a colleague. The spec renders with annotations hidden is a human-forward document that anyone can read. The spec with annotations visible is the agent-forward document that drives code generation. Keep the prose clean and readable — the user should never see raw CSS, code, or technical values in the prose. Write "square corners on all cards" not `border-radius: 0`. Write "no shadows" not `box-shadow: none`. Technical specifics belong in annotations.
|
|
27
27
|
|
|
28
|
-
When the design expert provides specific implementation details — layout structure, CSS values, spacing, font sizes, rotation angles, shadow definitions, animation timings, or things to pay special attention to or watch out for — capture them
|
|
28
|
+
When the design expert provides specific implementation details — layout structure, CSS values, spacing, font sizes, rotation angles, shadow definitions, animation timings, or things to pay special attention to or watch out for — it is critical that you capture them in full within the spec. The design expert's recommendations are precise and intentional; don't summarize them into vague language. The prose describes the intent, the annotations preserve the exact values the coder needs:
|
|
29
29
|
|
|
30
30
|
```markdown
|
|
31
31
|
Cards float at varied angles with [rounded corners]{border-radius: 24px} on a pure black background.
|
|
@@ -37,6 +37,8 @@ box-shadow: 0 8px 32px rgba(0,0,0,0.3) for floating depth
|
|
|
37
37
|
|
|
38
38
|
When you have image URLs (from the design expert), embed them directly in the spec using markdown image syntax. Write descriptive alt text that captures what the image actually depicts (this helps accessibility and helps the coding agent understand the image without loading it). Use the surrounding prose to explain the design intent — what the image is for, how it should be used in the layout, and why it was chosen.
|
|
39
39
|
|
|
40
|
+
When the design expert provides wireframes, include them directly in the spec for future reference.
|
|
41
|
+
|
|
40
42
|
```markdown
|
|
41
43
|
### Hero Section
|
|
42
44
|
|
|
@@ -43,7 +43,7 @@ For any work involving AI models, external actions (web scraping, email, SMS), o
|
|
|
43
43
|
- Use container queries for components that need to adapt to their container rather than the viewport.
|
|
44
44
|
|
|
45
45
|
### State Management
|
|
46
|
-
- Calls to methods introduce latency. When building web frontends that load data from methods,
|
|
46
|
+
- Calls to methods introduce latency. When building web frontends that load data from methods, front-load as much data as you can in a single API request - e.g., when possible, load a large data object into a central store and use that to render sub-screens in an app, rather than an API call on every screen. User experience and perceived speed/performance are far more valuable than normalization and good REST API design.
|
|
47
47
|
|
|
48
48
|
### Dependencies
|
|
49
49
|
Before installing a package you haven't used in this project, do a quick web search to confirm it's still the best option. The JavaScript ecosystem moves fast — the package you remember from training may have been superseded by something smaller, faster, or better maintained. A 10-second search beats debugging a deprecated library.
|