jettypod 4.4.118 → 4.4.121

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.
Files changed (240) hide show
  1. package/.env +4 -3
  2. package/Cargo.lock +6450 -0
  3. package/Cargo.toml +35 -0
  4. package/README.md +5 -1
  5. package/TAURI-MIGRATION-PLAN.md +840 -0
  6. package/apps/dashboard/app/connect-claude/page.tsx +5 -6
  7. package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
  8. package/apps/dashboard/app/demo/gates/page.tsx +43 -45
  9. package/apps/dashboard/app/design-system/page.tsx +868 -0
  10. package/apps/dashboard/app/globals.css +80 -4
  11. package/apps/dashboard/app/install-claude/page.tsx +4 -6
  12. package/apps/dashboard/app/login/page.tsx +72 -54
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +61 -13
  15. package/apps/dashboard/app/signup/page.tsx +242 -0
  16. package/apps/dashboard/app/subscribe/page.tsx +0 -2
  17. package/apps/dashboard/app/tests/page.tsx +37 -4
  18. package/apps/dashboard/app/welcome/page.tsx +13 -16
  19. package/apps/dashboard/app/work/[id]/page.tsx +117 -118
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +92 -85
  22. package/apps/dashboard/components/CardMenu.tsx +45 -12
  23. package/apps/dashboard/components/ClaudePanel.tsx +771 -850
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
  26. package/apps/dashboard/components/CopyableId.tsx +3 -4
  27. package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
  28. package/apps/dashboard/components/DragContext.tsx +134 -63
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +6 -7
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +26 -7
  34. package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
  35. package/apps/dashboard/components/EpicGroup.tsx +359 -0
  36. package/apps/dashboard/components/GateCard.tsx +79 -17
  37. package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
  39. package/apps/dashboard/components/JettyLoader.tsx +37 -0
  40. package/apps/dashboard/components/KanbanBoard.tsx +368 -958
  41. package/apps/dashboard/components/KanbanCard.tsx +740 -0
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
  44. package/apps/dashboard/components/MainNav.tsx +38 -73
  45. package/apps/dashboard/components/MessageBlock.tsx +468 -0
  46. package/apps/dashboard/components/ModeStartCard.tsx +15 -16
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
  48. package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
  53. package/apps/dashboard/components/ReviewFooter.tsx +139 -0
  54. package/apps/dashboard/components/SessionList.tsx +19 -19
  55. package/apps/dashboard/components/SubscribeContent.tsx +91 -47
  56. package/apps/dashboard/components/TestTree.tsx +16 -16
  57. package/apps/dashboard/components/TipCard.tsx +16 -17
  58. package/apps/dashboard/components/Toast.tsx +5 -6
  59. package/apps/dashboard/components/TypeIcon.tsx +55 -0
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
  62. package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
  63. package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
  64. package/apps/dashboard/components/WorkItemTree.tsx +11 -32
  65. package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
  66. package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
  67. package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
  68. package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
  72. package/apps/dashboard/components/ui/Button.tsx +104 -0
  73. package/apps/dashboard/components/ui/Input.tsx +78 -0
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
  77. package/apps/dashboard/contexts/UsageContext.tsx +87 -32
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
  81. package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
  82. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  83. package/apps/dashboard/index.html +73 -0
  84. package/apps/dashboard/lib/constants.ts +43 -0
  85. package/apps/dashboard/lib/data-bridge.ts +722 -0
  86. package/apps/dashboard/lib/db.ts +69 -1265
  87. package/apps/dashboard/lib/environment-config.ts +173 -0
  88. package/apps/dashboard/lib/environment-verification.ts +119 -0
  89. package/apps/dashboard/lib/kanban-utils.ts +270 -0
  90. package/apps/dashboard/lib/proof-run.ts +495 -0
  91. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  92. package/apps/dashboard/lib/run-migrations.js +27 -2
  93. package/apps/dashboard/lib/service-recovery.ts +326 -0
  94. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  95. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  96. package/apps/dashboard/lib/session-stream-manager.ts +308 -134
  97. package/apps/dashboard/lib/shadows.ts +7 -0
  98. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  99. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  100. package/apps/dashboard/lib/tauri.ts +106 -0
  101. package/apps/dashboard/lib/utils.ts +6 -0
  102. package/apps/dashboard/next-env.d.ts +1 -1
  103. package/apps/dashboard/package.json +21 -32
  104. package/apps/dashboard/public/bug-icon.png +0 -0
  105. package/apps/dashboard/public/buoy-icon.png +0 -0
  106. package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
  107. package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
  108. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  109. package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
  110. package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
  111. package/apps/dashboard/public/jettypod_logo.png +0 -0
  112. package/apps/dashboard/public/pier-icon.png +0 -0
  113. package/apps/dashboard/public/star-icon.png +0 -0
  114. package/apps/dashboard/public/wrench-icon.png +0 -0
  115. package/apps/dashboard/scripts/tauri-build.js +228 -0
  116. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  117. package/apps/dashboard/scripts/ws-server.js +191 -0
  118. package/apps/dashboard/src/main.tsx +12 -0
  119. package/apps/dashboard/src/router.tsx +107 -0
  120. package/apps/dashboard/src/vite-env.d.ts +1 -0
  121. package/apps/dashboard/tsconfig.json +7 -12
  122. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  123. package/apps/dashboard/vite.config.ts +33 -0
  124. package/apps/update-server/src/index.ts +228 -80
  125. package/claude-hooks/global-guardrails.js +14 -13
  126. package/crates/jettypod-cli/Cargo.toml +19 -0
  127. package/crates/jettypod-cli/src/commands.rs +1249 -0
  128. package/crates/jettypod-cli/src/main.rs +595 -0
  129. package/crates/jettypod-core/Cargo.toml +26 -0
  130. package/crates/jettypod-core/build.rs +98 -0
  131. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  132. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  133. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  134. package/crates/jettypod-core/src/auth.rs +294 -0
  135. package/crates/jettypod-core/src/config.rs +397 -0
  136. package/crates/jettypod-core/src/db/mod.rs +507 -0
  137. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  138. package/crates/jettypod-core/src/db/startup.rs +101 -0
  139. package/crates/jettypod-core/src/db/validate.rs +149 -0
  140. package/crates/jettypod-core/src/error.rs +76 -0
  141. package/crates/jettypod-core/src/git.rs +458 -0
  142. package/crates/jettypod-core/src/lib.rs +20 -0
  143. package/crates/jettypod-core/src/sessions.rs +625 -0
  144. package/crates/jettypod-core/src/skills.rs +556 -0
  145. package/crates/jettypod-core/src/work.rs +1086 -0
  146. package/crates/jettypod-core/src/worktree.rs +628 -0
  147. package/crates/jettypod-core/src/ws.rs +767 -0
  148. package/cucumber-test.cjs +6 -0
  149. package/cucumber.js +9 -3
  150. package/docs/COMMAND_REFERENCE.md +34 -0
  151. package/hooks/post-checkout +32 -75
  152. package/hooks/post-merge +111 -10
  153. package/jest.setup.js +1 -0
  154. package/jettypod.js +145 -116
  155. package/lib/bdd-preflight.js +96 -0
  156. package/lib/chore-taxonomy.js +33 -10
  157. package/lib/database.js +36 -16
  158. package/lib/db-watcher.js +1 -1
  159. package/lib/git-hooks/pre-commit +1 -1
  160. package/lib/jettypod-backup.js +27 -4
  161. package/lib/merge-lock.js +111 -253
  162. package/lib/migrations/027-plan-at-creation-column.js +3 -1
  163. package/lib/migrations/029-remove-autoincrement.js +307 -0
  164. package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
  165. package/lib/migrations/030-rejection-round-columns.js +54 -0
  166. package/lib/migrations/031-session-isolation-index.js +17 -0
  167. package/lib/migrations/index.js +47 -4
  168. package/lib/schema.js +10 -5
  169. package/lib/seed-onboarding.js +1 -1
  170. package/lib/update-command/index.js +9 -175
  171. package/lib/work-commands/index.js +144 -19
  172. package/lib/work-tracking/index.js +148 -27
  173. package/lib/worktree-diagnostics.js +16 -16
  174. package/lib/worktree-facade.js +1 -1
  175. package/lib/worktree-manager.js +8 -8
  176. package/lib/worktree-reconciler.js +5 -5
  177. package/package.json +9 -2
  178. package/scripts/ndjson-to-cucumber-json.js +152 -0
  179. package/scripts/postinstall.js +25 -0
  180. package/skills-templates/bug-mode/SKILL.md +79 -20
  181. package/skills-templates/bug-planning/SKILL.md +25 -29
  182. package/skills-templates/chore-mode/SKILL.md +171 -69
  183. package/skills-templates/chore-mode/verification.js +51 -10
  184. package/skills-templates/chore-planning/SKILL.md +47 -18
  185. package/skills-templates/design-system-selection/SKILL.md +273 -0
  186. package/skills-templates/epic-planning/SKILL.md +82 -48
  187. package/skills-templates/external-transition/SKILL.md +47 -47
  188. package/skills-templates/feature-planning/SKILL.md +173 -74
  189. package/skills-templates/production-mode/SKILL.md +69 -49
  190. package/skills-templates/request-routing/SKILL.md +4 -4
  191. package/skills-templates/simple-improvement/SKILL.md +74 -29
  192. package/skills-templates/speed-mode/SKILL.md +217 -141
  193. package/skills-templates/stable-mode/SKILL.md +148 -89
  194. package/apps/dashboard/README.md +0 -36
  195. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
  196. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  197. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
  198. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  199. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
  200. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  201. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  202. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  203. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  204. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  205. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  206. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  207. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  208. package/apps/dashboard/app/api/tests/route.ts +0 -9
  209. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  210. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  211. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  212. package/apps/dashboard/app/api/usage/route.ts +0 -17
  213. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  214. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  215. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  216. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
  217. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  218. package/apps/dashboard/app/layout.tsx +0 -43
  219. package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
  220. package/apps/dashboard/electron/ipc-handlers.js +0 -1028
  221. package/apps/dashboard/electron/main.js +0 -2124
  222. package/apps/dashboard/electron/preload.js +0 -123
  223. package/apps/dashboard/electron/session-manager.js +0 -141
  224. package/apps/dashboard/electron-builder.config.js +0 -357
  225. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  226. package/apps/dashboard/lib/claude-process-manager.ts +0 -492
  227. package/apps/dashboard/lib/db-bridge.ts +0 -282
  228. package/apps/dashboard/lib/prototypes.ts +0 -202
  229. package/apps/dashboard/lib/test-results-db.ts +0 -307
  230. package/apps/dashboard/lib/tests.ts +0 -282
  231. package/apps/dashboard/next.config.js +0 -50
  232. package/apps/dashboard/postcss.config.mjs +0 -7
  233. package/apps/dashboard/public/file.svg +0 -1
  234. package/apps/dashboard/public/globe.svg +0 -1
  235. package/apps/dashboard/public/next.svg +0 -1
  236. package/apps/dashboard/public/vercel.svg +0 -1
  237. package/apps/dashboard/public/window.svg +0 -1
  238. package/apps/dashboard/scripts/download-node.js +0 -104
  239. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
  240. package/docs/bdd-guidance.md +0 -390
@@ -1,104 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Downloads Node.js binaries for bundling with the Electron app.
4
- * Run this before electron-builder to ensure Node.js is available.
5
- *
6
- * Usage: node scripts/download-node.js
7
- */
8
-
9
- const https = require('https');
10
- const fs = require('fs');
11
- const path = require('path');
12
- const { execSync } = require('child_process');
13
-
14
- const NODE_VERSION = 'v24.13.0'; // LTS Krypton
15
- const ARCHITECTURES = ['arm64', 'x64'];
16
- const OUTPUT_DIR = path.join(__dirname, '..', 'build-resources', 'node');
17
-
18
- function download(url, dest) {
19
- return new Promise((resolve, reject) => {
20
- const file = fs.createWriteStream(dest);
21
- https.get(url, (response) => {
22
- if (response.statusCode === 302 || response.statusCode === 301) {
23
- // Follow redirect
24
- https.get(response.headers.location, (res) => {
25
- res.pipe(file);
26
- file.on('finish', () => {
27
- file.close(resolve);
28
- });
29
- }).on('error', reject);
30
- } else {
31
- response.pipe(file);
32
- file.on('finish', () => {
33
- file.close(resolve);
34
- });
35
- }
36
- }).on('error', reject);
37
- });
38
- }
39
-
40
- async function downloadAndExtract(arch) {
41
- const archDir = path.join(OUTPUT_DIR, arch);
42
- const nodeDir = path.join(archDir, 'bin');
43
-
44
- // Check if already downloaded
45
- if (fs.existsSync(path.join(nodeDir, 'node'))) {
46
- console.log(`✓ Node.js ${arch} already exists`);
47
- return;
48
- }
49
-
50
- const tarball = `node-${NODE_VERSION}-darwin-${arch}.tar.gz`;
51
- const url = `https://nodejs.org/dist/${NODE_VERSION}/${tarball}`;
52
- const tarPath = path.join(OUTPUT_DIR, tarball);
53
-
54
- console.log(`Downloading Node.js ${NODE_VERSION} for ${arch}...`);
55
- await download(url, tarPath);
56
-
57
- console.log(`Extracting ${arch}...`);
58
- fs.mkdirSync(archDir, { recursive: true });
59
- execSync(`tar -xzf "${tarPath}" -C "${OUTPUT_DIR}"`, { stdio: 'inherit' });
60
-
61
- // Move extracted directory to arch name
62
- const extractedDir = path.join(OUTPUT_DIR, `node-${NODE_VERSION}-darwin-${arch}`);
63
- if (fs.existsSync(archDir)) {
64
- fs.rmSync(archDir, { recursive: true });
65
- }
66
- fs.renameSync(extractedDir, archDir);
67
-
68
- // Remove unnecessary files to reduce size
69
- const toRemove = ['include', 'share', 'CHANGELOG.md', 'LICENSE', 'README.md'];
70
- for (const item of toRemove) {
71
- const itemPath = path.join(archDir, item);
72
- if (fs.existsSync(itemPath)) {
73
- fs.rmSync(itemPath, { recursive: true });
74
- }
75
- }
76
-
77
- // Remove corepack (not needed)
78
- const corepackBin = path.join(archDir, 'bin', 'corepack');
79
- const corepackLib = path.join(archDir, 'lib', 'node_modules', 'corepack');
80
- if (fs.existsSync(corepackBin)) fs.rmSync(corepackBin);
81
- if (fs.existsSync(corepackLib)) fs.rmSync(corepackLib, { recursive: true });
82
-
83
- // Clean up tarball
84
- fs.rmSync(tarPath);
85
-
86
- console.log(`✓ Node.js ${arch} ready`);
87
- }
88
-
89
- async function main() {
90
- console.log(`\nPreparing Node.js ${NODE_VERSION} for bundling...\n`);
91
-
92
- fs.mkdirSync(OUTPUT_DIR, { recursive: true });
93
-
94
- for (const arch of ARCHITECTURES) {
95
- await downloadAndExtract(arch);
96
- }
97
-
98
- console.log('\n✓ All Node.js binaries ready for bundling\n');
99
- }
100
-
101
- main().catch((err) => {
102
- console.error('Failed to download Node.js:', err);
103
- process.exit(1);
104
- });
@@ -1,89 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Upload release artifacts to Cloudflare R2.
5
- * Run after electron-builder: npm run upload:r2
6
- *
7
- * Requires CLOUDFLARE_API_TOKEN env var (or wrangler login).
8
- * Uploads: latest-mac.yml, DMG, ZIP, and blockmap files.
9
- */
10
-
11
- const { execSync } = require('child_process');
12
- const fs = require('fs');
13
- const path = require('path');
14
-
15
- const BUCKET_NAME = 'jettypod-releases';
16
- const DIST_DIR = path.join(__dirname, '..', 'dist');
17
-
18
- // File patterns to upload
19
- const UPLOAD_PATTERNS = [
20
- /^latest-mac\.yml$/,
21
- /\.dmg$/,
22
- /\.zip$/,
23
- /\.blockmap$/,
24
- ];
25
-
26
- function findArtifacts() {
27
- if (!fs.existsSync(DIST_DIR)) {
28
- console.error(`❌ dist/ directory not found at ${DIST_DIR}`);
29
- console.error('Run electron:build first.');
30
- process.exit(1);
31
- }
32
-
33
- const files = fs.readdirSync(DIST_DIR);
34
- return files.filter((file) =>
35
- UPLOAD_PATTERNS.some((pattern) => pattern.test(file))
36
- );
37
- }
38
-
39
- function uploadFile(filename) {
40
- const filePath = path.join(DIST_DIR, filename);
41
- const stats = fs.statSync(filePath);
42
- const sizeMB = (stats.size / (1024 * 1024)).toFixed(1);
43
-
44
- console.log(` Uploading ${filename} (${sizeMB} MB)...`);
45
-
46
- try {
47
- execSync(
48
- `npx wrangler r2 object put "${BUCKET_NAME}/${filename}" --file="${filePath}" --remote`,
49
- { stdio: 'pipe' }
50
- );
51
- console.log(` ✅ ${filename}`);
52
- return true;
53
- } catch (error) {
54
- console.error(` ❌ Failed to upload ${filename}: ${error.message}`);
55
- return false;
56
- }
57
- }
58
-
59
- function main() {
60
- console.log('🚀 Uploading release artifacts to R2...\n');
61
-
62
- const artifacts = findArtifacts();
63
- if (artifacts.length === 0) {
64
- console.error('❌ No release artifacts found in dist/');
65
- console.error('Expected: latest-mac.yml, .dmg, .zip, or .blockmap files');
66
- process.exit(1);
67
- }
68
-
69
- console.log(`Found ${artifacts.length} artifact(s):\n`);
70
-
71
- let success = 0;
72
- let failed = 0;
73
-
74
- for (const artifact of artifacts) {
75
- if (uploadFile(artifact)) {
76
- success++;
77
- } else {
78
- failed++;
79
- }
80
- }
81
-
82
- console.log(`\n📦 Upload complete: ${success} uploaded, ${failed} failed`);
83
-
84
- if (failed > 0) {
85
- process.exit(1);
86
- }
87
- }
88
-
89
- main();
@@ -1,390 +0,0 @@
1
- What BDD actually is (and isn’t)
2
-
3
- BDD is a collaboration + specification technique that uses concrete examples to describe behavior in a shared language.
4
-
5
- The “unit tests” are not the point. The examples are the point.
6
-
7
- BDD tests should validate behavior that matters to users/business—without leaking implementation details.
8
-
9
- BDD ≠ “write all tests in Gherkin.” BDD can be done with plain unit/integration tests too. Gherkin is just a common interface for readability and stakeholder alignment.
10
-
11
- A good mental model:
12
-
13
- Feature files describe “what” and “why.”
14
-
15
- Step definitions implement “how,” but only at a high level.
16
-
17
- Lower-level details live in helper layers (Page Objects, API clients, domain helpers).
18
-
19
- The BDD flow (what “good” looks like)
20
-
21
- Discovery (3 Amigos: product + dev + QA)
22
-
23
- Agree on behavior via examples: happy path + edge cases.
24
-
25
- Formulation
26
-
27
- Turn examples into scenarios (often in Gherkin).
28
-
29
- Automation
30
-
31
- Implement step definitions that call into a small, reusable automation layer.
32
-
33
- Living documentation
34
-
35
- Keep scenarios accurate and stable; prune duplicates; version behavior over time.
36
-
37
- Gherkin, done well (the style rules that save you later)
38
- Core primitives
39
-
40
- Feature: coherent behavior area
41
-
42
- Scenario: one concrete example
43
-
44
- Given/When/Then:
45
-
46
- Given: preconditions / state
47
-
48
- When: action
49
-
50
- Then: observable outcomes
51
-
52
- Good scenario traits
53
-
54
- Small: one behavior, one reason to fail
55
-
56
- Declarative: describes intent, not UI clicks
57
-
58
- Stable: avoids brittle details (pixel-level UI, timing hacks)
59
-
60
- Deterministic: no reliance on “whatever data happens to exist”
61
-
62
- Example (good)
63
- Scenario: User can retry a failed payment
64
- Given a user with an unpaid invoice
65
- And the payment processor returns "insufficient_funds"
66
- When the user retries payment with a different card
67
- Then the invoice is marked as paid
68
- And the user sees a receipt
69
-
70
- Example (brittle / not great)
71
- Scenario: Pay invoice
72
- Given I click the "Billing" tab
73
- And I wait 2 seconds
74
- And I click the third button on the page
75
- When I type "4111111111111111" into the card field
76
- Then I should see "Success"
77
-
78
-
79
- This is more of a UI macro recorder than a behavioral spec.
80
-
81
- Step definitions: the most common place things go off the rails
82
- The biggest rule
83
-
84
- Step definitions should be thin.
85
- They should:
86
-
87
- parse parameters
88
-
89
- call a helper/API/page-object method
90
-
91
- assert outcomes at the correct level
92
-
93
- They should not:
94
-
95
- contain lots of branching logic
96
-
97
- do complex loops
98
-
99
- embed SQL queries
100
-
101
- “know” too much about UI selectors
102
-
103
- implement multi-step workflows inline
104
-
105
- The “thin step” pattern
106
-
107
- Step def → calls one intentful function (e.g., billing.retryPaymentWith(card)), rather than doing click/type/wait directly.
108
-
109
- Example structure:
110
-
111
- // step definition
112
- When('the user retries payment with a different card', async () => {
113
- await billing.retryPaymentWith(validCard2);
114
- });
115
-
116
- // helper layer (page object / service client)
117
- async function retryPaymentWith(card) {
118
- await openBilling();
119
- await selectInvoice(...);
120
- await enterCard(card);
121
- await submit();
122
- await waitForReceipt(); // smart wait, not sleep(2000)
123
- }
124
-
125
- Handling “complex things” in BDD tests (the hard parts)
126
- 1) Asynchrony and eventual consistency
127
-
128
- Problem: background jobs, queues, delayed writes, distributed systems.
129
-
130
- Best practices
131
-
132
- Prefer event-based or state-based polling with timeouts over fixed sleeps.
133
-
134
- Assert intermediate states if meaningful (“processing” → “completed”).
135
-
136
- If possible, expose a test-only hook (e.g., “job runner runs immediately” in test env).
137
-
138
- What to do:
139
-
140
- await waitFor(() => order.status === 'COMPLETED', { timeout: 10_000 })
141
-
142
- avoid: sleep(5000)
143
-
144
- 2) External dependencies (payment providers, email/SMS, maps)
145
-
146
- Problem: flaky tests, slow runs, rate limits.
147
-
148
- Best practices
149
-
150
- For most BDD runs: stub at the boundary (in-process fake server, contract stub).
151
-
152
- Have a smaller set of true end-to-end smoke tests that hit real external services (maybe nightly).
153
-
154
- 3) Authentication flows (OAuth, magic links)
155
-
156
- Best practices
157
-
158
- Prefer test auth shortcuts:
159
-
160
- a test-only endpoint to mint tokens
161
-
162
- bypass UI login with session injection
163
-
164
- Keep one or two UI-login scenarios if you must, but don’t make every scenario pay the “login tax.”
165
-
166
- 4) Data setup that is “realistic” but not fragile
167
-
168
- Problem: complicated prerequisites create scenario bloat.
169
-
170
- Best practices
171
-
172
- Use factories/fixtures with names that encode intent:
173
-
174
- givenUserWithUnpaidInvoice()
175
-
176
- givenWorkspaceWith3MembersAndNoAdmin()
177
-
178
- Avoid “Given the database has…” in feature files. That’s implementation leakage.
179
-
180
- 5) Time, randomness, and IDs
181
-
182
- Best practices
183
-
184
- Freeze time (clock.set("2026-02-10T10:00:00Z")) or inject time providers.
185
-
186
- Seed randomness.
187
-
188
- Don’t assert on raw IDs; assert on meaning (“receipt exists”, “email sent to user”).
189
-
190
- 6) UI interactions that are inherently finicky
191
-
192
- Best practices
193
-
194
- Use stable locators (data-testid, ARIA roles) rather than CSS chains.
195
-
196
- Use smart waits (element visible/enabled, network idle) not sleeps.
197
-
198
- Put selectors in one place (page objects / screen model).
199
-
200
- 7) Distributed workflows (webhook in, job runs, UI updates)
201
-
202
- Best practices
203
-
204
- Split assertions by layer:
205
-
206
- API-level scenario verifies webhook → status update
207
-
208
- UI-level scenario verifies status display
209
-
210
- Don’t force one scenario to validate every link in the chain unless it’s explicitly a top-level acceptance test.
211
-
212
- The test pyramid in BDD terms (where each kind of test belongs)
213
-
214
- A very effective setup:
215
-
216
- Many unit tests (fast, deterministic): pure logic
217
-
218
- Many integration/contract tests: service boundaries, DB, message bus (still fast-ish)
219
-
220
- Some BDD scenarios: critical user journeys and key edge cases
221
-
222
- Very few UI E2E: smoke and “are we totally broken?” checks
223
-
224
- BDD scenarios can exist at multiple levels (API-level BDD is often a sweet spot).
225
-
226
- Mocks, stubs, fakes: what they are (and why people argue about them)
227
- Definitions (practical, not academic)
228
-
229
- Mock: a test double you can verify interactions with
230
- (“Was chargeCard() called with amount=4999?”)
231
-
232
- Stub: a test double that returns predetermined responses
233
- (“When /payments is called, return 402 insufficient_funds”)
234
-
235
- Fake: a lightweight working implementation
236
- (in-memory DB, fake email inbox, fake queue)
237
-
238
- Spy: like a mock, but wraps a real object and records calls
239
-
240
- When to use what
241
-
242
- Use stubs/fakes for most BDD scenarios because they support behavior assertions (“user sees receipt”) without coupling to call patterns.
243
-
244
- Use mocks sparingly, mostly in unit tests or when verifying a critical side effect is the purpose of the scenario.
245
-
246
- The big danger of mocks in BDD
247
-
248
- Mocks push you toward testing implementation details:
249
-
250
- “did we call X?” rather than “did the user get the outcome?”
251
-
252
- Sometimes verifying calls is legitimate (e.g., “audit event emitted”), but generally:
253
-
254
- BDD asserts outcomes, not internal choreography.
255
-
256
- Step definition best practices checklist (great for “is my AI behaving?”)
257
- ✅ Green flags
258
-
259
- Steps are short (often 1–5 lines)
260
-
261
- Steps call named helper methods (domain language)
262
-
263
- Assertions are in Then steps (or helper assertions)
264
-
265
- Givens set up intentful state, not low-level DB edits
266
-
267
- Reuse happens through helper methods, not giant shared step defs
268
-
269
- Steps avoid sleeps; use smart waits
270
-
271
- Scenario language avoids UI specifics unless truly necessary
272
-
273
- 🚩 Red flags (AI assistants love these)
274
-
275
- Step defs contain:
276
-
277
- loops, conditionals, try/catch gymnastics
278
-
279
- direct SQL / direct ORM writes sprinkled everywhere
280
-
281
- lots of selectors + click/type chains inline
282
-
283
- random sleeps/timeouts to “make it pass”
284
-
285
- Steps are overly generic:
286
-
287
- “When I do the thing”
288
-
289
- “Then it works”
290
-
291
- Heavy parameterization:
292
-
293
- Steps with 6–10 parameters usually mean you’re encoding a DSL no one can read
294
-
295
- Shared state is global and leaky across scenarios
296
-
297
- One scenario validates 12 different outcomes (“kitchen sink test”)
298
-
299
- A strict architecture that keeps BDD clean
300
-
301
- If you want your AI to stay disciplined, give it a structure it can’t easily “freestyle” out of:
302
-
303
- Recommended layers
304
-
305
- Feature files (behavior)
306
-
307
- Step definitions (glue)
308
-
309
- Domain tasks / Screenplay actions (intentful operations)
310
-
311
- Drivers
312
-
313
- UI driver (page objects / screen model)
314
-
315
- API client
316
-
317
- DB helper (sparingly)
318
-
319
- Message bus helper
320
-
321
- Test fixtures/factories
322
-
323
- Rule of thumb:
324
-
325
- Step defs may depend on domain tasks
326
-
327
- Domain tasks may depend on drivers
328
-
329
- Feature files know nothing about drivers
330
-
331
- This prevents selector soup from infecting Gherkin.
332
-
333
- Making your AI assistant “strict” (practical constraints you can enforce)
334
-
335
- Here are concrete constraints you can put in your prompt / code review rubric:
336
-
337
- Step definition max complexity
338
-
339
- No loops
340
-
341
- No conditionals except trivial parameter mapping
342
-
343
- No sleeps
344
-
345
- Selectors forbidden in steps
346
-
347
- Must live in page objects/screen models only
348
-
349
- One intentful call per step
350
-
351
- Steps call one task method
352
-
353
- Outcome assertions only in Then
354
-
355
- No shared global mutable state
356
-
357
- Use scenario context object only
358
-
359
- Deterministic data
360
-
361
- Factories generate known entities; tests never depend on prod-like ambient data
362
-
363
- If you tell the AI “follow best practices,” it’ll nod vigorously and then sleep(2000) anyway. If you tell it “sleep is banned,” it suddenly remembers how to wait for elements like an adult.
364
-
365
- Quick example: translating complex behavior into clean steps
366
-
367
- Complex behavior: “User triggers export; job runs async; user is notified; file is downloadable.”
368
-
369
- Good BDD split:
370
-
371
- Scenario asserts user-level behavior
372
-
373
- Implementation uses polling and test doubles
374
-
375
- Scenario: User can download a completed export
376
- Given a user with 3 projects
377
- When the user requests a project export
378
- Then the export eventually completes
379
- And the user can download the export file
380
-
381
-
382
- Implementation strategy:
383
-
384
- request export calls API
385
-
386
- eventually completes polls status endpoint with timeout
387
-
388
- can download checks signed URL returns 200 and file has expected headers
389
-
390
- No sleeps, no digging into job queue internals (unless you’re specifically testing that).