@mindstudio-ai/remy 0.1.282 → 0.1.284

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.
@@ -19,7 +19,7 @@ Then, build everything in one turn: tables, methods, interfaces, manifest update
19
19
  - First, run use `runScenario` to seed test data, then use `runMethod` to confirm important methods work.
20
20
  - If the app has a web frontend, check the browser logs to make sure there are no errors rendering it.
21
21
  - Use `runAutomatedBrowserTest` to smoke-test the main UI flow. The dev database is a disposable snapshot, so don't worry about being destructive. Fix any errors before finishing.
22
- - If there is a scenario that seeds the app with mock data, use it to present the app to the user with initial data seeded, so they can see and play with the real app. Let the user know they can reset the app using a scenario to empty it if they wish. Showing the user something they can play with immediately is important when it comes to landing a strong first impression.
22
+ - If there is a scenario that seeds the app with mock data, use it to present the app to the user with initial data seeded, so they can see and play with the real app. Let the user know they can reset the app using a scenario to empty it if they wish. Showing the user something they can play with immediately is important when it comes to landing a strong first impression. For apps with auth, the preview opens on the app's sign-in screen by design — that's the front door working, not a bug. Don't try to pre-authenticate the user or route around it; tell them the sign-in helper auto-fills the test account (`remy@mindstudio.ai`, code `123456`), so they're in with one tap.
23
23
 
24
24
  ## When you are done
25
25
 
package/dist/headless.js CHANGED
@@ -2709,8 +2709,8 @@ function formatSize(bytes) {
2709
2709
  }
2710
2710
  async function formatFile(dirPath, name, indent) {
2711
2711
  try {
2712
- const stat2 = await fs16.stat(path8.join(dirPath, name));
2713
- return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat2.size)}`;
2712
+ const stat3 = await fs16.stat(path8.join(dirPath, name));
2713
+ return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat3.size)}`;
2714
2714
  } catch {
2715
2715
  return `${indent}${name}`;
2716
2716
  }
@@ -2936,7 +2936,7 @@ var restartProcessTool = {
2936
2936
  var runScenarioTool = {
2937
2937
  definition: {
2938
2938
  name: "runScenario",
2939
- description: "Run a scenario to seed the dev database with test data. By default truncates all tables first, then executes the seed function and impersonates the scenario roles. Use skipTruncate to run the seed function against existing data without resetting. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details. Return synchronously - no need to sleep before checking results.",
2939
+ description: "Run a scenario to seed the dev database with test data. By default truncates all tables first, then executes the seed function and assigns the scenario's roles to the dev test user (a real user row; the roles persist on it until changed). Nobody gets signed in: the preview still shows the app's own sign-in, where the dev helper auto-fills the test account. Use skipTruncate to run the seed function against existing data without resetting. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details. Returns synchronously - no need to sleep before checking results.",
2940
2940
  inputSchema: {
2941
2941
  type: "object",
2942
2942
  properties: {
@@ -2975,12 +2975,12 @@ var runMethodTool = {
2975
2975
  },
2976
2976
  userId: {
2977
2977
  type: "string",
2978
- description: `Optional. Run the method as a specific user. Pass "testUser" to auto-auth as the default test user (the sandbox handles user creation/lookup \u2014 no scenario setup needed); works for email-code, sms-code, and "Sign in with Remy" apps (for sign-in-with-remy apps it resolves to the developer's own delegated identity rather than the test user). Or pass a real user ID from scenario-seeded data for a specific user. Overrides session-level impersonation for this call only.`
2978
+ description: `Optional. Run the method as a specific user. Pass "testUser" to auto-auth as the default test user (the sandbox handles user creation/lookup \u2014 no scenario setup needed); works for email-code, sms-code, and "Sign in with Remy" apps (for sign-in-with-remy apps it resolves to the developer's own delegated identity rather than the test user). Or pass a real user ID from scenario-seeded data for a specific user.`
2979
2979
  },
2980
2980
  roles: {
2981
2981
  type: "array",
2982
2982
  items: { type: "string" },
2983
- description: 'Optional. Role names for this request (e.g. ["admin"]). Combine with `userId` to test a specific role, or use alone to test role-gated logic without a full identity. Overrides session-level impersonation for this call only.'
2983
+ description: 'Optional. Role names for this request (e.g. ["admin"]). For auth-enabled apps, roles without a userId run as the dev test user holding exactly these roles \u2014 a real user row, so `auth.userId` and `requireRole` behave like production. For apps without auth, roles attach to an anonymous call. Applies to this call only.'
2984
2984
  }
2985
2985
  },
2986
2986
  required: ["method"]
@@ -4505,7 +4505,7 @@ async function runBrowserAutomation(task, context, opts) {
4505
4505
  await sidecarRequest(
4506
4506
  "/set-viewport",
4507
4507
  { mode: "default" },
4508
- { timeout: 2e4 }
4508
+ { timeout: 25e3 }
4509
4509
  );
4510
4510
  } catch {
4511
4511
  }
@@ -4524,7 +4524,7 @@ async function runBrowserAutomation(task, context, opts) {
4524
4524
  auth: _input.auth,
4525
4525
  path: _input.path
4526
4526
  },
4527
- { timeout: 15e3 }
4527
+ { timeout: 2e4 }
4528
4528
  );
4529
4529
  return JSON.stringify(result2);
4530
4530
  } catch (err) {
@@ -5570,20 +5570,24 @@ __export(createWireframe_exports, {
5570
5570
  definition: () => definition10,
5571
5571
  execute: () => execute10
5572
5572
  });
5573
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
5573
+ import { mkdir as mkdir2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
5574
5574
  import { join as join2 } from "path";
5575
5575
  var log9 = createLogger("createWireframe");
5576
5576
  var WIREFRAMES_DIR = "src/.wireframes";
5577
5577
  var UPLOAD_TIMEOUT_MS2 = 3e4;
5578
5578
  var definition10 = {
5579
5579
  name: "createWireframe",
5580
- description: "Create a wireframe from self-contained HTML+CSS. Returns a markdown reference line \u2014 include that exact line in your response wherever the wireframe belongs, with your notes in the surrounding prose. To revise a wireframe, create a new one (read the old file first if you are iterating on it).",
5580
+ description: "Create (or revise) a wireframe from self-contained HTML+CSS. The wireframe is saved to src/.wireframes/{slug}.html \u2014 reference it in your response and in specs as ![name](src/.wireframes/{slug}.html), with your notes in the surrounding prose. Calling again with the same slug overwrites the wireframe in place, so a revision keeps its path and existing references stay current.",
5581
5581
  inputSchema: {
5582
5582
  type: "object",
5583
5583
  properties: {
5584
5584
  name: {
5585
5585
  type: "string",
5586
- description: 'Short display name, e.g. "Feed Post Card". Becomes the caption and the filename slug.'
5586
+ description: 'Short display name, e.g. "Feed Post Card". Becomes the caption.'
5587
+ },
5588
+ slug: {
5589
+ type: "string",
5590
+ description: 'Filename stem, lowercase kebab-case (e.g. "feed-post-card"). The wireframe lives at src/.wireframes/{slug}.html. Re-use a slug to revise that wireframe in place.'
5587
5591
  },
5588
5592
  description: {
5589
5593
  type: "string",
@@ -5594,20 +5598,24 @@ var definition10 = {
5594
5598
  description: "The complete HTML document (<html>\u2026</html>), self-contained vanilla HTML/CSS/JS. No frontmatter \u2014 it is added for you."
5595
5599
  }
5596
5600
  },
5597
- required: ["name", "description", "html"]
5601
+ required: ["name", "slug", "description", "html"]
5598
5602
  }
5599
5603
  };
5600
- function slugify(name) {
5601
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "") || "wireframe";
5602
- }
5604
+ var SLUG_RE = /^[a-z0-9][a-z0-9-]{0,79}$/;
5603
5605
  function singleLine(value) {
5604
5606
  return value.replace(/\s+/g, " ").trim();
5605
5607
  }
5606
- function validate(name, html) {
5608
+ function validate(name, slug, html) {
5607
5609
  const warnings = [];
5608
5610
  if (!name.trim()) {
5609
5611
  return { error: "Error: name is required.", warnings };
5610
5612
  }
5613
+ if (!SLUG_RE.test(slug)) {
5614
+ return {
5615
+ error: 'Error: slug must be lowercase kebab-case ([a-z0-9-], starting with a letter or digit, \u2264 80 chars), e.g. "feed-post-card".',
5616
+ warnings
5617
+ };
5618
+ }
5611
5619
  if (!html.trim()) {
5612
5620
  return { error: "Error: html is required.", warnings };
5613
5621
  }
@@ -5666,13 +5674,13 @@ async function execute10(input, onLog, context) {
5666
5674
  return "Error: createWireframe requires execution context";
5667
5675
  }
5668
5676
  const name = String(input.name ?? "");
5677
+ const slug = String(input.slug ?? "");
5669
5678
  const description = String(input.description ?? "");
5670
5679
  const html = String(input.html ?? "");
5671
- const { error, warnings } = validate(name, html);
5680
+ const { error, warnings } = validate(name, slug, html);
5672
5681
  if (error) {
5673
5682
  return error;
5674
5683
  }
5675
- const slug = `${slugify(name)}-${Math.random().toString(36).slice(2, 8)}`;
5676
5684
  const relPath = `${WIREFRAMES_DIR}/${slug}.html`;
5677
5685
  const content = [
5678
5686
  "---",
@@ -5683,6 +5691,10 @@ async function execute10(input, onLog, context) {
5683
5691
  ""
5684
5692
  ].join("\n");
5685
5693
  await mkdir2(join2(PROJECT_ROOT, WIREFRAMES_DIR), { recursive: true });
5694
+ const existed = await stat2(join2(PROJECT_ROOT, relPath)).then(
5695
+ () => true,
5696
+ () => false
5697
+ );
5686
5698
  await writeFile2(join2(PROJECT_ROOT, relPath), content, "utf8");
5687
5699
  onLog?.(`Wrote ${relPath}, mirroring for preview...`);
5688
5700
  const mirror = await uploadMirror(context, slug, content);
@@ -5690,7 +5702,7 @@ async function execute10(input, onLog, context) {
5690
5702
  log9.warn("Wireframe mirror upload failed", { slug, note: mirror.note });
5691
5703
  }
5692
5704
  const lines = [
5693
- `Created wireframe "${singleLine(name)}" at ${relPath}.`,
5705
+ `${existed ? "Revised" : "Created"} wireframe "${singleLine(name)}" at ${relPath}.${existed ? " Existing references to this path now show the new version." : ""}`,
5694
5706
  `Reference it in your response and in specs with exactly: ![${singleLine(name)}](${relPath})`
5695
5707
  ];
5696
5708
  if (!mirror.ok) {
package/dist/index.js CHANGED
@@ -3816,8 +3816,8 @@ function formatSize(bytes) {
3816
3816
  }
3817
3817
  async function formatFile(dirPath, name, indent) {
3818
3818
  try {
3819
- const stat2 = await fs15.stat(path8.join(dirPath, name));
3820
- return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat2.size)}`;
3819
+ const stat3 = await fs15.stat(path8.join(dirPath, name));
3820
+ return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat3.size)}`;
3821
3821
  } catch {
3822
3822
  return `${indent}${name}`;
3823
3823
  }
@@ -4089,7 +4089,7 @@ var init_runScenario = __esm({
4089
4089
  runScenarioTool = {
4090
4090
  definition: {
4091
4091
  name: "runScenario",
4092
- description: "Run a scenario to seed the dev database with test data. By default truncates all tables first, then executes the seed function and impersonates the scenario roles. Use skipTruncate to run the seed function against existing data without resetting. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details. Return synchronously - no need to sleep before checking results.",
4092
+ description: "Run a scenario to seed the dev database with test data. By default truncates all tables first, then executes the seed function and assigns the scenario's roles to the dev test user (a real user row; the roles persist on it until changed). Nobody gets signed in: the preview still shows the app's own sign-in, where the dev helper auto-fills the test account. Use skipTruncate to run the seed function against existing data without resetting. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details. Returns synchronously - no need to sleep before checking results.",
4093
4093
  inputSchema: {
4094
4094
  type: "object",
4095
4095
  properties: {
@@ -4134,12 +4134,12 @@ var init_runMethod = __esm({
4134
4134
  },
4135
4135
  userId: {
4136
4136
  type: "string",
4137
- description: `Optional. Run the method as a specific user. Pass "testUser" to auto-auth as the default test user (the sandbox handles user creation/lookup \u2014 no scenario setup needed); works for email-code, sms-code, and "Sign in with Remy" apps (for sign-in-with-remy apps it resolves to the developer's own delegated identity rather than the test user). Or pass a real user ID from scenario-seeded data for a specific user. Overrides session-level impersonation for this call only.`
4137
+ description: `Optional. Run the method as a specific user. Pass "testUser" to auto-auth as the default test user (the sandbox handles user creation/lookup \u2014 no scenario setup needed); works for email-code, sms-code, and "Sign in with Remy" apps (for sign-in-with-remy apps it resolves to the developer's own delegated identity rather than the test user). Or pass a real user ID from scenario-seeded data for a specific user.`
4138
4138
  },
4139
4139
  roles: {
4140
4140
  type: "array",
4141
4141
  items: { type: "string" },
4142
- description: 'Optional. Role names for this request (e.g. ["admin"]). Combine with `userId` to test a specific role, or use alone to test role-gated logic without a full identity. Overrides session-level impersonation for this call only.'
4142
+ description: 'Optional. Role names for this request (e.g. ["admin"]). For auth-enabled apps, roles without a userId run as the dev test user holding exactly these roles \u2014 a real user row, so `auth.userId` and `requireRole` behave like production. For apps without auth, roles attach to an anonymous call. Applies to this call only.'
4143
4143
  }
4144
4144
  },
4145
4145
  required: ["method"]
@@ -5542,7 +5542,7 @@ async function runBrowserAutomation(task, context, opts) {
5542
5542
  await sidecarRequest(
5543
5543
  "/set-viewport",
5544
5544
  { mode: "default" },
5545
- { timeout: 2e4 }
5545
+ { timeout: 25e3 }
5546
5546
  );
5547
5547
  } catch {
5548
5548
  }
@@ -5561,7 +5561,7 @@ async function runBrowserAutomation(task, context, opts) {
5561
5561
  auth: _input.auth,
5562
5562
  path: _input.path
5563
5563
  },
5564
- { timeout: 15e3 }
5564
+ { timeout: 2e4 }
5565
5565
  );
5566
5566
  return JSON.stringify(result2);
5567
5567
  } catch (err) {
@@ -6752,19 +6752,22 @@ __export(createWireframe_exports, {
6752
6752
  definition: () => definition10,
6753
6753
  execute: () => execute10
6754
6754
  });
6755
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
6755
+ import { mkdir as mkdir2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
6756
6756
  import { join as join2 } from "path";
6757
- function slugify(name) {
6758
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "") || "wireframe";
6759
- }
6760
6757
  function singleLine(value) {
6761
6758
  return value.replace(/\s+/g, " ").trim();
6762
6759
  }
6763
- function validate(name, html) {
6760
+ function validate(name, slug, html) {
6764
6761
  const warnings = [];
6765
6762
  if (!name.trim()) {
6766
6763
  return { error: "Error: name is required.", warnings };
6767
6764
  }
6765
+ if (!SLUG_RE.test(slug)) {
6766
+ return {
6767
+ error: 'Error: slug must be lowercase kebab-case ([a-z0-9-], starting with a letter or digit, \u2264 80 chars), e.g. "feed-post-card".',
6768
+ warnings
6769
+ };
6770
+ }
6768
6771
  if (!html.trim()) {
6769
6772
  return { error: "Error: html is required.", warnings };
6770
6773
  }
@@ -6823,13 +6826,13 @@ async function execute10(input, onLog, context) {
6823
6826
  return "Error: createWireframe requires execution context";
6824
6827
  }
6825
6828
  const name = String(input.name ?? "");
6829
+ const slug = String(input.slug ?? "");
6826
6830
  const description = String(input.description ?? "");
6827
6831
  const html = String(input.html ?? "");
6828
- const { error, warnings } = validate(name, html);
6832
+ const { error, warnings } = validate(name, slug, html);
6829
6833
  if (error) {
6830
6834
  return error;
6831
6835
  }
6832
- const slug = `${slugify(name)}-${Math.random().toString(36).slice(2, 8)}`;
6833
6836
  const relPath = `${WIREFRAMES_DIR}/${slug}.html`;
6834
6837
  const content = [
6835
6838
  "---",
@@ -6840,6 +6843,10 @@ async function execute10(input, onLog, context) {
6840
6843
  ""
6841
6844
  ].join("\n");
6842
6845
  await mkdir2(join2(PROJECT_ROOT, WIREFRAMES_DIR), { recursive: true });
6846
+ const existed = await stat2(join2(PROJECT_ROOT, relPath)).then(
6847
+ () => true,
6848
+ () => false
6849
+ );
6843
6850
  await writeFile2(join2(PROJECT_ROOT, relPath), content, "utf8");
6844
6851
  onLog?.(`Wrote ${relPath}, mirroring for preview...`);
6845
6852
  const mirror = await uploadMirror(context, slug, content);
@@ -6847,7 +6854,7 @@ async function execute10(input, onLog, context) {
6847
6854
  log10.warn("Wireframe mirror upload failed", { slug, note: mirror.note });
6848
6855
  }
6849
6856
  const lines = [
6850
- `Created wireframe "${singleLine(name)}" at ${relPath}.`,
6857
+ `${existed ? "Revised" : "Created"} wireframe "${singleLine(name)}" at ${relPath}.${existed ? " Existing references to this path now show the new version." : ""}`,
6851
6858
  `Reference it in your response and in specs with exactly: ![${singleLine(name)}](${relPath})`
6852
6859
  ];
6853
6860
  if (!mirror.ok) {
@@ -6860,7 +6867,7 @@ async function execute10(input, onLog, context) {
6860
6867
  }
6861
6868
  return lines.join("\n");
6862
6869
  }
6863
- var log10, WIREFRAMES_DIR, UPLOAD_TIMEOUT_MS2, definition10;
6870
+ var log10, WIREFRAMES_DIR, UPLOAD_TIMEOUT_MS2, definition10, SLUG_RE;
6864
6871
  var init_createWireframe = __esm({
6865
6872
  "src/subagents/designExpert/tools/createWireframe.ts"() {
6866
6873
  "use strict";
@@ -6871,13 +6878,17 @@ var init_createWireframe = __esm({
6871
6878
  UPLOAD_TIMEOUT_MS2 = 3e4;
6872
6879
  definition10 = {
6873
6880
  name: "createWireframe",
6874
- description: "Create a wireframe from self-contained HTML+CSS. Returns a markdown reference line \u2014 include that exact line in your response wherever the wireframe belongs, with your notes in the surrounding prose. To revise a wireframe, create a new one (read the old file first if you are iterating on it).",
6881
+ description: "Create (or revise) a wireframe from self-contained HTML+CSS. The wireframe is saved to src/.wireframes/{slug}.html \u2014 reference it in your response and in specs as ![name](src/.wireframes/{slug}.html), with your notes in the surrounding prose. Calling again with the same slug overwrites the wireframe in place, so a revision keeps its path and existing references stay current.",
6875
6882
  inputSchema: {
6876
6883
  type: "object",
6877
6884
  properties: {
6878
6885
  name: {
6879
6886
  type: "string",
6880
- description: 'Short display name, e.g. "Feed Post Card". Becomes the caption and the filename slug.'
6887
+ description: 'Short display name, e.g. "Feed Post Card". Becomes the caption.'
6888
+ },
6889
+ slug: {
6890
+ type: "string",
6891
+ description: 'Filename stem, lowercase kebab-case (e.g. "feed-post-card"). The wireframe lives at src/.wireframes/{slug}.html. Re-use a slug to revise that wireframe in place.'
6881
6892
  },
6882
6893
  description: {
6883
6894
  type: "string",
@@ -6888,9 +6899,10 @@ var init_createWireframe = __esm({
6888
6899
  description: "The complete HTML document (<html>\u2026</html>), self-contained vanilla HTML/CSS/JS. No frontmatter \u2014 it is added for you."
6889
6900
  }
6890
6901
  },
6891
- required: ["name", "description", "html"]
6902
+ required: ["name", "slug", "description", "html"]
6892
6903
  }
6893
6904
  };
6905
+ SLUG_RE = /^[a-z0-9][a-z0-9-]{0,79}$/;
6894
6906
  }
6895
6907
  });
6896
6908
 
@@ -126,4 +126,4 @@
126
126
  | `description` | `string` | No | What state this scenario creates |
127
127
  | `path` | `string` | Yes | Path to the TypeScript file |
128
128
  | `export` | `string` | Yes | Named export (the async function) |
129
- | `roles` | `string[]` | Yes | Roles to impersonate after seeding |
129
+ | `roles` | `string[]` | Yes | Roles assigned to the dev test user after seeding |
@@ -430,7 +430,7 @@ Auth works the same in dev/preview as in production — real verification codes
430
430
 
431
431
  All other emails and phone numbers receive real codes. There is no dev-mode bypass, no fake code, and no way to skip verification. When testing auth flows in the preview, use one of the test bypasses above or a real email/phone.
432
432
 
433
- The `runMethod` tool's `userId: "testUser"` shortcut resolves to this same dev-bypass identity. The platform find-or-creates a real users-table row for it on first call and caches the row's UUID for the rest of the dev session. **`auth.userId` inside the method is that UUID — not the literal string `"testUser"`.** The user row already exists, so don't try to insert it. If you need the UUID to seed app-specific rows that reference it (profiles, preferences, foreign keys), read it from any method response or query the users table directly: `SELECT id FROM users WHERE email = 'remy@mindstudio.ai'` (or `phone = '+15555555555'` for SMS-auth apps).
433
+ This test account is the dev's standing identity: the preview's sign-in helper auto-fills it, the editor's Roles column edits its roles, and a scenario's `roles` field assigns roles to it after seeding. The `runMethod` tool's `userId: "testUser"` shortcut resolves to this same dev-bypass identity (as does `roles` without a `userId`). The platform find-or-creates a real users-table row for it on first call and caches the row's UUID for the rest of the dev session. **`auth.userId` inside the method is that UUID — not the literal string `"testUser"`.** The user row already exists, so don't try to insert it. If you need the UUID to seed app-specific rows that reference it (profiles, preferences, foreign keys), read it from any method response or query the users table directly: `SELECT id FROM users WHERE email = 'remy@mindstudio.ai'` (or `phone = '+15555555555'` for SMS-auth apps).
434
434
 
435
435
  For **"Sign in with Remy"** apps (`auth.methods` is `["remy"]`, with no `email-code`/`sms-code`), `testUser` — and `setupBrowser` — resolve to **the developer's own delegated Remy identity**, not the `remy@mindstudio.ai` code-bypass user. `auth.userId` is still that user's real UUID, but the `remy@mindstudio.ai` email lookup above does not apply — read the UUID from a method response instead.
436
436
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: Scenarios
3
- what: Seed scripts that reset the dev database to a specific state — the platform truncates all tables, runs an async function of plain `db.push()` calls, then impersonates a role, so the same scenario always produces the same state. They're how the user tests the app from each role's perspective and how a freshly built app makes its first impression already populated with data that fits its vibe. Declared in the manifest, written at `dist/methods/.scenarios/`.
3
+ what: Seed scripts that reset the dev database to a specific state — the platform truncates all tables, runs an async function of plain `db.push()` calls, then assigns the scenario's roles to the dev test user, so the same scenario always produces the same state. They're how the user tests the app from each role's perspective (by signing in as the test account) and how a freshly built app makes its first impression already populated with data that fits its vibe. Declared in the manifest, written at `dist/methods/.scenarios/`.
4
4
  when: Before writing or editing a scenario — including the initial build, where scenarios are required. Covers file placement and imports, truncate semantics, what scenarios must not touch (file stores, data sources), and seeding realistic data and bespoke images.
5
5
  ---
6
6
 
@@ -42,7 +42,7 @@ In `mindstudio.json`:
42
42
  | `description` | What state this scenario creates |
43
43
  | `path` | Path to the TypeScript file |
44
44
  | `export` | Named export (the async function) |
45
- | `roles` | Roles to impersonate after seeding |
45
+ | `roles` | Roles assigned to the dev test user after seeding (requires app auth) |
46
46
 
47
47
  ## Writing a Scenario
48
48
 
@@ -104,10 +104,12 @@ Shared setup code can go in `dist/methods/.scenarios/_helpers/`.
104
104
  When a scenario runs, the platform:
105
105
  1. **Truncates** all tables (deletes all rows, preserves schema - unless skipTruncate is true)
106
106
  2. **Executes** the seed function (your `db.push()` calls populate the clean database)
107
- 3. **Impersonates** the roles from the scenario's `roles` field (the app renders from that user's perspective)
107
+ 3. **Assigns** the roles from the scenario's `roles` field to the dev test user — a real write to that user's row, so it requires app auth to be enabled
108
108
 
109
109
  This is deterministic — same scenario always produces the same state.
110
110
 
111
+ Nobody gets signed in by a scenario. The preview shows the app's own sign-in screen, where the dev helper auto-fills the test account (`remy@mindstudio.ai`, code `123456`) — one tap and the user is in as the test user, seeing the app from the scenario's role's perspective. The roles persist on the test user until another scenario or the Roles column changes them.
112
+
111
113
  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.
112
114
 
113
115
  ## What scenarios don't touch
@@ -45,7 +45,7 @@ The SDK also includes a `reportIssue` method that can file bug reports on the ap
45
45
  - For signup and login, verification code inputs must feel polished — clear feedback on send, auto-send on paste, a "resend" option, and error messages for wrong/expired codes.
46
46
  - The auth table is the user profile. Add custom fields (displayName, avatar, plan, etc.) alongside the platform-managed columns. Don't create a separate profile table.
47
47
  - When delegated sign-in ("Sign in with Remy") is available for the org, prefer a "Continue with {Org}" button (`auth.signInWithRemy()`) and call `auth.handleRemyRedirect()` once on app load. Drive UI off `onAuthStateChanged`, not the sign-in return value (top-level sign-in redirects away and never returns). Load the `auth` skill for the full flow reference.
48
- - For apps with roles, create scenarios that seed users with different roles so the developer can test each perspective. Use the scenario `roles` field for impersonation.
48
+ - For apps with roles, create one scenario per role so the developer can test each perspective: the scenario's `roles` field assigns those roles to the dev test user, and signing in as that account (the preview's sign-in helper auto-fills it) shows the app from that role's perspective.
49
49
 
50
50
  ### CSS & Layout
51
51
  - Prefer CSS grid for page-level layout, flex for component-level alignment.
@@ -12,7 +12,7 @@ Your designer. Consult for any visual decision — choosing a color, picking fon
12
12
 
13
13
  The design expert cannot see your conversation with the user, so include relevant context and requirements in your task. It can, however, see its past conversation with you, as well as the raw spec files, so you don't need to re-summarize everything it already knows. Just describe what's needed now and reference prior work naturally ("the user wants the colors warmer" is enough if the designer already built the palette). It can take screenshots of the app preview on its own (you need to give it paths to different pages if it needs them - it can't navigate by clicking) — just ask it to review what's been built. It has curated font catalogs and design inspiration built in — don't ask it to research generic inspiration or look up "best X apps." Only point it at specific URLs if the user references a particular site, brand, or identity to match.
14
14
 
15
- The designer will return concrete resources: hex values, font names with CSS URLs, image URLs, layout descriptions, as well as specific techniques, CSS properties, animation timings, code snippets, and other values. Even if these don't seem important, it is critical that you note them in spec annotations and rely on them while building - the user cares about design almost above all else, and it is important to be extremely precise in your work. The designer can also return code-fenced typography and color schemes (self-contained HTML and CSS) - write these directly into specs for future reference. Wireframes arrive as file references like `![Feed Post Card](src/.wireframes/feed-post-card-x7f2.html)`: copy the reference line into specs verbatim (it renders as a visual preview in chat and in the spec), and read the file at that path while building to get the exact markup and CSS the designer specified.
15
+ The designer will return concrete resources: hex values, font names with CSS URLs, image URLs, layout descriptions, as well as specific techniques, CSS properties, animation timings, code snippets, and other values. Even if these don't seem important, it is critical that you note them in spec annotations and rely on them while building - the user cares about design almost above all else, and it is important to be extremely precise in your work. The designer can also return code-fenced typography and color schemes (self-contained HTML and CSS) - write these directly into specs for future reference. Wireframes arrive as file references like `![Feed Post Card](src/.wireframes/feed-post-card.html)`: copy the reference line into specs verbatim (it renders as a visual preview in chat and in the spec), and read the file at that path while building to get the exact markup and CSS the designer specified. When the designer revises a wireframe it overwrites the same path, so existing references stay current.
16
16
 
17
17
  When delegating, describe the design problem — where the asset will be used, what it needs to communicate, what the brand feels like. Do not specify technical details like image formats, pixel dimensions, generation techniques, or workarounds. The design expert makes those decisions.
18
18
 
@@ -11,13 +11,13 @@ The user is watching the automation happen on their screen in real-time. When ty
11
11
  ### Auth Testing
12
12
  When the content you need to test is behind authentication, use the `setupBrowser` tool to automatically pre-authenticate instead of manually navigating login flows. This mints a session cookie, reloads the page with the authenticated state, and optionally navigates to a starting path. Use `remy@mindstudio.ai` as the email. If the test requires a specific role, pass it in the `roles` array. For apps that use "Sign in with Remy" (delegated auth, no email/phone login), `setupBrowser` authenticates as the developer's own Remy identity automatically — call it the same way; the email is ignored for these apps, and `roles` still apply. Do not try to click through the "Sign in with Remy" button manually.
13
13
 
14
- If you need to test the login/signup flow itself (e.g., verifying the UI, error states, or the verification code input), navigate it manually: use `remy@mindstudio.ai` for email and `+15551234567` for phone. In the dev environment, verification codes are bypassed for this email and any 555-prefixed phone number enter any 6-digit code (e.g., `123456`).
14
+ If you need to test the login/signup flow itself (e.g., verifying the UI, error states, or the verification code input), navigate it manually: use `remy@mindstudio.ai` for email and `+15551234567` for phone. In the dev environment, verification for this email and any 555-prefixed phone number accepts the code `123456`.
15
15
 
16
16
  To test as a **signed-out visitor** (public pages, landing/join links), call `setupBrowser` with NO `auth` — it clears the auth cookie and reloads at the given path, giving you a clean unauthenticated session. Combine with `navigate` + `fresh: true` when you need a fresh-document view of an entry page mid-run.
17
17
 
18
18
  ## Browser Commands
19
19
 
20
- Your session always starts on the app root / in a logged out/unauthenticated state, on a freshly reloaded page running the current code — any changes made since the last run are already picked up. Never restart the dev server (or reload manually) to clear a "stale bundle"; that staleness cannot survive the start-of-run refresh. Use `setupBrowser` to authenticate before testing protected pages.
20
+ Your session always starts on the app root / in a logged out/unauthenticated state, on a freshly reloaded page running the current code (scenarios seed data and set the test user's roles but never create browser sessions) — any changes made since the last run are already picked up. Never restart the dev server (or reload manually) to clear a "stale bundle"; that staleness cannot survive the start-of-run refresh. Use `setupBrowser` to authenticate before testing protected pages.
21
21
 
22
22
  ### Snapshot format
23
23
 
@@ -16,7 +16,7 @@ Think about the ways you can truly elevate the design. Use image generation to c
16
16
  - After you've taken a screenshot, use analyze image to ask different questions about it - don't re-screenshot the page unnecessarily.
17
17
  - Match the image engine to the job: `renderImage` (a browser rendering HTML you author) for token-exact graphics — share cards, wordmarks, flat icon tiles; `generateImages` (an image model) for organic, photographic, and illustrated work. Don't ask the image model to hit exact hex codes or typography, and don't hand-write SVG path data — compose HTML/CSS and render it.
18
18
  - When you write user-facing copy (headlines, captions, labels, body text), hand it to `polishCopy` before finalizing. It tightens prose so it reads like a person wrote it rather than a machine, without changing what it says. Cheap and fast — use it on any copy that will ship.
19
- - Deliver wireframes with `createWireframe`. Include the returned `![name](src/.wireframes/….html)` line in your response where the wireframe belongs — it renders as a live preview, and the developer reads the file for the exact markup.
19
+ - Deliver wireframes with `createWireframe`. Include the `![name](src/.wireframes/{slug}.html)` line in your response where the wireframe belongs — it renders as a live preview, and the developer reads the file for the exact markup. Same slug = revise in place; new slug = new wireframe.
20
20
 
21
21
  ## Voice
22
22
  - No emoji, no filler.
@@ -25,7 +25,7 @@ Some surfaces are deep enough to carry their own craft reference in <available_s
25
25
 
26
26
  ### Wireframes
27
27
 
28
- When you need to show a layout, component, interaction, or animation, create a wireframe with the `createWireframe` tool: pass a `name`, a one-line `description`, and self-contained HTML+CSS. The tool saves the wireframe as a file under `src/.wireframes/` and returns a markdown reference line like `![Feed Post Card](src/.wireframes/feed-post-card-x7f2.html)` — include that exact line in your response wherever the wireframe belongs, with your notes in the surrounding prose. The reference line renders as a live visual preview, and the developer reads the file itself for the exact markup and CSS.
28
+ When you need to show a layout, component, interaction, or animation, create a wireframe with the `createWireframe` tool: pass a `name`, a kebab-case `slug`, a one-line `description`, and self-contained HTML+CSS. The wireframe is saved to `src/.wireframes/{slug}.html`, and you reference it as `![Feed Post Card](src/.wireframes/feed-post-card.html)` — include that line in your response wherever the wireframe belongs, with your notes in the surrounding prose. The reference line renders as a live visual preview, and the developer reads the file itself for the exact markup and CSS. A reference only renders once its wireframe exists, so make the `createWireframe` call for every reference you write — in the same response is fine.
29
29
 
30
30
  Never use ASCII art, box-drawing characters, or code-block diagrams to describe layouts. Always use a wireframe instead, even if it's just grey rectangles with labels. A 20-line wireframe with placeholder boxes communicates proportions, spacing, and hierarchy better than any text diagram. For abstract layouts, use skeleton-style placeholders (grey boxes, rounded rects) rather than mocking up real content.
31
31
 
@@ -35,9 +35,9 @@ Wireframes render in a small transparent iframe. Set a background color and shad
35
35
 
36
36
  Wireframes are vanilla HTML/CSS/JS (no React). For animations beyond CSS, use GSAP via CDN: `<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>`
37
37
 
38
- Wireframe files are immutable — to revise one, create a new wireframe. If you're iterating on an earlier one, read its file first and riff from there.
38
+ To revise a wireframe, call `createWireframe` again with the same slug the file is overwritten in place and every existing reference to that path (in chat and in specs) shows the new version. Read the current file first if you're iterating on it. Use a new slug when it's genuinely a different wireframe, not a revision.
39
39
 
40
- Quick skeleton wireframe (grey boxes, just showing layout and hierarchy) — `createWireframe` with name "Content Card Layout", description "Card with image area, title, metadata row, rating, and actions. Skeleton placeholders showing proportions and hierarchy.", and this html:
40
+ Quick skeleton wireframe (grey boxes, just showing layout and hierarchy) — `createWireframe` with name "Content Card Layout", slug "content-card-layout", description "Card with image area, title, metadata row, rating, and actions. Skeleton placeholders showing proportions and hierarchy.", and this html:
41
41
 
42
42
  ```html
43
43
  <html lang="en"><head>
@@ -72,7 +72,7 @@ Quick skeleton wireframe (grey boxes, just showing layout and hierarchy) — `cr
72
72
  </html>
73
73
  ```
74
74
 
75
- Detailed component wireframe (showing specific design decisions) — `createWireframe` with name "Feed Post Card", description "Photo post card with header, image frame, action row (like/comment/share/bookmark), like count, and caption. Shows spacing, typography hierarchy, and icon placement.", and this html:
75
+ Detailed component wireframe (showing specific design decisions) — `createWireframe` with name "Feed Post Card", slug "feed-post-card", description "Photo post card with header, image frame, action row (like/comment/share/bookmark), like count, and caption. Shows spacing, typography hierarchy, and icon placement.", and this html:
76
76
 
77
77
  ```html
78
78
  <html lang="en"><head>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.282",
3
+ "version": "0.1.284",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",