mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 00:21:21 +00:00
Developer Experience Improvements (multi-worktree simultaneous development) (#209)
* docs: add git hash display and configurable dev server port ## CHANGES - Add `VITE_PORT` env variable to configure dev server port - Display git commit hash in About modal next to version - Add `__GIT_HASH__` build-time constant to both Vite configs - Document running multiple Maestro instances with git worktrees - Update CONTRIBUTING.md with parallel development instructions * feat: add configurable ports for dev servers - Allow VITE_PORT to configure main dev server port - Update main window to load from configurable port - Enable VITE_WEB_PORT for web interface dev server - Add note in CONTRIBUTING.md about port configuration - Log port usage in development mode * docs: update CONTRIBUTING.md section and fix React DevTools script initialization ## CHANGES - Rename "Linting" section to "Linting & Pre-commit Hooks" in table of contents - Move script variable declaration outside conditional block - Fix React DevTools script initialization order in index.html * chore: update `.vscode/settings.json` with new markdownlint config * fix: disable biome linting. Project uses ESLint * chore: Update baseline-browser-mapping (>2 months old, warning message on "npm run build:web") * chore: add .vscode/ to gitignore * chore: fix gitignore to ignore .cscode/* files properly * fix * chore: stop tracking .vscode/ files, respect gitignore Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -33,11 +33,6 @@ scratch/
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
# .vscode/ is tracked for shared settings (settings.json, extensions.json)
|
||||
# But ignore personal/local files
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/extensions.json
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
@@ -55,5 +50,6 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
#VS Code
|
||||
.vscode/
|
||||
.VSCodeCounter
|
||||
.qodo
|
||||
.qodo
|
||||
|
||||
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig"
|
||||
]
|
||||
}
|
||||
47
.vscode/settings.json
vendored
47
.vscode/settings.json
vendored
@@ -1,47 +0,0 @@
|
||||
{
|
||||
// Format on save with Prettier
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
|
||||
// Use tabs (matches .prettierrc and .editorconfig)
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": false,
|
||||
"editor.detectIndentation": false,
|
||||
|
||||
// ESLint configuration
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
|
||||
|
||||
// Don't let ESLint format - let Prettier handle it
|
||||
"eslint.format.enable": false,
|
||||
|
||||
// File-specific formatters
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
|
||||
// Recommended extensions
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
|
||||
// Files to exclude from search/watch
|
||||
"files.exclude": {
|
||||
"dist": true,
|
||||
"release": true,
|
||||
"node_modules": true
|
||||
},
|
||||
|
||||
// TypeScript settings
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
@@ -22,7 +22,7 @@ See [Performance Guidelines](#performance-guidelines) for specific practices.
|
||||
- [Project Structure](#project-structure)
|
||||
- [Development Scripts](#development-scripts)
|
||||
- [Testing](#testing)
|
||||
- [Linting](#linting)
|
||||
- [Linting & Pre-commit Hooks](#linting--pre-commit-hooks)
|
||||
- [Common Development Tasks](#common-development-tasks)
|
||||
- [Adding a New AI Agent](#adding-a-new-ai-agent)
|
||||
- [Code Style](#code-style)
|
||||
@@ -150,6 +150,25 @@ You can also specify a custom demo directory via environment variable:
|
||||
MAESTRO_DEMO_DIR=~/Desktop/my-demo npm run dev
|
||||
```
|
||||
|
||||
### Running Multiple Instances (Git Worktrees)
|
||||
|
||||
When working with multiple git worktrees, you can run Maestro instances in parallel by specifying different ports using the `VITE_PORT` environment variable:
|
||||
|
||||
```bash
|
||||
# In the main worktree (uses default port 5173)
|
||||
npm run dev
|
||||
|
||||
# In worktree 2 (in another directory and terminal)
|
||||
VITE_PORT=5174 npm run dev
|
||||
|
||||
# In worktree 3
|
||||
VITE_PORT=5175 npm run dev
|
||||
```
|
||||
|
||||
This allows you to develop and test different branches simultaneously without port conflicts.
|
||||
|
||||
**Note:** The web interface dev server (`npm run dev:web`) uses a separate port (default 5174) and can be configured with `VITE_WEB_PORT` if needed.
|
||||
|
||||
## Testing
|
||||
|
||||
Run the test suite with Jest:
|
||||
|
||||
@@ -121,3 +121,11 @@ The confirmation dialog shows the full path to the worktree directory so you kno
|
||||
- **Use a dedicated worktree folder** — Keep all worktrees in one place outside the main repo
|
||||
- **Clean up when done** — Remove worktree agents after merging PRs to avoid clutter
|
||||
- **Watch for Changes** — Enable file watching to keep the file tree in sync with worktree activity
|
||||
- **Run multiple dev instances** — Use `VITE_PORT` environment variable to run Maestro in multiple worktrees simultaneously:
|
||||
```bash
|
||||
# In main worktree
|
||||
npm run dev
|
||||
|
||||
# In worktree 2 (different terminal/directory)
|
||||
VITE_PORT=5174 npm run dev
|
||||
```
|
||||
|
||||
@@ -1111,5 +1111,4 @@ describe('AboutModal', () => {
|
||||
expect(screen.getByText('$12,345,678.90')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -42,7 +42,6 @@ const mockData: StatsAggregation = {
|
||||
avgSessionDuration: 288000,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
|
||||
};
|
||||
|
||||
// Empty data for edge case testing
|
||||
@@ -61,7 +60,6 @@ const emptyData: StatsAggregation = {
|
||||
avgSessionDuration: 0,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
|
||||
};
|
||||
|
||||
// Data with large numbers
|
||||
@@ -83,7 +81,6 @@ const largeNumbersData: StatsAggregation = {
|
||||
avgSessionDuration: 7200000,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
|
||||
};
|
||||
|
||||
// Single agent data
|
||||
@@ -104,7 +101,6 @@ const singleAgentData: StatsAggregation = {
|
||||
avgSessionDuration: 360000,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
|
||||
};
|
||||
|
||||
// Only auto queries
|
||||
@@ -125,7 +121,6 @@ const onlyAutoData: StatsAggregation = {
|
||||
avgSessionDuration: 360000,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
|
||||
};
|
||||
|
||||
describe('SummaryCards', () => {
|
||||
|
||||
@@ -70,7 +70,6 @@ const mockStatsData: StatsAggregation = {
|
||||
avgSessionDuration: 288000,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
|
||||
};
|
||||
|
||||
describe('Chart Accessibility - AgentComparisonChart', () => {
|
||||
@@ -411,10 +410,9 @@ describe('Chart Accessibility - General ARIA Patterns', () => {
|
||||
sessionsByDay: [],
|
||||
avgSessionDuration: 0,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
bySessionByDay: {},
|
||||
};
|
||||
|
||||
|
||||
render(<AgentComparisonChart data={emptyData} theme={mockTheme} />);
|
||||
expect(screen.getByText(/no agent data available/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -212,7 +212,6 @@ const createSampleData = () => ({
|
||||
avgSessionDuration: 144000,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
|
||||
});
|
||||
|
||||
describe('UsageDashboard Responsive Layout', () => {
|
||||
|
||||
@@ -159,10 +159,9 @@ beforeEach(() => {
|
||||
],
|
||||
avgSessionDuration: 180000,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
bySessionByDay: {},
|
||||
});
|
||||
mockStats.getDatabaseSize.mockResolvedValue(1024 * 1024); // 1 MB
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -282,7 +281,7 @@ describe('Usage Dashboard State Transition Animations', () => {
|
||||
sessionsByDay: [],
|
||||
avgSessionDuration: 240000,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
bySessionByDay: {},
|
||||
};
|
||||
|
||||
it('applies dashboard-card-enter class to metric cards', () => {
|
||||
@@ -589,5 +588,4 @@ describe('Usage Dashboard State Transition Animations', () => {
|
||||
expect(totalMaxDuration).toBeLessThan(1000);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -147,7 +147,6 @@ const createSampleData = () => ({
|
||||
avgSessionDuration: 144000,
|
||||
byAgentByDay: {},
|
||||
bySessionByDay: {},
|
||||
|
||||
});
|
||||
|
||||
describe('UsageDashboardModal', () => {
|
||||
|
||||
@@ -642,9 +642,10 @@ function createWindow() {
|
||||
logger.warn(`Failed to load electron-devtools-installer: ${err.message}`, 'Window')
|
||||
);
|
||||
|
||||
mainWindow.loadURL('http://localhost:5173');
|
||||
const vitePort = process.env.VITE_PORT || '5173';
|
||||
mainWindow.loadURL(`http://localhost:${vitePort}`);
|
||||
// DevTools can be opened via Command-K menu instead of automatically on startup
|
||||
logger.info('Loading development server', 'Window');
|
||||
logger.info(`Loading development server on port ${vitePort}`, 'Window');
|
||||
} else {
|
||||
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
|
||||
logger.info('Loading production build', 'Window');
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
<!-- React DevTools: connects to standalone react-devtools app (npm install -g react-devtools) -->
|
||||
<!-- Only attempts connection in dev mode (Vite serves on localhost:5173) -->
|
||||
<script>
|
||||
if (window.location.hostname === 'localhost') {
|
||||
var script = document.createElement('script');
|
||||
var script = document.createElement('script');
|
||||
if (window.location.hostname === 'localhost') {
|
||||
script.src = 'http://localhost:8097';
|
||||
script.async = false;
|
||||
document.head.appendChild(script);
|
||||
|
||||
@@ -11,48 +11,49 @@ const appVersion = process.env.VITE_APP_VERSION || packageJson.version;
|
||||
|
||||
// Get the first 8 chars of git commit hash for dev mode
|
||||
function getCommitHash(): string {
|
||||
try {
|
||||
// Note: execSync is safe here - no user input, static git command
|
||||
return execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim().slice(0, 8);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
// Note: execSync is safe here - no user input, static git command
|
||||
return execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim().slice(0, 8);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const disableHmr = process.env.DISABLE_HMR === '1';
|
||||
|
||||
export default defineConfig(({ mode }) => ({
|
||||
plugins: [react({ fastRefresh: !disableHmr })],
|
||||
root: path.join(__dirname, 'src/renderer'),
|
||||
base: './',
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify(appVersion),
|
||||
// Show commit hash only in development mode
|
||||
__COMMIT_HASH__: JSON.stringify(mode === 'development' ? getCommitHash() : ''),
|
||||
// Explicitly define NODE_ENV for React and related packages
|
||||
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
// In development, use wdyr.dev.ts which loads why-did-you-render
|
||||
// In production, use wdyr.ts which is empty (prevents bundling the library)
|
||||
'./wdyr': mode === 'development'
|
||||
? path.join(__dirname, 'src/renderer/wdyr.dev.ts')
|
||||
: path.join(__dirname, 'src/renderer/wdyr.ts'),
|
||||
},
|
||||
},
|
||||
esbuild: {
|
||||
// Strip console.* and debugger in production builds
|
||||
drop: mode === 'production' ? ['console', 'debugger'] : [],
|
||||
},
|
||||
build: {
|
||||
outDir: path.join(__dirname, 'dist/renderer'),
|
||||
emptyOutDir: true,
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
hmr: !disableHmr,
|
||||
// Disable file watching entirely when HMR is disabled to prevent any reloads
|
||||
watch: disableHmr ? null : undefined,
|
||||
},
|
||||
plugins: [react({ fastRefresh: !disableHmr })],
|
||||
root: path.join(__dirname, 'src/renderer'),
|
||||
base: './',
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify(appVersion),
|
||||
// Show commit hash only in development mode
|
||||
__COMMIT_HASH__: JSON.stringify(mode === 'development' ? getCommitHash() : ''),
|
||||
// Explicitly define NODE_ENV for React and related packages
|
||||
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
// In development, use wdyr.dev.ts which loads why-did-you-render
|
||||
// In production, use wdyr.ts which is empty (prevents bundling the library)
|
||||
'./wdyr':
|
||||
mode === 'development'
|
||||
? path.join(__dirname, 'src/renderer/wdyr.dev.ts')
|
||||
: path.join(__dirname, 'src/renderer/wdyr.ts'),
|
||||
},
|
||||
},
|
||||
esbuild: {
|
||||
// Strip console.* and debugger in production builds
|
||||
drop: mode === 'production' ? ['console', 'debugger'] : [],
|
||||
},
|
||||
build: {
|
||||
outDir: path.join(__dirname, 'dist/renderer'),
|
||||
emptyOutDir: true,
|
||||
},
|
||||
server: {
|
||||
port: process.env.VITE_PORT ? parseInt(process.env.VITE_PORT) : 5173,
|
||||
hmr: !disableHmr,
|
||||
// Disable file watching entirely when HMR is disabled to prevent any reloads
|
||||
watch: disableHmr ? null : undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -11,6 +11,7 @@ import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
import { execFileSync } from 'child_process';
|
||||
|
||||
// Read version from package.json
|
||||
const packageJson = JSON.parse(
|
||||
@@ -18,6 +19,19 @@ const packageJson = JSON.parse(
|
||||
);
|
||||
const appVersion = process.env.VITE_APP_VERSION || packageJson.version;
|
||||
|
||||
// Get git hash
|
||||
function getGitHash() {
|
||||
try {
|
||||
return execFileSync('git', ['rev-parse', '--short=8', 'HEAD'], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
}).trim();
|
||||
} catch {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
const gitHash = getGitHash();
|
||||
|
||||
export default defineConfig(({ mode }) => ({
|
||||
plugins: [react()],
|
||||
|
||||
@@ -33,6 +47,7 @@ export default defineConfig(({ mode }) => ({
|
||||
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify(appVersion),
|
||||
__GIT_HASH__: JSON.stringify(gitHash),
|
||||
},
|
||||
|
||||
esbuild: {
|
||||
@@ -128,7 +143,7 @@ export default defineConfig(({ mode }) => ({
|
||||
|
||||
// Development server (for testing web interface standalone)
|
||||
server: {
|
||||
port: 5174, // Different from renderer dev server (5173)
|
||||
port: process.env.VITE_WEB_PORT ? parseInt(process.env.VITE_WEB_PORT) : 5174, // Different from renderer dev server (5173)
|
||||
strictPort: true,
|
||||
// Proxy API calls to the running Maestro app during development
|
||||
proxy: {
|
||||
|
||||
Reference in New Issue
Block a user