@mindstudio-ai/remy 0.1.84 → 0.1.85
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/dist/headless.js +99 -83
- package/dist/index.js +100 -84
- package/dist/prompt/compiled/dev-and-deploy.md +1 -1
- package/dist/prompt/compiled/scenarios.md +3 -1
- package/dist/prompt/static/coding.md +1 -4
- package/dist/prompt/static/instructions.md +3 -0
- package/dist/subagents/codeSanityCheck/prompt.md +4 -0
- package/dist/subagents/designExpert/prompts/ui-patterns.md +5 -3
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -3147,6 +3147,16 @@ ${appSpec}
|
|
|
3147
3147
|
|
|
3148
3148
|
// src/subagents/browserAutomation/index.ts
|
|
3149
3149
|
var log6 = createLogger("browser-automation");
|
|
3150
|
+
var lockQueue = Promise.resolve();
|
|
3151
|
+
function acquireBrowserLock() {
|
|
3152
|
+
let release;
|
|
3153
|
+
const next = new Promise((res) => {
|
|
3154
|
+
release = res;
|
|
3155
|
+
});
|
|
3156
|
+
const wait = lockQueue;
|
|
3157
|
+
lockQueue = next;
|
|
3158
|
+
return wait.then(() => release);
|
|
3159
|
+
}
|
|
3150
3160
|
var browserAutomationTool = {
|
|
3151
3161
|
clearable: true,
|
|
3152
3162
|
definition: {
|
|
@@ -3167,99 +3177,104 @@ var browserAutomationTool = {
|
|
|
3167
3177
|
if (!context) {
|
|
3168
3178
|
return "Error: browser automation requires execution context (only available in headless mode)";
|
|
3169
3179
|
}
|
|
3180
|
+
const release = await acquireBrowserLock();
|
|
3170
3181
|
try {
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3182
|
+
try {
|
|
3183
|
+
const status = await sidecarRequest(
|
|
3184
|
+
"/browser-status",
|
|
3185
|
+
{},
|
|
3186
|
+
{ timeout: 5e3 }
|
|
3187
|
+
);
|
|
3188
|
+
if (!status.connected) {
|
|
3189
|
+
return "Error: the browser preview is not connected. The user needs to open the preview before browser tests can run.";
|
|
3190
|
+
}
|
|
3191
|
+
} catch {
|
|
3192
|
+
return "Error: could not check browser status. The dev environment may not be running.";
|
|
3178
3193
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
await
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
}
|
|
3198
|
-
} catch (err) {
|
|
3199
|
-
return `Error taking screenshot: ${err.message}`;
|
|
3194
|
+
try {
|
|
3195
|
+
await sidecarRequest("/reset-browser", {}, { timeout: 5e3 });
|
|
3196
|
+
} catch {
|
|
3197
|
+
}
|
|
3198
|
+
const result = await runSubAgent({
|
|
3199
|
+
system: getBrowserAutomationPrompt(),
|
|
3200
|
+
task: input.task,
|
|
3201
|
+
tools: BROWSER_TOOLS,
|
|
3202
|
+
externalTools: BROWSER_EXTERNAL_TOOLS,
|
|
3203
|
+
executeTool: async (name, _input, _toolCallId, onLog) => {
|
|
3204
|
+
if (name === "screenshotFullPage") {
|
|
3205
|
+
try {
|
|
3206
|
+
return await captureAndAnalyzeScreenshot({
|
|
3207
|
+
path: _input.path,
|
|
3208
|
+
onLog
|
|
3209
|
+
});
|
|
3210
|
+
} catch (err) {
|
|
3211
|
+
return `Error taking screenshot: ${err.message}`;
|
|
3212
|
+
}
|
|
3200
3213
|
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
(s) => s.command === "screenshotViewport" && s.result?.url
|
|
3221
|
-
);
|
|
3222
|
-
if (screenshotSteps.length > 0) {
|
|
3223
|
-
const batchInput = screenshotSteps.map((s) => ({
|
|
3224
|
-
stepType: "analyzeImage",
|
|
3225
|
-
step: {
|
|
3226
|
-
imageUrl: s.result.url,
|
|
3227
|
-
prompt: SCREENSHOT_ANALYSIS_PROMPT
|
|
3228
|
-
}
|
|
3229
|
-
}));
|
|
3230
|
-
const batchResult = await runCli(
|
|
3231
|
-
`mindstudio batch --no-meta ${JSON.stringify(JSON.stringify(batchInput))}`,
|
|
3232
|
-
{ timeout: 2e5 }
|
|
3214
|
+
return `Error: unknown local tool "${name}"`;
|
|
3215
|
+
},
|
|
3216
|
+
apiConfig: context.apiConfig,
|
|
3217
|
+
model: context.model,
|
|
3218
|
+
subAgentId: "browserAutomation",
|
|
3219
|
+
signal: context.signal,
|
|
3220
|
+
parentToolId: context.toolCallId,
|
|
3221
|
+
requestId: context.requestId,
|
|
3222
|
+
onEvent: context.onEvent,
|
|
3223
|
+
resolveExternalTool: async (id, name, input2) => {
|
|
3224
|
+
if (!context.resolveExternalTool) {
|
|
3225
|
+
return "Error: no external tool resolver";
|
|
3226
|
+
}
|
|
3227
|
+
const result2 = await context.resolveExternalTool(id, name, input2);
|
|
3228
|
+
if (name === "browserCommand") {
|
|
3229
|
+
try {
|
|
3230
|
+
const parsed = JSON.parse(result2);
|
|
3231
|
+
const screenshotSteps = (parsed.steps || []).filter(
|
|
3232
|
+
(s) => s.command === "screenshotViewport" && s.result?.url
|
|
3233
3233
|
);
|
|
3234
|
-
|
|
3235
|
-
const
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
ai++;
|
|
3234
|
+
if (screenshotSteps.length > 0) {
|
|
3235
|
+
const batchInput = screenshotSteps.map((s) => ({
|
|
3236
|
+
stepType: "analyzeImage",
|
|
3237
|
+
step: {
|
|
3238
|
+
imageUrl: s.result.url,
|
|
3239
|
+
prompt: SCREENSHOT_ANALYSIS_PROMPT
|
|
3241
3240
|
}
|
|
3241
|
+
}));
|
|
3242
|
+
const batchResult = await runCli(
|
|
3243
|
+
`mindstudio batch --no-meta ${JSON.stringify(JSON.stringify(batchInput))}`,
|
|
3244
|
+
{ timeout: 2e5 }
|
|
3245
|
+
);
|
|
3246
|
+
try {
|
|
3247
|
+
const analyses = JSON.parse(batchResult);
|
|
3248
|
+
let ai = 0;
|
|
3249
|
+
for (const step of parsed.steps) {
|
|
3250
|
+
if (step.command === "screenshotViewport" && step.result?.url && ai < analyses.length) {
|
|
3251
|
+
step.result.analysis = analyses[ai]?.output?.analysis || analyses[ai]?.output || "";
|
|
3252
|
+
ai++;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
} catch {
|
|
3256
|
+
log6.debug("Failed to parse batch analysis result", {
|
|
3257
|
+
batchResult
|
|
3258
|
+
});
|
|
3242
3259
|
}
|
|
3243
|
-
|
|
3244
|
-
log6.debug("Failed to parse batch analysis result", {
|
|
3245
|
-
batchResult
|
|
3246
|
-
});
|
|
3260
|
+
return JSON.stringify(parsed);
|
|
3247
3261
|
}
|
|
3248
|
-
|
|
3262
|
+
} catch {
|
|
3249
3263
|
}
|
|
3250
|
-
} catch {
|
|
3251
3264
|
}
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3265
|
+
return result2;
|
|
3266
|
+
},
|
|
3267
|
+
toolRegistry: context.toolRegistry
|
|
3268
|
+
});
|
|
3269
|
+
try {
|
|
3270
|
+
await sidecarRequest("/reset-browser", {}, { timeout: 5e3 });
|
|
3271
|
+
} catch {
|
|
3272
|
+
}
|
|
3273
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3274
|
+
return result.text;
|
|
3275
|
+
} finally {
|
|
3276
|
+
release();
|
|
3260
3277
|
}
|
|
3261
|
-
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3262
|
-
return result.text;
|
|
3263
3278
|
}
|
|
3264
3279
|
};
|
|
3265
3280
|
|
|
@@ -4951,6 +4966,7 @@ var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
|
4951
4966
|
"confirmDestructiveAction",
|
|
4952
4967
|
"runScenario",
|
|
4953
4968
|
"runMethod",
|
|
4969
|
+
"queryDatabase",
|
|
4954
4970
|
"browserCommand",
|
|
4955
4971
|
"setProjectMetadata"
|
|
4956
4972
|
]);
|
package/dist/index.js
CHANGED
|
@@ -2931,7 +2931,16 @@ var init_prompt = __esm({
|
|
|
2931
2931
|
});
|
|
2932
2932
|
|
|
2933
2933
|
// src/subagents/browserAutomation/index.ts
|
|
2934
|
-
|
|
2934
|
+
function acquireBrowserLock() {
|
|
2935
|
+
let release;
|
|
2936
|
+
const next = new Promise((res) => {
|
|
2937
|
+
release = res;
|
|
2938
|
+
});
|
|
2939
|
+
const wait = lockQueue;
|
|
2940
|
+
lockQueue = next;
|
|
2941
|
+
return wait.then(() => release);
|
|
2942
|
+
}
|
|
2943
|
+
var log4, lockQueue, browserAutomationTool;
|
|
2935
2944
|
var init_browserAutomation = __esm({
|
|
2936
2945
|
"src/subagents/browserAutomation/index.ts"() {
|
|
2937
2946
|
"use strict";
|
|
@@ -2943,6 +2952,7 @@ var init_browserAutomation = __esm({
|
|
|
2943
2952
|
init_runCli();
|
|
2944
2953
|
init_logger();
|
|
2945
2954
|
log4 = createLogger("browser-automation");
|
|
2955
|
+
lockQueue = Promise.resolve();
|
|
2946
2956
|
browserAutomationTool = {
|
|
2947
2957
|
clearable: true,
|
|
2948
2958
|
definition: {
|
|
@@ -2963,99 +2973,104 @@ var init_browserAutomation = __esm({
|
|
|
2963
2973
|
if (!context) {
|
|
2964
2974
|
return "Error: browser automation requires execution context (only available in headless mode)";
|
|
2965
2975
|
}
|
|
2976
|
+
const release = await acquireBrowserLock();
|
|
2966
2977
|
try {
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2978
|
+
try {
|
|
2979
|
+
const status = await sidecarRequest(
|
|
2980
|
+
"/browser-status",
|
|
2981
|
+
{},
|
|
2982
|
+
{ timeout: 5e3 }
|
|
2983
|
+
);
|
|
2984
|
+
if (!status.connected) {
|
|
2985
|
+
return "Error: the browser preview is not connected. The user needs to open the preview before browser tests can run.";
|
|
2986
|
+
}
|
|
2987
|
+
} catch {
|
|
2988
|
+
return "Error: could not check browser status. The dev environment may not be running.";
|
|
2974
2989
|
}
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
await
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
}
|
|
2994
|
-
} catch (err) {
|
|
2995
|
-
return `Error taking screenshot: ${err.message}`;
|
|
2990
|
+
try {
|
|
2991
|
+
await sidecarRequest("/reset-browser", {}, { timeout: 5e3 });
|
|
2992
|
+
} catch {
|
|
2993
|
+
}
|
|
2994
|
+
const result = await runSubAgent({
|
|
2995
|
+
system: getBrowserAutomationPrompt(),
|
|
2996
|
+
task: input.task,
|
|
2997
|
+
tools: BROWSER_TOOLS,
|
|
2998
|
+
externalTools: BROWSER_EXTERNAL_TOOLS,
|
|
2999
|
+
executeTool: async (name, _input, _toolCallId, onLog) => {
|
|
3000
|
+
if (name === "screenshotFullPage") {
|
|
3001
|
+
try {
|
|
3002
|
+
return await captureAndAnalyzeScreenshot({
|
|
3003
|
+
path: _input.path,
|
|
3004
|
+
onLog
|
|
3005
|
+
});
|
|
3006
|
+
} catch (err) {
|
|
3007
|
+
return `Error taking screenshot: ${err.message}`;
|
|
3008
|
+
}
|
|
2996
3009
|
}
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
(s) => s.command === "screenshotViewport" && s.result?.url
|
|
3017
|
-
);
|
|
3018
|
-
if (screenshotSteps.length > 0) {
|
|
3019
|
-
const batchInput = screenshotSteps.map((s) => ({
|
|
3020
|
-
stepType: "analyzeImage",
|
|
3021
|
-
step: {
|
|
3022
|
-
imageUrl: s.result.url,
|
|
3023
|
-
prompt: SCREENSHOT_ANALYSIS_PROMPT
|
|
3024
|
-
}
|
|
3025
|
-
}));
|
|
3026
|
-
const batchResult = await runCli(
|
|
3027
|
-
`mindstudio batch --no-meta ${JSON.stringify(JSON.stringify(batchInput))}`,
|
|
3028
|
-
{ timeout: 2e5 }
|
|
3010
|
+
return `Error: unknown local tool "${name}"`;
|
|
3011
|
+
},
|
|
3012
|
+
apiConfig: context.apiConfig,
|
|
3013
|
+
model: context.model,
|
|
3014
|
+
subAgentId: "browserAutomation",
|
|
3015
|
+
signal: context.signal,
|
|
3016
|
+
parentToolId: context.toolCallId,
|
|
3017
|
+
requestId: context.requestId,
|
|
3018
|
+
onEvent: context.onEvent,
|
|
3019
|
+
resolveExternalTool: async (id, name, input2) => {
|
|
3020
|
+
if (!context.resolveExternalTool) {
|
|
3021
|
+
return "Error: no external tool resolver";
|
|
3022
|
+
}
|
|
3023
|
+
const result2 = await context.resolveExternalTool(id, name, input2);
|
|
3024
|
+
if (name === "browserCommand") {
|
|
3025
|
+
try {
|
|
3026
|
+
const parsed = JSON.parse(result2);
|
|
3027
|
+
const screenshotSteps = (parsed.steps || []).filter(
|
|
3028
|
+
(s) => s.command === "screenshotViewport" && s.result?.url
|
|
3029
3029
|
);
|
|
3030
|
-
|
|
3031
|
-
const
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3030
|
+
if (screenshotSteps.length > 0) {
|
|
3031
|
+
const batchInput = screenshotSteps.map((s) => ({
|
|
3032
|
+
stepType: "analyzeImage",
|
|
3033
|
+
step: {
|
|
3034
|
+
imageUrl: s.result.url,
|
|
3035
|
+
prompt: SCREENSHOT_ANALYSIS_PROMPT
|
|
3036
|
+
}
|
|
3037
|
+
}));
|
|
3038
|
+
const batchResult = await runCli(
|
|
3039
|
+
`mindstudio batch --no-meta ${JSON.stringify(JSON.stringify(batchInput))}`,
|
|
3040
|
+
{ timeout: 2e5 }
|
|
3041
|
+
);
|
|
3042
|
+
try {
|
|
3043
|
+
const analyses = JSON.parse(batchResult);
|
|
3044
|
+
let ai = 0;
|
|
3045
|
+
for (const step of parsed.steps) {
|
|
3046
|
+
if (step.command === "screenshotViewport" && step.result?.url && ai < analyses.length) {
|
|
3047
|
+
step.result.analysis = analyses[ai]?.output?.analysis || analyses[ai]?.output || "";
|
|
3048
|
+
ai++;
|
|
3049
|
+
}
|
|
3037
3050
|
}
|
|
3051
|
+
} catch {
|
|
3052
|
+
log4.debug("Failed to parse batch analysis result", {
|
|
3053
|
+
batchResult
|
|
3054
|
+
});
|
|
3038
3055
|
}
|
|
3039
|
-
|
|
3040
|
-
log4.debug("Failed to parse batch analysis result", {
|
|
3041
|
-
batchResult
|
|
3042
|
-
});
|
|
3056
|
+
return JSON.stringify(parsed);
|
|
3043
3057
|
}
|
|
3044
|
-
|
|
3058
|
+
} catch {
|
|
3045
3059
|
}
|
|
3046
|
-
} catch {
|
|
3047
3060
|
}
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3061
|
+
return result2;
|
|
3062
|
+
},
|
|
3063
|
+
toolRegistry: context.toolRegistry
|
|
3064
|
+
});
|
|
3065
|
+
try {
|
|
3066
|
+
await sidecarRequest("/reset-browser", {}, { timeout: 5e3 });
|
|
3067
|
+
} catch {
|
|
3068
|
+
}
|
|
3069
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3070
|
+
return result.text;
|
|
3071
|
+
} finally {
|
|
3072
|
+
release();
|
|
3056
3073
|
}
|
|
3057
|
-
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3058
|
-
return result.text;
|
|
3059
3074
|
}
|
|
3060
3075
|
};
|
|
3061
3076
|
}
|
|
@@ -5494,6 +5509,7 @@ var init_agent = __esm({
|
|
|
5494
5509
|
"confirmDestructiveAction",
|
|
5495
5510
|
"runScenario",
|
|
5496
5511
|
"runMethod",
|
|
5512
|
+
"queryDatabase",
|
|
5497
5513
|
"browserCommand",
|
|
5498
5514
|
"setProjectMetadata"
|
|
5499
5515
|
]);
|
|
@@ -19,7 +19,7 @@ The dev session gets its own database — a snapshot of the live database at ses
|
|
|
19
19
|
- **Truncate** — keep the schema, delete all row data (used by scenarios for a clean canvas)
|
|
20
20
|
- **Schema sync** — add a field to a table interface and it's immediately available in dev
|
|
21
21
|
|
|
22
|
-
The dev database is disposable. Experiment freely — there's no risk of breaking anything.
|
|
22
|
+
The dev database is disposable. Experiment freely — there's no risk of breaking anything. Just be considerate that the user may have created their own data (user rows or other data) while testing, and it might be frustrating for them to have it wiped.
|
|
23
23
|
|
|
24
24
|
### Debugging
|
|
25
25
|
|
|
@@ -96,12 +96,14 @@ Shared setup code can go in `dist/methods/.scenarios/_helpers/`.
|
|
|
96
96
|
## How Scenarios Run
|
|
97
97
|
|
|
98
98
|
When a scenario runs, the platform:
|
|
99
|
-
1. **Truncates** all tables (deletes all rows, preserves schema)
|
|
99
|
+
1. **Truncates** all tables (deletes all rows, preserves schema - unless skipTruncate is true)
|
|
100
100
|
2. **Executes** the seed function (your `db.push()` calls populate the clean database)
|
|
101
101
|
3. **Impersonates** the roles from the scenario's `roles` field (the app renders from that user's perspective)
|
|
102
102
|
|
|
103
103
|
This is deterministic — same scenario always produces the same state.
|
|
104
104
|
|
|
105
|
+
Scenarios are useful for seeding initial app state after build for testing, as well as to give the user a first impression of an app that is already filled with data and looks and feels usable. The user can choose to run further scenarios after initial build by clicking the Scenarios tab and selecting a scenario to run.
|
|
106
|
+
|
|
105
107
|
## Scenario Data
|
|
106
108
|
|
|
107
109
|
Align scenario data to the vibe of the app - construct data that feels like it fits.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
### Verification
|
|
10
10
|
Run `lspDiagnostics` after every turn where you have edited code in any meaningful way. You don't need to run it for things like changing copy or CSS colors, but you should run it after any structural changes to code. It catches syntax errors, broken imports, and type mismatches instantly. After a big build or significant changes, also do a lightweight runtime check to catch the things static analysis misses (schema mismatches, missing imports, bad queries):
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Spot-check methods with `runMethod`. The dev database is a disposable snapshot that will have been seeded with scenario data, so don't worry about being destructive.
|
|
13
13
|
- For frontend work, take a single `screenshot` to confirm the main view renders correctly or look at the browser log for any console errors in the user's preview.
|
|
14
14
|
- Use `runAutomatedBrowserTest` to verify an interactive flow that you can't confirm from a screenshot, or when the user reports something broken that you can't identify from code alone.
|
|
15
15
|
|
|
@@ -38,8 +38,5 @@ For any work involving AI models, external actions (web scraping, email, SMS), o
|
|
|
38
38
|
### State Management
|
|
39
39
|
- Calls to methods introduce latency. When building web frontends that load data from methods, consider front-loading 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.
|
|
40
40
|
|
|
41
|
-
### Build Notes
|
|
42
|
-
For complex builds that span many files — especially an initial buildout from a spec — write a `.remy-notes.md` scratchpad in the project root. Use it to record decisions, keep a checklist of tasks, and reference data you'll need across multiple tool calls: design tokens, color values, typography specs, image URLs, what's been built so far, what's left. Read it back instead of restating everything in your messages. Delete it when the build is done. Don't use this for small changes or single-file edits.
|
|
43
|
-
|
|
44
41
|
### Dependencies
|
|
45
42
|
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.
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
- After two failed attempts at the same approach, tell the user what's going wrong.
|
|
16
16
|
- Pushing to main branch will trigger a deploy. The user presses the publish button in the interface to request publishing.
|
|
17
17
|
|
|
18
|
+
### Build Notes
|
|
19
|
+
For complex tasks — especially an initial buildout from a spec or making multiple changes in a single turn — write a `.remy-notes.md` scratchpad in the project root. Use it to record decisions, keep a checklist of tasks, and reference data you'll need across multiple tool calls: design tokens, color values, typography specs, image URLs, what's been built so far, what's left. Read it back instead of restating everything in your messages. Delete it when your work is done.
|
|
20
|
+
|
|
18
21
|
## Communication
|
|
19
22
|
The user can already see your tool calls, so most of your work is visible without narration. Focus text output on three things:
|
|
20
23
|
- **Decisions that need input.** Questions, tradeoffs, ambiguity that blocks progress.
|
|
@@ -35,6 +35,10 @@ These are recurring mistakes the coding agent makes. If you see the conditions f
|
|
|
35
35
|
|
|
36
36
|
- **CSS Module animation scoping.** If the agent defines `@keyframes` in a global CSS file but references the animation name from a CSS Module, the animation will silently fail. CSS Modules scope animation names, so a keyframe defined globally can't be found by a scoped class. The fix: define keyframes in the same CSS Module that uses them, or use `:global()` to escape the scoping.
|
|
37
37
|
|
|
38
|
+
- **Too many granular API calls.** These apps are MVPs with small datasets. If the plan has separate method calls for every screen or sub-view (load profile, then load posts, then load post detail, then load comments), flag it. Favor fewer, fatter requests — a profile page that loads posts with full content means tapping a post is instant. A feed that includes comment previews and like state means the detail view renders from memory. Over-fetching at this scale is almost always the right call — users notice instant transitions, they don't notice a slightly larger payload.
|
|
39
|
+
|
|
40
|
+
- **Wouter is not React Router.** The agent defaults to React Router patterns which silently break in wouter. Key differences: no `useNavigate()` (use `const [, setLocation] = useLocation()`), no `navigate(-1)` for back (use `window.history.back()`), no `element` prop on Route (use `component={Foo}` or children), no `<Routes>` (use `<Switch>` — without it all matching routes render simultaneously), no `<Navigate>` (use `<Redirect>`), no `<Outlet>` for nested routes (use `nest` prop on Route), and no `useSearchParams()` from react-router (wouter has its own version with a different setter API). If you see any of these React Router patterns in a wouter project, flag it.
|
|
41
|
+
|
|
38
42
|
## When to stay quiet
|
|
39
43
|
|
|
40
44
|
Nits, style preferences, missing edge cases, things the agent will figure out as it goes, patterns that are "not ideal but fine," minor code smells. Let them slide. The agent is busy.
|
|
@@ -32,11 +32,13 @@ Pay close attention to text streaming when the AI replies - it should feel natur
|
|
|
32
32
|
|
|
33
33
|
### Wireframes
|
|
34
34
|
|
|
35
|
-
When a pattern or interaction is hard to convey in words alone — an animation sequence, a swipe gesture, a layout grid — you can include a small interactive wireframe to demonstrate it. Use a markdown code fence with `wireframe` as the type. Start with a YAML frontmatter block (`name` and `description`) to identify the component, then the self-contained HTML+CSS prototype.
|
|
35
|
+
When a pattern or interaction is hard to convey in words alone — a core component, an animation sequence, a swipe gesture, a layout grid — you can include a small interactive wireframe to demonstrate it. Use a markdown code fence with `wireframe` as the type. Start with a YAML frontmatter block (`name` and `description`) to identify the component, then the self-contained HTML+CSS prototype.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Use wireframes instead of ASCII art and code-block diagrams you might otherwise reach for when trying to show a layout or interaction. Wireframes are better because the developer can actually see and interact with the result. Like those diagrams, they isolate one small piece: a single card component, a button animation, a transition, a grid layout. Each wireframe should be around 60-80 lines of HTML+CSS — if you're past 100 lines, you're building too much. These are not screens, flows, or multi-step prototypes. They render in a small iframe and should look complete at that scale. Most of your communication should be in words - wireframes are simply another tool when you need them. Never build out full screens or pages in wireframes, even if you are asked to - this is critically important.
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Remember, never use ascii art or code-block diagrams to describe layouts - always use wireframes.
|
|
40
|
+
|
|
41
|
+
The wireframe code will be rendered in a transparent iframe. Don't fill the viewport or add a background color to the body. Place the component at a natural size in a card with a background color that is centered vertically and horizontally in the viewport. Keep the component tight and self-contained. The iframe is for the component only — no annotations, labels, or explanatory text inside it. Put your notes and implementation guidance in the markdown around the wireframe. Wireframes can be interactive and are especially useful for demonstrating states, animations, effects, and transitions. If your wireframe has triggers or states, include a small "play" control button within the frame (make sure to allow reply/reset for all interactivity). No images - these are functional prototypes meant to demonstrate feel and behavior, not visual comps.
|
|
40
42
|
|
|
41
43
|
```wireframe
|
|
42
44
|
---
|