@programinglive/dev-workflow-mcp-server 1.5.7 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/pre-commit +2 -1
- package/CHANGELOG.md +20 -0
- package/docs/release-notes/RELEASE_NOTES.md +15 -0
- package/package.json +2 -3
- package/scripts/release.cjs +0 -1
- package/scripts/update-release-notes.cjs +18 -14
- package/tests/build.test.js +1 -10
- package/verify-build.js +176 -0
- package/web/README.md +116 -19
- package/web/app/layout.tsx +7 -4
- package/web/components/Features.tsx +6 -6
- package/web/components/Footer.tsx +2 -2
- package/web/components/Hero.tsx +18 -18
- package/web/components/History.tsx +27 -42
- package/web/components/Navbar.tsx +69 -26
- package/web/components/Terminal.tsx +1 -1
- package/web/components/ThemeProvider.tsx +58 -0
- package/web/tailwind.config.ts +1 -0
- package/web/tests/build.test.js +69 -0
- package/scripts/build-docs.js +0 -58
package/.husky/pre-commit
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Pre-commit hook - tests are run only during release
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [1.6.0](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.5.11...v1.6.0) (2025-12-06)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ✨ Features
|
|
9
|
+
|
|
10
|
+
* add responsive vertical monitor support and dark/light mode toggle ([5565765](https://github.com/programinglive/dev-workflow-mcp-server/commit/5565765f5e5db63d5239c9b82371ae1853a72085))
|
|
11
|
+
|
|
12
|
+
### [1.5.11](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.5.10...v1.5.11) (2025-12-05)
|
|
13
|
+
|
|
14
|
+
### [1.5.10](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.5.9...v1.5.10) (2025-12-05)
|
|
15
|
+
|
|
16
|
+
### [1.5.9](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.5.8...v1.5.9) (2025-12-05)
|
|
17
|
+
|
|
18
|
+
### [1.5.8](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.5.7...v1.5.8) (2025-12-05)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### 🐛 Bug Fixes
|
|
22
|
+
|
|
23
|
+
* **release:** amend tag to include release notes, remove build-docs.js and dist/docs ([a3cea9b](https://github.com/programinglive/dev-workflow-mcp-server/commit/a3cea9b612ee2d8f970bf7dd93bcbeef98ba0924))
|
|
24
|
+
|
|
5
25
|
### [1.5.7](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.5.6...v1.5.7) (2025-12-05)
|
|
6
26
|
|
|
7
27
|
|
|
@@ -4,6 +4,21 @@ Authoritative history of user-visible changes for the Development Workflow MCP S
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 1.5.11 — 2025-12-05
|
|
8
|
+
- See CHANGELOG for details.
|
|
9
|
+
|
|
10
|
+
## 1.5.10 — 2025-12-05
|
|
11
|
+
- See CHANGELOG for details.
|
|
12
|
+
|
|
13
|
+
## 1.5.9 — 2025-12-05
|
|
14
|
+
- See CHANGELOG for details.
|
|
15
|
+
|
|
16
|
+
## 1.5.8 — 2025-12-05 — 🐛 Bug Fixes
|
|
17
|
+
- **release:** amend tag to include release notes, remove build-docs.js and dist/docs (a3cea9b)
|
|
18
|
+
|
|
19
|
+
## 1.5.7 — 2025-12-05 — 🐛 Bug Fixes
|
|
20
|
+
- **ci:** checkout main branch for release notes and remove RELEASE_NOTES.html generation (5d50dc0)
|
|
21
|
+
|
|
7
22
|
## 1.5.6 — 2025-12-05 — 🐛 Bug Fixes
|
|
8
23
|
- **ci:** update github release workflow to use github-script (0990808)
|
|
9
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "MCP server to enforce development workflow discipline",
|
|
5
5
|
"homepage": "https://devworkflow.programinglive.com/",
|
|
6
6
|
"mcpName": "io.github.programinglive/dev-workflow-mcp-server",
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"build:server": "vite build",
|
|
15
15
|
"build:web": "npm run build --prefix web",
|
|
16
16
|
"watch:web": "npm run dev --prefix web",
|
|
17
|
-
"postbuild": "node scripts/build-docs.js",
|
|
18
17
|
"dev": "node --watch index.js",
|
|
19
18
|
"start": "node index.js",
|
|
20
19
|
"local": "node index.js",
|
|
@@ -54,7 +53,7 @@
|
|
|
54
53
|
"devDependencies": {
|
|
55
54
|
"@commitlint/cli": "^20.2.0",
|
|
56
55
|
"@commitlint/config-conventional": "^20.2.0",
|
|
57
|
-
"@programinglive/commiter": "^1.
|
|
56
|
+
"@programinglive/commiter": "^1.2.8",
|
|
58
57
|
"autoprefixer": "^10.4.22",
|
|
59
58
|
"husky": "^9.1.7",
|
|
60
59
|
"postcss": "^8.5.6",
|
package/scripts/release.cjs
CHANGED
|
@@ -67,29 +67,33 @@ function updateReleaseNotes({
|
|
|
67
67
|
|
|
68
68
|
function insertReleaseNotesEntry({ content, version, releaseDate, highlight, sectionHeading, detailBullets }) {
|
|
69
69
|
const lines = content.split('\n');
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (unreleasedIndex === -1) {
|
|
74
|
-
throw new Error('Unable to locate "## Unreleased" section in release notes.');
|
|
70
|
+
const headerSeparatorIndex = lines.findIndex((line) => line.trim().startsWith('|---------'));
|
|
71
|
+
if (headerSeparatorIndex === -1) {
|
|
72
|
+
throw new Error('Unable to locate release notes table header.');
|
|
75
73
|
}
|
|
76
74
|
|
|
75
|
+
const newRow = `| ${version} | ${releaseDate} | ${escapePipes(highlight)} |`;
|
|
76
|
+
lines.splice(headerSeparatorIndex + 1, 0, newRow);
|
|
77
|
+
|
|
77
78
|
const sectionLines = [];
|
|
78
79
|
sectionLines.push('');
|
|
79
80
|
const trimmedHeading = typeof sectionHeading === 'string' ? sectionHeading.trim() : '';
|
|
80
|
-
const headingSuffix = trimmedHeading ? `
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
sectionLines.push(
|
|
84
|
-
|
|
81
|
+
const headingSuffix = trimmedHeading ? ` – ${trimmedHeading}` : '';
|
|
82
|
+
sectionLines.push(`## ${version}${headingSuffix}`);
|
|
83
|
+
sectionLines.push('');
|
|
84
|
+
sectionLines.push(`Released on **${releaseDate}**.`);
|
|
85
|
+
sectionLines.push('');
|
|
85
86
|
for (const bullet of detailBullets) {
|
|
86
87
|
sectionLines.push(`- ${bullet}`);
|
|
87
88
|
}
|
|
89
|
+
sectionLines.push('');
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
const firstSectionIndex = lines.findIndex((line, idx) => idx > headerSeparatorIndex && line.startsWith('## '));
|
|
92
|
+
if (firstSectionIndex === -1) {
|
|
93
|
+
lines.push(...sectionLines);
|
|
94
|
+
} else {
|
|
95
|
+
lines.splice(firstSectionIndex, 0, ...sectionLines);
|
|
96
|
+
}
|
|
93
97
|
|
|
94
98
|
return lines.join('\n');
|
|
95
99
|
}
|
package/tests/build.test.js
CHANGED
|
@@ -8,7 +8,7 @@ import path from 'path';
|
|
|
8
8
|
const exec = promisify(execCallback);
|
|
9
9
|
|
|
10
10
|
test('build generates dist/index.mjs file', async () => {
|
|
11
|
-
const { stderr } = await exec('npm run build:server
|
|
11
|
+
const { stderr } = await exec('npm run build:server');
|
|
12
12
|
// Allow the known vite warning about node:process
|
|
13
13
|
const knownWarning = '[plugin vite:resolve] Module "node:process" has been externalized for browser compatibility';
|
|
14
14
|
const tailwindOutput = '≈ tailwindcss';
|
|
@@ -27,15 +27,6 @@ test('build generates dist/index.mjs file', async () => {
|
|
|
27
27
|
const content = await readFile(distPath, 'utf-8');
|
|
28
28
|
assert(content.includes('main'), 'Bundle should contain main function');
|
|
29
29
|
assert(content.length > 1000, 'Bundle should be substantial');
|
|
30
|
-
|
|
31
|
-
const docDir = path.join(process.cwd(), 'dist', 'docs');
|
|
32
|
-
const docFiles = ['README.html', 'web-dashboard.html'];
|
|
33
|
-
for (const file of docFiles) {
|
|
34
|
-
const fullPath = path.join(docDir, file);
|
|
35
|
-
await access(fullPath);
|
|
36
|
-
const docContent = await readFile(fullPath, 'utf-8');
|
|
37
|
-
assert(docContent.includes('<h1'), `${file} should contain HTML output`);
|
|
38
|
-
}
|
|
39
30
|
});
|
|
40
31
|
|
|
41
32
|
test('build script exists in package.json', async () => {
|
package/verify-build.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build Verification Script
|
|
5
|
+
* Verifies that the website builds successfully and key features are present
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
|
|
16
|
+
const webDir = path.join(__dirname, 'web');
|
|
17
|
+
const outDir = path.join(webDir, 'out');
|
|
18
|
+
|
|
19
|
+
let passedTests = 0;
|
|
20
|
+
let failedTests = 0;
|
|
21
|
+
|
|
22
|
+
function test(name, fn) {
|
|
23
|
+
try {
|
|
24
|
+
fn();
|
|
25
|
+
console.log(`✓ ${name}`);
|
|
26
|
+
passedTests++;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(`✗ ${name}`);
|
|
29
|
+
console.error(` Error: ${error.message}`);
|
|
30
|
+
failedTests++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log('\n🧪 Running Build Verification Tests...\n');
|
|
35
|
+
|
|
36
|
+
// Test 1: Build succeeds
|
|
37
|
+
test('Build completes successfully', () => {
|
|
38
|
+
try {
|
|
39
|
+
execSync('npm run build', { cwd: webDir, stdio: 'pipe' });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new Error('Build failed');
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Test 2: Output directory exists
|
|
46
|
+
test('Output directory exists', () => {
|
|
47
|
+
if (!fs.existsSync(outDir)) {
|
|
48
|
+
throw new Error('Output directory not found');
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Test 3: Index HTML exists
|
|
53
|
+
test('Homepage HTML generated', () => {
|
|
54
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
55
|
+
if (!fs.existsSync(indexPath)) {
|
|
56
|
+
throw new Error('index.html not found');
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Test 4: Check for key page content
|
|
61
|
+
test('Homepage contains "Dev Workflow"', () => {
|
|
62
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
63
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
64
|
+
if (!content.includes('Dev Workflow')) {
|
|
65
|
+
throw new Error('Dev Workflow text not found');
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Test 5: Check for hero section
|
|
70
|
+
test('Homepage contains hero section', () => {
|
|
71
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
72
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
73
|
+
if (!content.includes('Master Your') || !content.includes('Workflow')) {
|
|
74
|
+
throw new Error('Hero section not found');
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Test 6: Check for dark mode support
|
|
79
|
+
test('Dark mode classes included', () => {
|
|
80
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
81
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
82
|
+
if (!content.includes('dark:')) {
|
|
83
|
+
throw new Error('Dark mode classes not found');
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Test 7: Check for responsive design
|
|
88
|
+
test('Responsive design classes included', () => {
|
|
89
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
90
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
91
|
+
if (!content.includes('sm:') || !content.includes('md:') || !content.includes('lg:')) {
|
|
92
|
+
throw new Error('Responsive classes not found');
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Test 8: Check for features section
|
|
97
|
+
test('Features section present', () => {
|
|
98
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
99
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
100
|
+
if (!content.includes('Your Coding Conscience')) {
|
|
101
|
+
throw new Error('Features section not found');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Test 9: Check for history section
|
|
106
|
+
test('History section present', () => {
|
|
107
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
108
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
109
|
+
if (!content.includes('Workflow History')) {
|
|
110
|
+
throw new Error('History section not found');
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Test 10: Check for theme toggle
|
|
115
|
+
test('Theme toggle button present', () => {
|
|
116
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
117
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
118
|
+
// Check for theme toggle in minified form or readable form
|
|
119
|
+
if (!content.includes('toggleTheme') && !content.includes('theme') && !content.includes('Moon') && !content.includes('Sun')) {
|
|
120
|
+
throw new Error('Theme toggle not found');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Test 11: Check for viewport meta tag
|
|
125
|
+
test('Viewport meta tag for mobile', () => {
|
|
126
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
127
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
128
|
+
if (!content.includes('viewport') || !content.includes('width=device-width')) {
|
|
129
|
+
throw new Error('Viewport meta tag not found');
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Test 12: Check for localStorage theme persistence
|
|
134
|
+
test('Theme persistence in Navbar component', () => {
|
|
135
|
+
const navbarPath = path.join(webDir, 'components', 'Navbar.tsx');
|
|
136
|
+
const content = fs.readFileSync(navbarPath, 'utf-8');
|
|
137
|
+
if (!content.includes('localStorage')) {
|
|
138
|
+
throw new Error('localStorage not found in Navbar');
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Test 13: Check for system preference detection
|
|
143
|
+
test('System color scheme preference detection', () => {
|
|
144
|
+
const navbarPath = path.join(webDir, 'components', 'Navbar.tsx');
|
|
145
|
+
const content = fs.readFileSync(navbarPath, 'utf-8');
|
|
146
|
+
if (!content.includes('prefers-color-scheme')) {
|
|
147
|
+
throw new Error('prefers-color-scheme not found');
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Test 14: Check for responsive Navbar
|
|
152
|
+
test('Responsive Navbar implementation', () => {
|
|
153
|
+
const navbarPath = path.join(webDir, 'components', 'Navbar.tsx');
|
|
154
|
+
const content = fs.readFileSync(navbarPath, 'utf-8');
|
|
155
|
+
if (!content.includes('md:hidden') && !content.includes('hidden md:')) {
|
|
156
|
+
throw new Error('Responsive Navbar not found');
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Test 15: Check for vertical monitor optimization
|
|
161
|
+
test('Vertical monitor responsive design', () => {
|
|
162
|
+
const heroPath = path.join(webDir, 'components', 'Hero.tsx');
|
|
163
|
+
const content = fs.readFileSync(heroPath, 'utf-8');
|
|
164
|
+
if (!content.includes('xl:grid-cols-2')) {
|
|
165
|
+
throw new Error('Vertical monitor optimization not found');
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
console.log(`\n📊 Test Results: ${passedTests} passed, ${failedTests} failed\n`);
|
|
170
|
+
|
|
171
|
+
if (failedTests > 0) {
|
|
172
|
+
process.exit(1);
|
|
173
|
+
} else {
|
|
174
|
+
console.log('✅ All tests passed!\n');
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
package/web/README.md
CHANGED
|
@@ -1,36 +1,133 @@
|
|
|
1
|
-
|
|
1
|
+
# Dev Workflow Website
|
|
2
|
+
|
|
3
|
+
A modern, responsive website for the Dev Workflow MCP Server built with Next.js, React, and Tailwind CSS.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Responsive Design** - Optimized for all screen sizes including vertical/portrait monitors
|
|
8
|
+
- 🌓 **Dark/Light Mode Toggle** - User preference persisted to localStorage with system preference detection
|
|
9
|
+
- 📱 **Mobile First** - Fully responsive with adaptive layouts for sm, md, lg, and xl breakpoints
|
|
10
|
+
- ⚡ **Static Export** - Pre-rendered static HTML for fast Netlify deployment
|
|
11
|
+
- 🎨 **Modern UI** - Built with Tailwind CSS and Lucide React icons
|
|
12
|
+
- ♿ **Accessible** - Semantic HTML and proper ARIA labels
|
|
2
13
|
|
|
3
14
|
## Getting Started
|
|
4
15
|
|
|
5
|
-
|
|
16
|
+
### Development
|
|
6
17
|
|
|
7
18
|
```bash
|
|
8
19
|
npm run dev
|
|
9
|
-
# or
|
|
10
|
-
yarn dev
|
|
11
|
-
# or
|
|
12
|
-
pnpm dev
|
|
13
|
-
# or
|
|
14
|
-
bun dev
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
Open [http://localhost:3000](http://localhost:3000)
|
|
22
|
+
Open [http://localhost:3000](http://localhost:3000) to see the site.
|
|
23
|
+
|
|
24
|
+
### Build
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm run build
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Generates static HTML in the `out/` directory.
|
|
31
|
+
|
|
32
|
+
## Features Overview
|
|
33
|
+
|
|
34
|
+
### Responsive Vertical Monitor Support
|
|
35
|
+
- Grid layout switches from 2-column to single-column on smaller screens
|
|
36
|
+
- Terminal visualization hidden on screens smaller than xl breakpoint
|
|
37
|
+
- Responsive padding and text sizes scale appropriately
|
|
38
|
+
|
|
39
|
+
### Dark/Light Mode
|
|
40
|
+
- Toggle button in navbar (Sun/Moon icons)
|
|
41
|
+
- Theme preference stored in localStorage
|
|
42
|
+
- Respects system color scheme preference on first visit
|
|
43
|
+
- Smooth transitions between modes
|
|
44
|
+
|
|
45
|
+
## Deployment
|
|
46
|
+
|
|
47
|
+
### Netlify
|
|
18
48
|
|
|
19
|
-
|
|
49
|
+
1. **Prerequisites**: Install Netlify CLI
|
|
50
|
+
```bash
|
|
51
|
+
npm install -g netlify-cli
|
|
52
|
+
```
|
|
20
53
|
|
|
21
|
-
|
|
54
|
+
2. **Build the site**
|
|
55
|
+
```bash
|
|
56
|
+
npm run build
|
|
57
|
+
```
|
|
22
58
|
|
|
23
|
-
|
|
59
|
+
3. **Deploy**
|
|
60
|
+
```bash
|
|
61
|
+
netlify deploy --prod --dir=out
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
4. **Or connect GitHub for automatic deployments**
|
|
65
|
+
- Push to GitHub
|
|
66
|
+
- Connect repo to Netlify
|
|
67
|
+
- Netlify automatically builds and deploys on push
|
|
68
|
+
|
|
69
|
+
### Environment Setup
|
|
70
|
+
|
|
71
|
+
Create a `.env.local` file if needed for environment variables (currently not required).
|
|
72
|
+
|
|
73
|
+
## Testing
|
|
74
|
+
|
|
75
|
+
Run the build verification tests:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
node verify-build.js
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Tests verify:
|
|
82
|
+
- Build completes successfully
|
|
83
|
+
- All pages generate correctly
|
|
84
|
+
- Dark mode classes are included
|
|
85
|
+
- Responsive design classes present
|
|
86
|
+
- Theme toggle functionality
|
|
87
|
+
- Viewport meta tags for mobile
|
|
88
|
+
- Vertical monitor optimization
|
|
89
|
+
|
|
90
|
+
## Project Structure
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
web/
|
|
94
|
+
├── app/
|
|
95
|
+
│ ├── layout.tsx # Root layout with ThemeProvider
|
|
96
|
+
│ ├── page.tsx # Homepage
|
|
97
|
+
│ ├── globals.css # Global styles
|
|
98
|
+
│ └── docs/ # Documentation pages
|
|
99
|
+
├── components/
|
|
100
|
+
│ ├── Navbar.tsx # Navigation with theme toggle
|
|
101
|
+
│ ├── Hero.tsx # Hero section
|
|
102
|
+
│ ├── Features.tsx # Features grid
|
|
103
|
+
│ ├── History.tsx # Workflow history section
|
|
104
|
+
│ ├── Terminal.tsx # Animated terminal demo
|
|
105
|
+
│ ├── Footer.tsx # Footer
|
|
106
|
+
│ └── ThemeProvider.tsx # Theme context provider
|
|
107
|
+
├── public/ # Static assets
|
|
108
|
+
├── tailwind.config.ts # Tailwind configuration
|
|
109
|
+
├── next.config.ts # Next.js configuration
|
|
110
|
+
└── package.json
|
|
111
|
+
```
|
|
24
112
|
|
|
25
|
-
|
|
113
|
+
## Technologies
|
|
26
114
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
115
|
+
- **Framework**: Next.js 16 with Turbopack
|
|
116
|
+
- **Styling**: Tailwind CSS 3
|
|
117
|
+
- **Icons**: Lucide React
|
|
118
|
+
- **Language**: TypeScript
|
|
119
|
+
- **Deployment**: Netlify (static export)
|
|
29
120
|
|
|
30
|
-
|
|
121
|
+
## Browser Support
|
|
31
122
|
|
|
32
|
-
|
|
123
|
+
- Chrome/Edge (latest)
|
|
124
|
+
- Firefox (latest)
|
|
125
|
+
- Safari (latest)
|
|
126
|
+
- Mobile browsers (iOS Safari, Chrome Mobile)
|
|
33
127
|
|
|
34
|
-
|
|
128
|
+
## Performance
|
|
35
129
|
|
|
36
|
-
|
|
130
|
+
- Static HTML export for instant page loads
|
|
131
|
+
- Optimized images with Next.js Image component
|
|
132
|
+
- Minimal JavaScript bundle
|
|
133
|
+
- CSS-in-JS with Tailwind for efficient styling
|
package/web/app/layout.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|
|
2
2
|
import "./globals.css";
|
|
3
3
|
import Navbar from "@/components/Navbar";
|
|
4
4
|
import Footer from "@/components/Footer";
|
|
5
|
+
import { ThemeProvider } from "@/components/ThemeProvider";
|
|
5
6
|
|
|
6
7
|
export const metadata: Metadata = {
|
|
7
8
|
title: "Dev Workflow Dashboard",
|
|
@@ -26,10 +27,12 @@ export default function RootLayout({
|
|
|
26
27
|
}>) {
|
|
27
28
|
return (
|
|
28
29
|
<html lang="en" className="scroll-smooth">
|
|
29
|
-
<body className="antialiased bg-[#0a0a0f] text-white">
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
<body className="antialiased bg-white dark:bg-[#0a0a0f] text-gray-900 dark:text-white transition-colors duration-300">
|
|
31
|
+
<ThemeProvider>
|
|
32
|
+
<Navbar />
|
|
33
|
+
{children}
|
|
34
|
+
<Footer />
|
|
35
|
+
</ThemeProvider>
|
|
33
36
|
</body>
|
|
34
37
|
</html>
|
|
35
38
|
);
|
|
@@ -2,10 +2,10 @@ import { ShieldCheck, TestTube, FileText, GitBranch, History, Zap } from "lucide
|
|
|
2
2
|
|
|
3
3
|
export default function Features() {
|
|
4
4
|
return (
|
|
5
|
-
<section id="features" className="py-20 px-4">
|
|
5
|
+
<section id="features" className="py-12 sm:py-16 lg:py-20 px-3 sm:px-4 bg-gray-50 dark:bg-transparent transition-colors duration-300">
|
|
6
6
|
<div className="max-w-7xl mx-auto">
|
|
7
|
-
<h2 className="text-3xl md:text-4xl font-bold text-center mb-16">Your Coding Conscience</h2>
|
|
8
|
-
<div className="grid
|
|
7
|
+
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold text-center mb-8 sm:mb-12 lg:mb-16 text-gray-900 dark:text-white">Your Coding Conscience</h2>
|
|
8
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
|
|
9
9
|
<FeatureCard
|
|
10
10
|
icon={<ShieldCheck className="w-6 h-6 text-indigo-400" />}
|
|
11
11
|
title="Enforced Discipline"
|
|
@@ -50,12 +50,12 @@ export default function Features() {
|
|
|
50
50
|
|
|
51
51
|
function FeatureCard({ icon, title, description, bg }: { icon: React.ReactNode, title: string, description: string, bg: string }) {
|
|
52
52
|
return (
|
|
53
|
-
<div className="p-6 rounded-xl bg-white/5 border border-white/10 hover:bg-white/10 transition">
|
|
53
|
+
<div className="p-6 rounded-xl bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 hover:bg-gray-100 dark:hover:bg-white/10 transition">
|
|
54
54
|
<div className={`w-12 h-12 rounded-lg ${bg} flex items-center justify-center mb-4`}>
|
|
55
55
|
{icon}
|
|
56
56
|
</div>
|
|
57
|
-
<h3 className="text-xl font-semibold mb-2">{title}</h3>
|
|
58
|
-
<p className="text-gray-400">{description}</p>
|
|
57
|
+
<h3 className="text-xl font-semibold mb-2 text-gray-900 dark:text-white">{title}</h3>
|
|
58
|
+
<p className="text-gray-600 dark:text-gray-400">{description}</p>
|
|
59
59
|
</div>
|
|
60
60
|
);
|
|
61
61
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default function Footer() {
|
|
2
2
|
return (
|
|
3
|
-
<footer className="py-12 px-4 border-t border-white/10">
|
|
4
|
-
<div className="max-w-7xl mx-auto text-center text-gray-400">
|
|
3
|
+
<footer className="py-12 px-4 border-t border-gray-200 dark:border-white/10 bg-white dark:bg-transparent transition-colors duration-300">
|
|
4
|
+
<div className="max-w-7xl mx-auto text-center text-gray-600 dark:text-gray-400">
|
|
5
5
|
<p>Built with ❤️ for developers who care about quality</p>
|
|
6
6
|
</div>
|
|
7
7
|
</footer>
|
package/web/components/Hero.tsx
CHANGED
|
@@ -27,9 +27,9 @@ export default function Hero() {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<section className="min-h-screen flex items-center justify-center px-4 pt-16 relative overflow-hidden">
|
|
31
|
-
<div className="absolute inset-0 bg-gradient-to-br from-indigo-500/10 via-purple-500/10 to-pink-500/10"></div>
|
|
32
|
-
<div className="max-w-7xl mx-auto grid
|
|
30
|
+
<section className="min-h-screen flex items-center justify-center px-3 sm:px-4 pt-16 relative overflow-hidden bg-white dark:bg-transparent transition-colors duration-300">
|
|
31
|
+
<div className="absolute inset-0 bg-gradient-to-br from-indigo-500/5 dark:from-indigo-500/10 via-purple-500/5 dark:via-purple-500/10 to-pink-500/5 dark:to-pink-500/10"></div>
|
|
32
|
+
<div className="max-w-7xl mx-auto grid xl:grid-cols-2 gap-6 sm:gap-8 lg:gap-12 items-center relative z-10">
|
|
33
33
|
{/* Left Column - Content */}
|
|
34
34
|
<div>
|
|
35
35
|
<div className="animate-fade-in-up" style={{ animationDelay: "0.1s" }}>
|
|
@@ -39,47 +39,47 @@ export default function Hero() {
|
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
41
41
|
|
|
42
|
-
<h1 className="text-
|
|
42
|
+
<h1 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold mb-4 sm:mb-6 leading-tight animate-fade-in-up" style={{ animationDelay: "0.2s" }}>
|
|
43
43
|
Master Your<br />
|
|
44
44
|
<span className="bg-gradient-to-r from-indigo-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">Workflow</span>
|
|
45
45
|
</h1>
|
|
46
|
-
<p className="text-lg md:text-xl text-gray-400 mb-8 max-w-xl animate-fade-in-up" style={{ animationDelay: "0.3s" }}>
|
|
46
|
+
<p className="text-base sm:text-lg md:text-xl text-gray-600 dark:text-gray-400 mb-6 sm:mb-8 max-w-xl animate-fade-in-up" style={{ animationDelay: "0.3s" }}>
|
|
47
47
|
An MCP server that acts as your coding conscience. Enforce test-driven development, ensure documentation, and never skip a step in your release process.
|
|
48
48
|
</p>
|
|
49
49
|
<div className="flex flex-col sm:flex-row gap-4 mb-8 animate-fade-in-up" style={{ animationDelay: "0.4s" }}>
|
|
50
|
-
<Link href="#history" className="px-6 py-3 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-lg font-semibold hover:shadow-lg hover:shadow-indigo-500/50 transition inline-flex items-center justify-center gap-2">
|
|
50
|
+
<Link href="#history" className="px-6 py-3 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-lg font-semibold hover:shadow-lg hover:shadow-indigo-500/50 transition inline-flex items-center justify-center gap-2 text-white">
|
|
51
51
|
View History <ArrowRight className="w-4 h-4" />
|
|
52
52
|
</Link>
|
|
53
|
-
<Link href="/docs" className="px-6 py-3 bg-white/5 border border-white/10 rounded-lg font-semibold hover:bg-white/10 transition text-center">
|
|
53
|
+
<Link href="/docs" className="px-6 py-3 bg-gray-200 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg font-semibold hover:bg-gray-300 dark:hover:bg-white/10 transition text-center text-gray-900 dark:text-white">
|
|
54
54
|
Documentation
|
|
55
55
|
</Link>
|
|
56
56
|
</div>
|
|
57
|
-
<div className="flex flex-wrap gap-
|
|
57
|
+
<div className="flex flex-wrap gap-4 sm:gap-6 lg:gap-8 text-xs sm:text-sm animate-fade-in-up" style={{ animationDelay: "0.5s" }}>
|
|
58
58
|
<div>
|
|
59
|
-
<div className="text-2xl font-bold">{version ?? "..."}</div>
|
|
60
|
-
<div className="text-gray-500">Latest Version</div>
|
|
59
|
+
<div className="text-xl sm:text-2xl font-bold text-gray-900 dark:text-white">{version ?? "..."}</div>
|
|
60
|
+
<div className="text-gray-600 dark:text-gray-500">Latest Version</div>
|
|
61
61
|
</div>
|
|
62
62
|
<div>
|
|
63
|
-
<div className="text-2xl font-bold">MIT</div>
|
|
64
|
-
<div className="text-gray-500">License</div>
|
|
63
|
+
<div className="text-xl sm:text-2xl font-bold text-gray-900 dark:text-white">MIT</div>
|
|
64
|
+
<div className="text-gray-600 dark:text-gray-500">License</div>
|
|
65
65
|
</div>
|
|
66
66
|
<div>
|
|
67
|
-
<div className="text-2xl font-bold">Node 18+</div>
|
|
68
|
-
<div className="text-gray-500">Compatibility</div>
|
|
67
|
+
<div className="text-xl sm:text-2xl font-bold text-gray-900 dark:text-white">Node 18+</div>
|
|
68
|
+
<div className="text-gray-600 dark:text-gray-500">Compatibility</div>
|
|
69
69
|
</div>
|
|
70
70
|
{downloads !== null && (
|
|
71
71
|
<div>
|
|
72
|
-
<div className="text-2xl font-bold">{formatDownloads(downloads)}</div>
|
|
73
|
-
<div className="text-gray-500">Downloads/mo</div>
|
|
72
|
+
<div className="text-xl sm:text-2xl font-bold text-gray-900 dark:text-white">{formatDownloads(downloads)}</div>
|
|
73
|
+
<div className="text-gray-600 dark:text-gray-500">Downloads/mo</div>
|
|
74
74
|
</div>
|
|
75
75
|
)}
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
78
78
|
{/* Right Column - Terminal */}
|
|
79
|
-
<div className="hidden
|
|
79
|
+
<div className="hidden xl:block">
|
|
80
80
|
<div className="relative">
|
|
81
81
|
<div className="absolute inset-0 bg-gradient-to-r from-indigo-500/20 to-purple-500/20 blur-3xl"></div>
|
|
82
|
-
<div className="relative bg-[#1a1a24] rounded-xl border border-white/10 p-6 shadow-2xl">
|
|
82
|
+
<div className="relative bg-[#1a1a24] rounded-xl border border-white/10 p-4 sm:p-6 shadow-2xl">
|
|
83
83
|
<div className="flex items-center gap-2 mb-4">
|
|
84
84
|
<div className="w-3 h-3 rounded-full bg-red-500"></div>
|
|
85
85
|
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
|
|
@@ -25,30 +25,15 @@ export default function History() {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
const loadData = async () => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
// Placeholder for future API integration
|
|
29
|
+
// For now, show empty state
|
|
30
|
+
setHistory([]);
|
|
31
|
+
setTotalPages(1);
|
|
32
|
+
setSummary({
|
|
33
|
+
summary: {
|
|
34
|
+
totalTasks: 0,
|
|
35
|
+
},
|
|
34
36
|
});
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const res = await fetch(`/api/history?${params}`);
|
|
38
|
-
const data = await res.json();
|
|
39
|
-
setHistory(data.data || []);
|
|
40
|
-
setTotalPages(data.totalPages || 1);
|
|
41
|
-
} catch (err) {
|
|
42
|
-
console.error("Failed to load history", err);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const res = await fetch(`/api/summary?${params}`);
|
|
47
|
-
const data = await res.json();
|
|
48
|
-
setSummary(data);
|
|
49
|
-
} catch (err) {
|
|
50
|
-
console.error("Failed to load summary", err);
|
|
51
|
-
}
|
|
52
37
|
};
|
|
53
38
|
|
|
54
39
|
useEffect(() => {
|
|
@@ -61,34 +46,34 @@ export default function History() {
|
|
|
61
46
|
};
|
|
62
47
|
|
|
63
48
|
return (
|
|
64
|
-
<section id="history" className="py-20 px-4 bg-white/5">
|
|
49
|
+
<section id="history" className="py-12 sm:py-16 lg:py-20 px-3 sm:px-4 bg-gray-50 dark:bg-white/5 transition-colors duration-300">
|
|
65
50
|
<div className="max-w-7xl mx-auto">
|
|
66
|
-
<h2 className="text-3xl md:text-4xl font-bold mb-8">Workflow History</h2>
|
|
67
|
-
<p className="text-gray-400 mb-12">Track your development progress and analyze your workflow patterns.</p>
|
|
68
|
-
<div className="bg-[#0a0a0f] rounded-xl border border-white/10 p-6">
|
|
69
|
-
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4 mb-6">
|
|
51
|
+
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold mb-4 sm:mb-8 text-gray-900 dark:text-white">Workflow History</h2>
|
|
52
|
+
<p className="text-sm sm:text-base text-gray-600 dark:text-gray-400 mb-8 sm:mb-12">Track your development progress and analyze your workflow patterns.</p>
|
|
53
|
+
<div className="bg-white dark:bg-[#0a0a0f] rounded-xl border border-gray-200 dark:border-white/10 p-4 sm:p-6 transition-colors duration-300">
|
|
54
|
+
<div className="grid gap-3 sm:gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 mb-6">
|
|
70
55
|
<input
|
|
71
56
|
type="text"
|
|
72
57
|
placeholder="User ID"
|
|
73
58
|
value={filters.userId}
|
|
74
59
|
onChange={(e) => setFilters({ ...filters, userId: e.target.value })}
|
|
75
|
-
className="px-4 py-2 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-white"
|
|
60
|
+
className="px-4 py-2 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
|
|
76
61
|
/>
|
|
77
62
|
<input
|
|
78
63
|
type="date"
|
|
79
64
|
value={filters.startDate}
|
|
80
65
|
onChange={(e) => setFilters({ ...filters, startDate: e.target.value })}
|
|
81
|
-
className="px-4 py-2 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 [color-scheme:dark]"
|
|
66
|
+
className="px-4 py-2 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-gray-900 dark:text-white [color-scheme:light] dark:[color-scheme:dark]"
|
|
82
67
|
/>
|
|
83
68
|
<input
|
|
84
69
|
type="date"
|
|
85
70
|
value={filters.endDate}
|
|
86
71
|
onChange={(e) => setFilters({ ...filters, endDate: e.target.value })}
|
|
87
|
-
className="px-4 py-2 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 [color-scheme:dark]"
|
|
72
|
+
className="px-4 py-2 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-gray-900 dark:text-white [color-scheme:light] dark:[color-scheme:dark]"
|
|
88
73
|
/>
|
|
89
74
|
<button
|
|
90
75
|
onClick={handleApplyFilters}
|
|
91
|
-
className="px-4 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-lg font-semibold hover:shadow-lg hover:shadow-indigo-500/50 transition"
|
|
76
|
+
className="px-4 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-lg font-semibold hover:shadow-lg hover:shadow-indigo-500/50 transition text-white"
|
|
92
77
|
>
|
|
93
78
|
Apply Filters
|
|
94
79
|
</button>
|
|
@@ -96,25 +81,25 @@ export default function History() {
|
|
|
96
81
|
|
|
97
82
|
{summary && (
|
|
98
83
|
<div className="mb-6">
|
|
99
|
-
<h3 className="text-lg font-semibold mb-4">Project Summary</h3>
|
|
100
|
-
<div className="text-gray-400">
|
|
84
|
+
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Project Summary</h3>
|
|
85
|
+
<div className="text-gray-600 dark:text-gray-400">
|
|
101
86
|
<p>Total tasks: {summary.summary?.totalTasks || 0}</p>
|
|
102
87
|
</div>
|
|
103
88
|
</div>
|
|
104
89
|
)}
|
|
105
90
|
|
|
106
91
|
<div>
|
|
107
|
-
<h3 className="text-lg font-semibold mb-4">Recent History</h3>
|
|
92
|
+
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Recent History</h3>
|
|
108
93
|
<div className="space-y-3">
|
|
109
94
|
{history.length > 0 ? (
|
|
110
95
|
history.map((row, i) => (
|
|
111
|
-
<div key={i} className="p-4 rounded-lg bg-white/5 border border-white/10">
|
|
112
|
-
<div className="font-semibold">{row.task_description}</div>
|
|
113
|
-
<div className="text-sm text-gray-400 mt-1">{new Date(row.created_at).toLocaleString()}</div>
|
|
96
|
+
<div key={i} className="p-4 rounded-lg bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10">
|
|
97
|
+
<div className="font-semibold text-gray-900 dark:text-white">{row.task_description}</div>
|
|
98
|
+
<div className="text-sm text-gray-600 dark:text-gray-400 mt-1">{new Date(row.created_at).toLocaleString()}</div>
|
|
114
99
|
</div>
|
|
115
100
|
))
|
|
116
101
|
) : (
|
|
117
|
-
<p className="text-gray-500">No history yet.</p>
|
|
102
|
+
<p className="text-gray-500 dark:text-gray-400">No history yet.</p>
|
|
118
103
|
)}
|
|
119
104
|
</div>
|
|
120
105
|
|
|
@@ -122,15 +107,15 @@ export default function History() {
|
|
|
122
107
|
<button
|
|
123
108
|
onClick={() => setPage(p => Math.max(1, p - 1))}
|
|
124
109
|
disabled={page <= 1}
|
|
125
|
-
className="px-4 py-2 bg-white/5 border border-white/10 rounded-lg hover:bg-white/10 disabled:opacity-50"
|
|
110
|
+
className="px-4 py-2 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg hover:bg-gray-200 dark:hover:bg-white/10 disabled:opacity-50 text-gray-900 dark:text-white transition"
|
|
126
111
|
>
|
|
127
112
|
Previous
|
|
128
113
|
</button>
|
|
129
|
-
<span className="text-sm text-gray-400">Page {page} of {totalPages}</span>
|
|
114
|
+
<span className="text-sm text-gray-600 dark:text-gray-400">Page {page} of {totalPages}</span>
|
|
130
115
|
<button
|
|
131
116
|
onClick={() => setPage(p => Math.min(totalPages, p + 1))}
|
|
132
117
|
disabled={page >= totalPages}
|
|
133
|
-
className="px-4 py-2 bg-white/5 border border-white/10 rounded-lg hover:bg-white/10 disabled:opacity-50"
|
|
118
|
+
className="px-4 py-2 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg hover:bg-gray-200 dark:hover:bg-white/10 disabled:opacity-50 text-gray-900 dark:text-white transition"
|
|
134
119
|
>
|
|
135
120
|
Next
|
|
136
121
|
</button>
|
|
@@ -1,57 +1,100 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
|
-
import { Github, Menu, X } from "lucide-react";
|
|
5
|
+
import { Github, Menu, X, Moon, Sun } from "lucide-react";
|
|
6
6
|
import Image from "next/image";
|
|
7
7
|
|
|
8
8
|
export default function Navbar() {
|
|
9
9
|
const [isOpen, setIsOpen] = useState(false);
|
|
10
|
+
const [theme, setTheme] = useState<"light" | "dark">("dark");
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
// Get theme from localStorage or system preference
|
|
14
|
+
const savedTheme = localStorage.getItem("theme") as "light" | "dark" | null;
|
|
15
|
+
if (savedTheme) {
|
|
16
|
+
setTheme(savedTheme);
|
|
17
|
+
} else {
|
|
18
|
+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
19
|
+
setTheme(prefersDark ? "dark" : "light");
|
|
20
|
+
}
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
const toggleTheme = () => {
|
|
24
|
+
const newTheme = theme === "dark" ? "light" : "dark";
|
|
25
|
+
setTheme(newTheme);
|
|
26
|
+
localStorage.setItem("theme", newTheme);
|
|
27
|
+
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
|
28
|
+
};
|
|
10
29
|
|
|
11
30
|
return (
|
|
12
|
-
<nav className="fixed top-0 left-0 right-0 z-50 bg-[#0a0a0f]/80 backdrop-blur-lg border-b border-white/10">
|
|
13
|
-
<div className="max-w-7xl mx-auto px-
|
|
14
|
-
<div className="flex items-center justify-between h-16">
|
|
31
|
+
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 dark:bg-[#0a0a0f]/80 backdrop-blur-lg border-b border-gray-200 dark:border-white/10 transition-colors duration-300">
|
|
32
|
+
<div className="max-w-7xl mx-auto px-3 sm:px-4 lg:px-8">
|
|
33
|
+
<div className="flex items-center justify-between h-14 sm:h-16">
|
|
15
34
|
<Link href="/" className="flex items-center gap-2">
|
|
16
|
-
<Image src="/assets/logo.png" alt="Dev Workflow" width={
|
|
17
|
-
<span className="text-lg font-bold">Dev Workflow</span>
|
|
35
|
+
<Image src="/assets/logo.png" alt="Dev Workflow" width={28} height={28} className="rounded-lg w-7 h-7 sm:w-8 sm:h-8" />
|
|
36
|
+
<span className="text-sm sm:text-lg font-bold text-gray-900 dark:text-white">Dev Workflow</span>
|
|
18
37
|
</Link>
|
|
19
|
-
<div className="hidden md:flex items-center gap-6">
|
|
20
|
-
<Link href="/#features" className="text-gray-300 hover:text-white transition">Features</Link>
|
|
21
|
-
<Link href="/#history" className="text-gray-300 hover:text-white transition">History</Link>
|
|
22
|
-
<Link href="/#api" className="text-gray-300 hover:text-white transition">API</Link>
|
|
23
|
-
<Link href="/docs" className="text-gray-300 hover:text-white transition">Docs</Link>
|
|
38
|
+
<div className="hidden md:flex items-center gap-3 lg:gap-6">
|
|
39
|
+
<Link href="/#features" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">Features</Link>
|
|
40
|
+
<Link href="/#history" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">History</Link>
|
|
41
|
+
<Link href="/#api" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">API</Link>
|
|
42
|
+
<Link href="/docs" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">Docs</Link>
|
|
43
|
+
<button
|
|
44
|
+
onClick={toggleTheme}
|
|
45
|
+
className="flex items-center justify-center w-8 h-8 rounded-lg bg-gray-100 dark:bg-white/10 hover:bg-gray-200 dark:hover:bg-white/20 transition text-gray-900 dark:text-white"
|
|
46
|
+
aria-label="Toggle theme"
|
|
47
|
+
>
|
|
48
|
+
{theme === "dark" ? (
|
|
49
|
+
<Sun className="w-4 h-4" />
|
|
50
|
+
) : (
|
|
51
|
+
<Moon className="w-4 h-4" />
|
|
52
|
+
)}
|
|
53
|
+
</button>
|
|
24
54
|
<a
|
|
25
55
|
href="https://github.com/programinglive/dev-workflow-mcp-server"
|
|
26
56
|
target="_blank"
|
|
27
57
|
rel="noopener noreferrer"
|
|
28
|
-
className="flex items-center gap-
|
|
58
|
+
className="flex items-center gap-1.5 px-3 py-1.5 lg:px-4 lg:py-2 bg-gray-100 dark:bg-white/5 hover:bg-gray-200 dark:hover:bg-white/10 rounded-lg transition text-xs lg:text-sm text-gray-900 dark:text-white"
|
|
29
59
|
>
|
|
30
|
-
<Github className="w-4 h-4" />
|
|
60
|
+
<Github className="w-3.5 h-3.5 lg:w-4 lg:h-4" />
|
|
31
61
|
GitHub
|
|
32
62
|
</a>
|
|
33
63
|
</div>
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
64
|
+
<div className="md:hidden flex items-center gap-2">
|
|
65
|
+
<button
|
|
66
|
+
onClick={toggleTheme}
|
|
67
|
+
className="p-2 rounded-lg bg-gray-100 dark:bg-white/10 hover:bg-gray-200 dark:hover:bg-white/20 transition text-gray-900 dark:text-white"
|
|
68
|
+
aria-label="Toggle theme"
|
|
69
|
+
>
|
|
70
|
+
{theme === "dark" ? (
|
|
71
|
+
<Sun className="w-5 h-5" />
|
|
72
|
+
) : (
|
|
73
|
+
<Moon className="w-5 h-5" />
|
|
74
|
+
)}
|
|
75
|
+
</button>
|
|
76
|
+
<button
|
|
77
|
+
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-white/5 transition text-gray-900 dark:text-white"
|
|
78
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
79
|
+
>
|
|
80
|
+
{isOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
40
83
|
</div>
|
|
41
84
|
</div>
|
|
42
85
|
{/* Mobile Menu */}
|
|
43
86
|
{isOpen && (
|
|
44
|
-
<div className="md:hidden border-t border-white/10 bg-[#0a0a0f]">
|
|
87
|
+
<div className="md:hidden border-t border-gray-200 dark:border-white/10 bg-white dark:bg-[#0a0a0f] transition-colors duration-300">
|
|
45
88
|
<div className="px-4 py-4 space-y-3">
|
|
46
|
-
<Link href="/#features" className="block text-gray-300 hover:text-white transition" onClick={() => setIsOpen(false)}>Features</Link>
|
|
47
|
-
<Link href="/#history" className="block text-gray-300 hover:text-white transition" onClick={() => setIsOpen(false)}>History</Link>
|
|
48
|
-
<Link href="/#api" className="block text-gray-300 hover:text-white transition" onClick={() => setIsOpen(false)}>API</Link>
|
|
49
|
-
<Link href="/docs" className="block text-gray-300 hover:text-white transition" onClick={() => setIsOpen(false)}>Docs</Link>
|
|
89
|
+
<Link href="/#features" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>Features</Link>
|
|
90
|
+
<Link href="/#history" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>History</Link>
|
|
91
|
+
<Link href="/#api" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>API</Link>
|
|
92
|
+
<Link href="/docs" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>Docs</Link>
|
|
50
93
|
<a
|
|
51
94
|
href="https://github.com/programinglive/dev-workflow-mcp-server"
|
|
52
95
|
target="_blank"
|
|
53
96
|
rel="noopener noreferrer"
|
|
54
|
-
className="flex items-center gap-2 text-gray-300 hover:text-white transition"
|
|
97
|
+
className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition"
|
|
55
98
|
>
|
|
56
99
|
<Github className="w-4 h-4" />
|
|
57
100
|
GitHub
|
|
@@ -107,7 +107,7 @@ export default function Terminal() {
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
return (
|
|
110
|
-
<div ref={containerRef} className="font-mono text-sm space-y-2 min-h-[400px]">
|
|
110
|
+
<div ref={containerRef} className="font-mono text-xs sm:text-sm space-y-1.5 sm:space-y-2 min-h-[300px] sm:min-h-[400px]">
|
|
111
111
|
{lines}
|
|
112
112
|
</div>
|
|
113
113
|
);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
type Theme = "light" | "dark";
|
|
6
|
+
|
|
7
|
+
interface ThemeContextType {
|
|
8
|
+
theme: Theme;
|
|
9
|
+
toggleTheme: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
13
|
+
|
|
14
|
+
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
15
|
+
const [theme, setTheme] = useState<Theme>("dark");
|
|
16
|
+
const [mounted, setMounted] = useState(false);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setMounted(true);
|
|
20
|
+
// Check localStorage for saved theme preference
|
|
21
|
+
const savedTheme = localStorage.getItem("theme") as Theme | null;
|
|
22
|
+
if (savedTheme) {
|
|
23
|
+
setTheme(savedTheme);
|
|
24
|
+
document.documentElement.classList.toggle("dark", savedTheme === "dark");
|
|
25
|
+
} else {
|
|
26
|
+
// Check system preference
|
|
27
|
+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
28
|
+
const initialTheme = prefersDark ? "dark" : "light";
|
|
29
|
+
setTheme(initialTheme);
|
|
30
|
+
document.documentElement.classList.toggle("dark", prefersDark);
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const toggleTheme = () => {
|
|
35
|
+
const newTheme = theme === "dark" ? "light" : "dark";
|
|
36
|
+
setTheme(newTheme);
|
|
37
|
+
localStorage.setItem("theme", newTheme);
|
|
38
|
+
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (!mounted) {
|
|
42
|
+
return <>{children}</>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
|
47
|
+
{children}
|
|
48
|
+
</ThemeContext.Provider>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useTheme() {
|
|
53
|
+
const context = useContext(ThemeContext);
|
|
54
|
+
if (context === undefined) {
|
|
55
|
+
throw new Error("useTheme must be used within a ThemeProvider");
|
|
56
|
+
}
|
|
57
|
+
return context;
|
|
58
|
+
}
|
package/web/tailwind.config.ts
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build Verification Tests
|
|
3
|
+
* Tests that the website builds successfully and key features are present
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const webDir = path.join(__dirname, '..');
|
|
13
|
+
const outDir = path.join(webDir, 'out');
|
|
14
|
+
|
|
15
|
+
describe('Build Tests', () => {
|
|
16
|
+
test('should build successfully', () => {
|
|
17
|
+
execSync('npm run build', { cwd: webDir, stdio: 'pipe' });
|
|
18
|
+
expect(fs.existsSync(outDir)).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('should generate homepage', () => {
|
|
22
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
23
|
+
expect(fs.existsSync(indexPath)).toBe(true);
|
|
24
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
25
|
+
expect(content).toContain('Dev Workflow');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('should include dark mode support', () => {
|
|
29
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
30
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
31
|
+
expect(content).toContain('dark:');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('should include responsive design', () => {
|
|
35
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
36
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
37
|
+
expect(content).toContain('sm:');
|
|
38
|
+
expect(content).toContain('md:');
|
|
39
|
+
expect(content).toContain('lg:');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('Theme Toggle Tests', () => {
|
|
44
|
+
test('should have theme persistence', () => {
|
|
45
|
+
const navbarPath = path.join(webDir, 'components', 'Navbar.tsx');
|
|
46
|
+
const content = fs.readFileSync(navbarPath, 'utf-8');
|
|
47
|
+
expect(content).toContain('localStorage');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should detect system preference', () => {
|
|
51
|
+
const navbarPath = path.join(webDir, 'components', 'Navbar.tsx');
|
|
52
|
+
const content = fs.readFileSync(navbarPath, 'utf-8');
|
|
53
|
+
expect(content).toContain('prefers-color-scheme');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('Responsive Design Tests', () => {
|
|
58
|
+
test('should optimize for vertical monitors', () => {
|
|
59
|
+
const heroPath = path.join(webDir, 'components', 'Hero.tsx');
|
|
60
|
+
const content = fs.readFileSync(heroPath, 'utf-8');
|
|
61
|
+
expect(content).toContain('xl:grid-cols-2');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('should have responsive navbar', () => {
|
|
65
|
+
const navbarPath = path.join(webDir, 'components', 'Navbar.tsx');
|
|
66
|
+
const content = fs.readFileSync(navbarPath, 'utf-8');
|
|
67
|
+
expect(content).toContain('md:hidden');
|
|
68
|
+
});
|
|
69
|
+
});
|
package/scripts/build-docs.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { marked } from 'marked';
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = path.dirname(__filename);
|
|
8
|
-
const projectRoot = path.join(__dirname, '..');
|
|
9
|
-
|
|
10
|
-
const docs = [
|
|
11
|
-
{
|
|
12
|
-
key: 'README.md',
|
|
13
|
-
source: path.join(projectRoot, 'README.md'),
|
|
14
|
-
output: 'README.html',
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
key: 'web-dashboard.md',
|
|
18
|
-
source: path.join(projectRoot, 'docs', 'web-dashboard.md'),
|
|
19
|
-
output: 'web-dashboard.html',
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
key: 'GETTING-STARTED.md',
|
|
23
|
-
source: path.join(projectRoot, 'docs', 'antigravity', 'GETTING-STARTED.md'),
|
|
24
|
-
output: 'GETTING-STARTED.html',
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
key: 'EXAMPLES.md',
|
|
28
|
-
source: path.join(projectRoot, 'docs', 'antigravity', 'EXAMPLES.md'),
|
|
29
|
-
output: 'EXAMPLES.html',
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
key: 'WORKFLOW-TEMPLATES.md',
|
|
33
|
-
source: path.join(projectRoot, 'docs', 'antigravity', 'WORKFLOW-TEMPLATES.md'),
|
|
34
|
-
output: 'WORKFLOW-TEMPLATES.html',
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
key: 'TROUBLESHOOTING.md',
|
|
38
|
-
source: path.join(projectRoot, 'docs', 'antigravity', 'TROUBLESHOOTING.md'),
|
|
39
|
-
output: 'TROUBLESHOOTING.html',
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
const distDocDir = path.join(projectRoot, 'dist', 'docs');
|
|
44
|
-
|
|
45
|
-
async function buildDocs() {
|
|
46
|
-
await mkdir(distDocDir, { recursive: true });
|
|
47
|
-
|
|
48
|
-
for (const doc of docs) {
|
|
49
|
-
const markdown = await readFile(doc.source, 'utf-8');
|
|
50
|
-
const html = marked.parse(markdown);
|
|
51
|
-
await writeFile(path.join(distDocDir, doc.output), html, 'utf-8');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
buildDocs().catch((error) => {
|
|
56
|
-
console.error('Failed to build documentation assets:', error);
|
|
57
|
-
process.exitCode = 1;
|
|
58
|
-
});
|