lean-spec 0.2.5-dev.20251124044002 → 0.2.5-dev.20251124050427

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 (35) hide show
  1. package/dist/{chunk-IGNO4GX2.js → chunk-YKBICZNR.js} +760 -550
  2. package/dist/chunk-YKBICZNR.js.map +1 -0
  3. package/dist/cli.js +6 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/mcp-server.js +1 -1
  6. package/package.json +1 -1
  7. package/templates/examples/api-refactor/README.md +81 -0
  8. package/templates/examples/api-refactor/package.json +16 -0
  9. package/templates/examples/api-refactor/src/app.js +40 -0
  10. package/templates/examples/api-refactor/src/services/currencyService.js +43 -0
  11. package/templates/examples/api-refactor/src/services/timezoneService.js +41 -0
  12. package/templates/examples/api-refactor/src/services/weatherService.js +42 -0
  13. package/templates/examples/dashboard-widgets/README.md +70 -0
  14. package/templates/examples/dashboard-widgets/index.html +12 -0
  15. package/templates/examples/dashboard-widgets/package.json +22 -0
  16. package/templates/examples/dashboard-widgets/src/App.css +20 -0
  17. package/templates/examples/dashboard-widgets/src/App.jsx +16 -0
  18. package/templates/examples/dashboard-widgets/src/components/Dashboard.css +17 -0
  19. package/templates/examples/dashboard-widgets/src/components/Dashboard.jsx +15 -0
  20. package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.css +23 -0
  21. package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.jsx +16 -0
  22. package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.css +33 -0
  23. package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.jsx +28 -0
  24. package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.css +24 -0
  25. package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.jsx +22 -0
  26. package/templates/examples/dashboard-widgets/src/index.css +13 -0
  27. package/templates/examples/dashboard-widgets/src/main.jsx +10 -0
  28. package/templates/examples/dashboard-widgets/src/utils/mockData.js +30 -0
  29. package/templates/examples/dashboard-widgets/vite.config.js +6 -0
  30. package/templates/examples/email-notifications/README.md +55 -0
  31. package/templates/examples/email-notifications/package.json +16 -0
  32. package/templates/examples/email-notifications/src/server.js +57 -0
  33. package/templates/examples/email-notifications/src/storage.js +40 -0
  34. package/templates/examples/email-notifications/src/users.js +38 -0
  35. package/dist/chunk-IGNO4GX2.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { analyzeCommand, archiveCommand, backfillCommand, boardCommand, checkCommand, compactCommand, createCommand, depsCommand, filesCommand, ganttCommand, initCommand, linkCommand, listCommand, mcpCommand, migrateCommand, openCommand, searchCommand, splitCommand, statsCommand, templatesCommand, timelineCommand, tokensCommand, uiCommand, unlinkCommand, updateCommand, validateCommand, viewCommand } from './chunk-IGNO4GX2.js';
1
+ import { analyzeCommand, archiveCommand, backfillCommand, boardCommand, checkCommand, compactCommand, createCommand, depsCommand, examplesCommand, filesCommand, ganttCommand, initCommand, linkCommand, listCommand, mcpCommand, migrateCommand, openCommand, searchCommand, splitCommand, statsCommand, templatesCommand, timelineCommand, tokensCommand, uiCommand, unlinkCommand, updateCommand, validateCommand, viewCommand } from './chunk-YKBICZNR.js';
2
2
  import './chunk-LVD7ZAVZ.js';
3
3
  import { Command } from 'commander';
4
4
  import { readFileSync } from 'fs';
@@ -15,6 +15,7 @@ function registerCommands(program2) {
15
15
  program2.addCommand(compactCommand());
16
16
  program2.addCommand(createCommand());
17
17
  program2.addCommand(depsCommand());
18
+ program2.addCommand(examplesCommand());
18
19
  program2.addCommand(filesCommand());
19
20
  program2.addCommand(ganttCommand());
20
21
  program2.addCommand(initCommand());
@@ -51,6 +52,7 @@ Command Groups:
51
52
  archive <spec> Move spec to archived/
52
53
  backfill [specs...] Backfill timestamps from git history
53
54
  create <name> Create new spec
55
+ examples List example projects for tutorials
54
56
  init Initialize LeanSpec in current directory
55
57
  link <spec> Add relationships between specs
56
58
  migrate <input-path> Migrate specs from other SDD tools
@@ -91,6 +93,9 @@ Command Groups:
91
93
  Examples:
92
94
  $ lean-spec init
93
95
  $ lean-spec init -y
96
+ $ lean-spec init --example email-notifications
97
+ $ lean-spec init --example dashboard-widgets --name my-demo
98
+ $ lean-spec examples
94
99
  $ lean-spec create my-feature --priority high
95
100
  $ lean-spec list --status in-progress
96
101
  $ lean-spec view 042
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/registry.ts","../src/cli.ts"],"names":["program"],"mappings":"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","file":"cli.js","sourcesContent":["import { Command } from 'commander';\nimport {\n analyzeCommand,\n archiveCommand,\n backfillCommand,\n boardCommand,\n checkCommand,\n compactCommand,\n createCommand,\n depsCommand,\n filesCommand,\n ganttCommand,\n initCommand,\n linkCommand,\n listCommand,\n mcpCommand,\n migrateCommand,\n openCommand,\n searchCommand,\n splitCommand,\n statsCommand,\n templatesCommand,\n timelineCommand,\n tokensCommand,\n uiCommand,\n unlinkCommand,\n updateCommand,\n validateCommand,\n viewCommand,\n} from './index.js';\n\n/**\n * Register all commands in alphabetical order\n */\nexport function registerCommands(program: Command): void {\n // Alphabetically sorted command registration\n program.addCommand(analyzeCommand());\n program.addCommand(archiveCommand());\n program.addCommand(backfillCommand());\n program.addCommand(boardCommand());\n program.addCommand(checkCommand());\n program.addCommand(compactCommand());\n program.addCommand(createCommand());\n program.addCommand(depsCommand());\n program.addCommand(filesCommand());\n program.addCommand(ganttCommand());\n program.addCommand(initCommand());\n program.addCommand(linkCommand());\n program.addCommand(listCommand());\n program.addCommand(mcpCommand());\n program.addCommand(migrateCommand());\n program.addCommand(openCommand());\n program.addCommand(searchCommand());\n program.addCommand(splitCommand());\n program.addCommand(statsCommand());\n program.addCommand(templatesCommand());\n program.addCommand(timelineCommand());\n program.addCommand(tokensCommand());\n program.addCommand(uiCommand());\n program.addCommand(unlinkCommand());\n program.addCommand(updateCommand());\n program.addCommand(validateCommand());\n program.addCommand(viewCommand());\n}\n","import { Command } from 'commander';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\nimport { registerCommands } from './commands/registry.js';\n\n// Get version from package.json\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst packageJson = JSON.parse(\n readFileSync(join(__dirname, '../package.json'), 'utf-8')\n);\n\nconst program = new Command();\n\nprogram\n .name('lean-spec')\n .description('Manage LeanSpec documents')\n .version(packageJson.version);\n\n// Add custom help text with grouped commands\nprogram.addHelpText('after', `\nCommand Groups:\n\n Core Workflow:\n archive <spec> Move spec to archived/\n backfill [specs...] Backfill timestamps from git history\n create <name> Create new spec\n init Initialize LeanSpec in current directory\n link <spec> Add relationships between specs\n migrate <input-path> Migrate specs from other SDD tools\n unlink <spec> Remove relationships between specs\n update <spec> Update spec metadata\n \n Discovery & Search:\n files <spec> List files in a spec\n list List all specs\n open <spec> Open spec in editor\n search <query> Full-text search with metadata filters\n view <spec> View spec content\n \n Project Analytics:\n board Show Kanban-style board view\n deps <spec> Show dependency graph for a spec\n gantt Show timeline with dependencies\n stats Show aggregate statistics\n timeline Show creation/completion over time\n \n Quality & Optimization:\n analyze <spec> Analyze spec complexity and structure\n check Check for sequence conflicts\n tokens [spec] Count tokens for LLM context management\n validate [specs...] Validate specs for quality issues\n \n Advanced Editing:\n compact <spec> Remove specified line ranges from spec\n split <spec> Split spec into multiple files\n \n Configuration:\n templates Manage spec templates\n \n Integration:\n mcp Start MCP server for AI assistants\n ui Start local web UI for spec management\n\nExamples:\n $ lean-spec init\n $ lean-spec init -y\n $ lean-spec create my-feature --priority high\n $ lean-spec list --status in-progress\n $ lean-spec view 042\n $ lean-spec link 085 --depends-on 042,035\n $ lean-spec link 085 --related 082\n $ lean-spec unlink 085 --depends-on 042\n $ lean-spec deps 085\n $ lean-spec backfill --dry-run\n $ lean-spec migrate ./docs/adr\n $ lean-spec migrate ./docs/rfcs --with copilot\n $ lean-spec board --tag backend\n $ lean-spec search \"authentication\"\n $ lean-spec validate\n $ lean-spec tokens 059\n $ lean-spec analyze 045 --json\n $ lean-spec split 045 --output README.md:1-150 --output DESIGN.md:151-end\n $ lean-spec ui\n $ lean-spec ui --port 3001 --no-open\n $ lean-spec ui --specs ./docs/specs --dry-run\n`);\n\n// Register all commands (alphabetically ordered)\nregisterCommands(program);\n\n// Parse and execute\nprogram.parse();\n"]}
1
+ {"version":3,"sources":["../src/commands/registry.ts","../src/cli.ts"],"names":["program"],"mappings":"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","file":"cli.js","sourcesContent":["import { Command } from 'commander';\nimport {\n analyzeCommand,\n archiveCommand,\n backfillCommand,\n boardCommand,\n checkCommand,\n compactCommand,\n createCommand,\n depsCommand,\n examplesCommand,\n filesCommand,\n ganttCommand,\n initCommand,\n linkCommand,\n listCommand,\n mcpCommand,\n migrateCommand,\n openCommand,\n searchCommand,\n splitCommand,\n statsCommand,\n templatesCommand,\n timelineCommand,\n tokensCommand,\n uiCommand,\n unlinkCommand,\n updateCommand,\n validateCommand,\n viewCommand,\n} from './index.js';\n\n/**\n * Register all commands in alphabetical order\n */\nexport function registerCommands(program: Command): void {\n // Alphabetically sorted command registration\n program.addCommand(analyzeCommand());\n program.addCommand(archiveCommand());\n program.addCommand(backfillCommand());\n program.addCommand(boardCommand());\n program.addCommand(checkCommand());\n program.addCommand(compactCommand());\n program.addCommand(createCommand());\n program.addCommand(depsCommand());\n program.addCommand(examplesCommand());\n program.addCommand(filesCommand());\n program.addCommand(ganttCommand());\n program.addCommand(initCommand());\n program.addCommand(linkCommand());\n program.addCommand(listCommand());\n program.addCommand(mcpCommand());\n program.addCommand(migrateCommand());\n program.addCommand(openCommand());\n program.addCommand(searchCommand());\n program.addCommand(splitCommand());\n program.addCommand(statsCommand());\n program.addCommand(templatesCommand());\n program.addCommand(timelineCommand());\n program.addCommand(tokensCommand());\n program.addCommand(uiCommand());\n program.addCommand(unlinkCommand());\n program.addCommand(updateCommand());\n program.addCommand(validateCommand());\n program.addCommand(viewCommand());\n}\n","import { Command } from 'commander';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\nimport { registerCommands } from './commands/registry.js';\n\n// Get version from package.json\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst packageJson = JSON.parse(\n readFileSync(join(__dirname, '../package.json'), 'utf-8')\n);\n\nconst program = new Command();\n\nprogram\n .name('lean-spec')\n .description('Manage LeanSpec documents')\n .version(packageJson.version);\n\n// Add custom help text with grouped commands\nprogram.addHelpText('after', `\nCommand Groups:\n\n Core Workflow:\n archive <spec> Move spec to archived/\n backfill [specs...] Backfill timestamps from git history\n create <name> Create new spec\n examples List example projects for tutorials\n init Initialize LeanSpec in current directory\n link <spec> Add relationships between specs\n migrate <input-path> Migrate specs from other SDD tools\n unlink <spec> Remove relationships between specs\n update <spec> Update spec metadata\n \n Discovery & Search:\n files <spec> List files in a spec\n list List all specs\n open <spec> Open spec in editor\n search <query> Full-text search with metadata filters\n view <spec> View spec content\n \n Project Analytics:\n board Show Kanban-style board view\n deps <spec> Show dependency graph for a spec\n gantt Show timeline with dependencies\n stats Show aggregate statistics\n timeline Show creation/completion over time\n \n Quality & Optimization:\n analyze <spec> Analyze spec complexity and structure\n check Check for sequence conflicts\n tokens [spec] Count tokens for LLM context management\n validate [specs...] Validate specs for quality issues\n \n Advanced Editing:\n compact <spec> Remove specified line ranges from spec\n split <spec> Split spec into multiple files\n \n Configuration:\n templates Manage spec templates\n \n Integration:\n mcp Start MCP server for AI assistants\n ui Start local web UI for spec management\n\nExamples:\n $ lean-spec init\n $ lean-spec init -y\n $ lean-spec init --example email-notifications\n $ lean-spec init --example dashboard-widgets --name my-demo\n $ lean-spec examples\n $ lean-spec create my-feature --priority high\n $ lean-spec list --status in-progress\n $ lean-spec view 042\n $ lean-spec link 085 --depends-on 042,035\n $ lean-spec link 085 --related 082\n $ lean-spec unlink 085 --depends-on 042\n $ lean-spec deps 085\n $ lean-spec backfill --dry-run\n $ lean-spec migrate ./docs/adr\n $ lean-spec migrate ./docs/rfcs --with copilot\n $ lean-spec board --tag backend\n $ lean-spec search \"authentication\"\n $ lean-spec validate\n $ lean-spec tokens 059\n $ lean-spec analyze 045 --json\n $ lean-spec split 045 --output README.md:1-150 --output DESIGN.md:151-end\n $ lean-spec ui\n $ lean-spec ui --port 3001 --no-open\n $ lean-spec ui --specs ./docs/specs --dry-run\n`);\n\n// Register all commands (alphabetically ordered)\nregisterCommands(program);\n\n// Parse and execute\nprogram.parse();\n"]}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- export { createMcpServer } from './chunk-IGNO4GX2.js';
2
+ export { createMcpServer } from './chunk-YKBICZNR.js';
3
3
  import './chunk-LVD7ZAVZ.js';
4
4
  //# sourceMappingURL=mcp-server.js.map
5
5
  //# sourceMappingURL=mcp-server.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lean-spec",
3
- "version": "0.2.5-dev.20251124044002",
3
+ "version": "0.2.5-dev.20251124050427",
4
4
  "description": "Specification-driven development made simple",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,81 @@
1
+ # API Refactor Demo
2
+
3
+ > **Tutorial**: [Refactoring with Specs](https://leanspec.dev/docs/tutorials/refactoring-specs)
4
+
5
+ ## Scenario
6
+
7
+ You're maintaining a Node.js application that started simple but has grown messy. The app integrates with multiple external services (weather API, currency converter, timezone lookup), but all the HTTP logic is tangled together in the main application code.
8
+
9
+ You want to extract a reusable API client module that:
10
+ - Centralizes HTTP request handling
11
+ - Provides a clean interface for service calls
12
+ - Handles errors consistently
13
+ - Makes the code easier to test and maintain
14
+
15
+ ## What's Here
16
+
17
+ A monolithic Node.js app with:
18
+ - Weather lookup feature (calls external API)
19
+ - Currency conversion (calls external API)
20
+ - Timezone lookup (calls external API)
21
+ - All HTTP logic mixed into business logic (tight coupling)
22
+ - No error handling abstraction
23
+ - Hard to test individual parts
24
+
25
+ **Files:**
26
+ - `src/app.js` - Main application with all features
27
+ - `src/services/weatherService.js` - Weather API calls (tightly coupled)
28
+ - `src/services/currencyService.js` - Currency API calls (tightly coupled)
29
+ - `src/services/timezoneService.js` - Timezone API calls (tightly coupled)
30
+
31
+ ## Getting Started
32
+
33
+ ```bash
34
+ # Install dependencies
35
+ npm install
36
+
37
+ # Run the app
38
+ npm start
39
+
40
+ # Try the features:
41
+ # - Weather: Get weather for a city
42
+ # - Currency: Convert between currencies
43
+ # - Timezone: Look up timezone info
44
+ ```
45
+
46
+ ## Your Mission
47
+
48
+ Refactor the HTTP logic into a clean, reusable API client. Follow the tutorial and ask your AI assistant:
49
+
50
+ > "Help me refactor this app using LeanSpec. I want to extract a reusable API client module that centralizes all the HTTP logic."
51
+
52
+ The AI will guide you through:
53
+ 1. Creating a refactoring spec
54
+ 2. Designing the API client interface
55
+ 3. Extracting the HTTP logic step by step
56
+ 4. Updating services to use the new client
57
+ 5. Verifying everything still works
58
+
59
+ ## Current Problems
60
+
61
+ - **Duplicated code**: Each service reimplements HTTP requests
62
+ - **No error handling**: Errors handled inconsistently
63
+ - **Hard to test**: Can't mock HTTP calls easily
64
+ - **Tight coupling**: Business logic mixed with HTTP details
65
+ - **No retry logic**: Network failures aren't handled
66
+
67
+ These are perfect opportunities to practice refactoring with specs!
68
+
69
+ ## Expected Result
70
+
71
+ After refactoring, you should have:
72
+ ```
73
+ src/
74
+ app.js (unchanged interface)
75
+ client/
76
+ apiClient.js (new - centralized HTTP logic)
77
+ services/
78
+ weatherService.js (simplified - uses apiClient)
79
+ currencyService.js (simplified - uses apiClient)
80
+ timezoneService.js (simplified - uses apiClient)
81
+ ```
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "api-refactor-demo",
3
+ "version": "1.0.0",
4
+ "description": "Monolithic Node.js app for LeanSpec Tutorial 3",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node src/app.js",
8
+ "dev": "node --watch src/app.js"
9
+ },
10
+ "keywords": ["leanspec", "tutorial", "demo"],
11
+ "author": "",
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "node-fetch": "^3.3.2"
15
+ }
16
+ }
@@ -0,0 +1,40 @@
1
+ import { getWeather } from './services/weatherService.js';
2
+ import { convertCurrency } from './services/currencyService.js';
3
+ import { getTimezone } from './services/timezoneService.js';
4
+
5
+ console.log('=== Multi-Service App Demo ===\n');
6
+
7
+ // Demo 1: Weather lookup
8
+ console.log('1. Weather Lookup:');
9
+ try {
10
+ const weather = await getWeather('London');
11
+ console.log(` ${weather.city}: ${weather.temp}°C, ${weather.condition}`);
12
+ } catch (error) {
13
+ console.log(` Error: ${error.message}`);
14
+ }
15
+
16
+ console.log('');
17
+
18
+ // Demo 2: Currency conversion
19
+ console.log('2. Currency Conversion:');
20
+ try {
21
+ const result = await convertCurrency(100, 'USD', 'EUR');
22
+ console.log(` ${result.amount} ${result.from} = ${result.converted} ${result.to}`);
23
+ } catch (error) {
24
+ console.log(` Error: ${error.message}`);
25
+ }
26
+
27
+ console.log('');
28
+
29
+ // Demo 3: Timezone lookup
30
+ console.log('3. Timezone Lookup:');
31
+ try {
32
+ const timezone = await getTimezone('America/New_York');
33
+ console.log(` ${timezone.name}: ${timezone.offset} (${timezone.abbreviation})`);
34
+ } catch (error) {
35
+ console.log(` Error: ${error.message}`);
36
+ }
37
+
38
+ console.log('');
39
+ console.log('Notice: All services work, but they all duplicate HTTP logic!');
40
+ console.log('Your task: Extract a reusable API client module.');
@@ -0,0 +1,43 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ /**
4
+ * Convert currency
5
+ *
6
+ * PROBLEM: Duplicates HTTP logic from weatherService
7
+ * - Same fetch boilerplate
8
+ * - Same error handling pattern
9
+ * - Same JSON parsing
10
+ * - Should be using a shared HTTP client!
11
+ */
12
+ export async function convertCurrency(amount, from, to) {
13
+ // Mock API endpoint (replace with real API in production)
14
+ const url = `https://api.exchangerate.host/convert?from=${from}&to=${to}&amount=${amount}`;
15
+
16
+ try {
17
+ const response = await fetch(url);
18
+
19
+ if (!response.ok) {
20
+ throw new Error(`Currency API error: ${response.status}`);
21
+ }
22
+
23
+ const data = await response.json();
24
+
25
+ // Business logic: Transform API response to our format
26
+ return {
27
+ amount,
28
+ from,
29
+ to,
30
+ converted: data.result || (amount * 0.85).toFixed(2),
31
+ rate: data.info?.rate || 0.85,
32
+ };
33
+ } catch (error) {
34
+ // For demo, return mock data on error
35
+ return {
36
+ amount,
37
+ from,
38
+ to,
39
+ converted: (amount * 0.85).toFixed(2),
40
+ rate: 0.85,
41
+ };
42
+ }
43
+ }
@@ -0,0 +1,41 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ /**
4
+ * Get timezone information
5
+ *
6
+ * PROBLEM: Yet another copy of the same HTTP logic!
7
+ * - Third time we're writing fetch + error handling + JSON parsing
8
+ * - Violates DRY principle
9
+ * - Makes testing hard (need to mock fetch in 3 places)
10
+ * - Changes to HTTP logic need updates in 3 files
11
+ */
12
+ export async function getTimezone(zone) {
13
+ // Mock API endpoint (replace with real API in production)
14
+ const url = `http://worldtimeapi.org/api/timezone/${zone}`;
15
+
16
+ try {
17
+ const response = await fetch(url);
18
+
19
+ if (!response.ok) {
20
+ throw new Error(`Timezone API error: ${response.status}`);
21
+ }
22
+
23
+ const data = await response.json();
24
+
25
+ // Business logic: Transform API response to our format
26
+ return {
27
+ name: data.timezone || zone,
28
+ offset: data.utc_offset || '-05:00',
29
+ abbreviation: data.abbreviation || 'EST',
30
+ datetime: data.datetime || new Date().toISOString(),
31
+ };
32
+ } catch (error) {
33
+ // For demo, return mock data on error
34
+ return {
35
+ name: zone,
36
+ offset: '-05:00',
37
+ abbreviation: 'EST',
38
+ datetime: new Date().toISOString(),
39
+ };
40
+ }
41
+ }
@@ -0,0 +1,42 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ /**
4
+ * Get weather for a city
5
+ *
6
+ * PROBLEM: This function has HTTP logic mixed with business logic
7
+ * - Manual fetch calls
8
+ * - Manual error handling
9
+ * - Manual JSON parsing
10
+ * - No retry logic
11
+ * - Hard to test (can't mock HTTP)
12
+ */
13
+ export async function getWeather(city) {
14
+ // Mock API endpoint (replace with real API in production)
15
+ const url = `https://api.weatherapi.com/v1/current.json?key=mock&q=${city}`;
16
+
17
+ try {
18
+ const response = await fetch(url);
19
+
20
+ if (!response.ok) {
21
+ throw new Error(`Weather API error: ${response.status}`);
22
+ }
23
+
24
+ const data = await response.json();
25
+
26
+ // Business logic: Transform API response to our format
27
+ return {
28
+ city: data.location?.name || city,
29
+ temp: data.current?.temp_c || Math.floor(Math.random() * 30),
30
+ condition: data.current?.condition?.text || 'Sunny',
31
+ humidity: data.current?.humidity || Math.floor(Math.random() * 100),
32
+ };
33
+ } catch (error) {
34
+ // For demo, return mock data on error
35
+ return {
36
+ city,
37
+ temp: Math.floor(Math.random() * 30),
38
+ condition: 'Sunny (mock data)',
39
+ humidity: Math.floor(Math.random() * 100),
40
+ };
41
+ }
42
+ }
@@ -0,0 +1,70 @@
1
+ # Dashboard Widgets Demo
2
+
3
+ > **Tutorial**: [Managing Multiple Features](https://leanspec.dev/docs/tutorials/multiple-features)
4
+
5
+ ## Scenario
6
+
7
+ You're building an analytics dashboard for a SaaS product. The dashboard has some basic widgets (stats cards and a simple chart), but the product team wants to add three new widgets:
8
+ - **Recent Activity Feed** - Show latest user actions
9
+ - **Performance Metrics** - Display system health indicators
10
+ - **Quick Actions Panel** - Common shortcuts for users
11
+
12
+ Each widget needs to be designed, implemented, and integrated into the dashboard grid.
13
+
14
+ ## What's Here
15
+
16
+ A minimal React + Vite dashboard with:
17
+ - Grid layout for widgets
18
+ - Two existing widgets (Stats, Chart)
19
+ - Reusable widget wrapper component
20
+ - Mock data utilities
21
+
22
+ **Files:**
23
+ - `src/App.jsx` - Main dashboard component
24
+ - `src/components/Dashboard.jsx` - Dashboard grid layout
25
+ - `src/components/widgets/` - Existing widgets
26
+ - `src/utils/mockData.js` - Sample data generator
27
+ - `index.html` - Entry point
28
+
29
+ ## Getting Started
30
+
31
+ ```bash
32
+ # Install dependencies
33
+ npm install
34
+
35
+ # Start dev server
36
+ npm run dev
37
+
38
+ # Open http://localhost:5173 in your browser
39
+ ```
40
+
41
+ ## Your Mission
42
+
43
+ Add three new widgets to the dashboard. Follow the tutorial and ask your AI assistant:
44
+
45
+ > "Help me add three new widgets to this dashboard using LeanSpec: Recent Activity Feed, Performance Metrics, and Quick Actions Panel."
46
+
47
+ The AI will guide you through:
48
+ 1. Creating specs for each widget (or one unified spec)
49
+ 2. Designing the widget interfaces
50
+ 3. Implementing components
51
+ 4. Managing dependencies between widgets
52
+ 5. Testing the integrated dashboard
53
+
54
+ ## Current Structure
55
+
56
+ ```
57
+ Dashboard
58
+ ├── StatsWidget (implemented)
59
+ ├── ChartWidget (implemented)
60
+ ├── ActivityWidget (TODO)
61
+ ├── MetricsWidget (TODO)
62
+ └── ActionsWidget (TODO)
63
+ ```
64
+
65
+ ## Tips
66
+
67
+ - Each widget is self-contained with its own component
68
+ - Widgets use the `WidgetWrapper` component for consistent styling
69
+ - Mock data is in `utils/mockData.js` - add new generators as needed
70
+ - Consider which widgets share dependencies (e.g., both might need user data)
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Dashboard Widgets Demo</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.jsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "dashboard-widgets-demo",
3
+ "version": "1.0.0",
4
+ "description": "React Dashboard for LeanSpec Tutorial 2",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "keywords": ["leanspec", "tutorial", "demo"],
12
+ "author": "",
13
+ "license": "MIT",
14
+ "dependencies": {
15
+ "react": "^18.2.0",
16
+ "react-dom": "^18.2.0"
17
+ },
18
+ "devDependencies": {
19
+ "@vitejs/plugin-react": "^4.2.1",
20
+ "vite": "^5.0.0"
21
+ }
22
+ }
@@ -0,0 +1,20 @@
1
+ .app {
2
+ padding: 20px;
3
+ max-width: 1400px;
4
+ margin: 0 auto;
5
+ }
6
+
7
+ header {
8
+ margin-bottom: 30px;
9
+ }
10
+
11
+ header h1 {
12
+ margin: 0 0 5px 0;
13
+ color: #333;
14
+ }
15
+
16
+ header p {
17
+ margin: 0;
18
+ color: #666;
19
+ font-size: 14px;
20
+ }
@@ -0,0 +1,16 @@
1
+ import Dashboard from './components/Dashboard';
2
+ import './App.css';
3
+
4
+ function App() {
5
+ return (
6
+ <div className="app">
7
+ <header>
8
+ <h1>Analytics Dashboard</h1>
9
+ <p>Demo for LeanSpec Tutorial 2</p>
10
+ </header>
11
+ <Dashboard />
12
+ </div>
13
+ );
14
+ }
15
+
16
+ export default App;
@@ -0,0 +1,17 @@
1
+ .dashboard-grid {
2
+ display: grid;
3
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
4
+ gap: 20px;
5
+ }
6
+
7
+ @media (min-width: 768px) {
8
+ .dashboard-grid {
9
+ grid-template-columns: repeat(2, 1fr);
10
+ }
11
+ }
12
+
13
+ @media (min-width: 1200px) {
14
+ .dashboard-grid {
15
+ grid-template-columns: repeat(3, 1fr);
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ import StatsWidget from './widgets/StatsWidget';
2
+ import ChartWidget from './widgets/ChartWidget';
3
+ import './Dashboard.css';
4
+
5
+ function Dashboard() {
6
+ return (
7
+ <div className="dashboard-grid">
8
+ <StatsWidget />
9
+ <ChartWidget />
10
+ {/* TODO: Add new widgets here */}
11
+ </div>
12
+ );
13
+ }
14
+
15
+ export default Dashboard;
@@ -0,0 +1,23 @@
1
+ .widget {
2
+ background: white;
3
+ border-radius: 8px;
4
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
5
+ overflow: hidden;
6
+ }
7
+
8
+ .widget-header {
9
+ padding: 15px 20px;
10
+ border-bottom: 1px solid #eee;
11
+ background: #fafafa;
12
+ }
13
+
14
+ .widget-header h3 {
15
+ margin: 0;
16
+ font-size: 16px;
17
+ font-weight: 600;
18
+ color: #333;
19
+ }
20
+
21
+ .widget-body {
22
+ padding: 20px;
23
+ }
@@ -0,0 +1,16 @@
1
+ import './WidgetWrapper.css';
2
+
3
+ function WidgetWrapper({ title, children }) {
4
+ return (
5
+ <div className="widget">
6
+ <div className="widget-header">
7
+ <h3>{title}</h3>
8
+ </div>
9
+ <div className="widget-body">
10
+ {children}
11
+ </div>
12
+ </div>
13
+ );
14
+ }
15
+
16
+ export default WidgetWrapper;
@@ -0,0 +1,33 @@
1
+ .chart {
2
+ height: 200px;
3
+ }
4
+
5
+ .chart-bars {
6
+ display: flex;
7
+ align-items: flex-end;
8
+ justify-content: space-around;
9
+ height: 100%;
10
+ gap: 8px;
11
+ }
12
+
13
+ .chart-bar-container {
14
+ flex: 1;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+ height: 100%;
19
+ }
20
+
21
+ .chart-bar {
22
+ width: 100%;
23
+ background: linear-gradient(to top, #2563eb, #60a5fa);
24
+ border-radius: 4px 4px 0 0;
25
+ transition: height 0.3s ease;
26
+ min-height: 4px;
27
+ }
28
+
29
+ .chart-label {
30
+ margin-top: 8px;
31
+ font-size: 11px;
32
+ color: #666;
33
+ }
@@ -0,0 +1,28 @@
1
+ import WidgetWrapper from '../WidgetWrapper';
2
+ import { getChartData } from '../../utils/mockData';
3
+ import './ChartWidget.css';
4
+
5
+ function ChartWidget() {
6
+ const data = getChartData();
7
+ const maxValue = Math.max(...data.map(d => d.value));
8
+
9
+ return (
10
+ <WidgetWrapper title="Activity Trend">
11
+ <div className="chart">
12
+ <div className="chart-bars">
13
+ {data.map((item, index) => (
14
+ <div key={index} className="chart-bar-container">
15
+ <div
16
+ className="chart-bar"
17
+ style={{ height: `${(item.value / maxValue) * 100}%` }}
18
+ />
19
+ <div className="chart-label">{item.label}</div>
20
+ </div>
21
+ ))}
22
+ </div>
23
+ </div>
24
+ </WidgetWrapper>
25
+ );
26
+ }
27
+
28
+ export default ChartWidget;
@@ -0,0 +1,24 @@
1
+ .stats-grid {
2
+ display: grid;
3
+ grid-template-columns: repeat(2, 1fr);
4
+ gap: 15px;
5
+ }
6
+
7
+ .stat-item {
8
+ text-align: center;
9
+ padding: 10px;
10
+ }
11
+
12
+ .stat-value {
13
+ font-size: 28px;
14
+ font-weight: bold;
15
+ color: #2563eb;
16
+ margin-bottom: 5px;
17
+ }
18
+
19
+ .stat-label {
20
+ font-size: 12px;
21
+ color: #666;
22
+ text-transform: uppercase;
23
+ letter-spacing: 0.5px;
24
+ }