13 KiB
GitHub Copilot Agent Instructions for MQTT Explorer
Overview
MQTT Explorer is an Electron-based desktop application for exploring MQTT brokers. It provides a comprehensive UI for connecting to MQTT brokers, browsing topics, and analyzing message flows.
Git Commit and PR Guidelines
Commit Messages
ALWAYS use semantic commit message format:
<type>: <description>
[optional body]
[optional footer]
Required types:
feat:- New featurefix:- Bug fixdocs:- Documentation only changesstyle:- Code style changes (formatting, semicolons, etc.)refactor:- Code refactoring without changing functionalityperf:- Performance improvementstest:- Adding or updating testsbuild:- Changes to build system or dependenciesci:- Changes to CI configuration files and scriptschore:- Other changes that don't modify src or test files
Examples:
feat: add support for MQTT 5.0 protocol
fix: resolve connection timeout issue with SSL/TLS
docs: update installation instructions for Node.js 24
test: add unit tests for message parsing
Pull Request Titles
ALWAYS use the same semantic format for PR titles:
<type>: <concise description>
Examples:
feat: add WebSocket support for browser modefix: prevent memory leak in topic tree renderingdocs: improve agent instructions clarity
Working Principles
Be Thorough and Complete
- Always validate your changes - Run tests, linters, and builds to ensure nothing breaks
- Deliver complete solutions - Don't leave partial implementations or half-fixed bugs
- Test edge cases - Consider and handle error conditions, edge cases, and failure scenarios
- Verify the fix works - Manually test the functionality you changed, take screenshots for UI changes
- Follow through - If a test fails, debug and fix it; don't leave broken tests behind
Quality Standards
- Completeness: Ensure all aspects of the issue are addressed
- Correctness: Verify changes work as expected through testing
- Consistency: Follow existing code patterns and conventions
- Clarity: Write clear code with meaningful variable/function names
- Minimal changes: Make surgical, focused changes that solve the problem without unnecessary refactoring
Technology Stack
- Frontend: React 18.x with Material-UI v5/v6
- Backend: Node.js with TypeScript
- Desktop Framework: Electron 39.x
- MQTT Client: mqttjs v5.x
- State Management: Redux with redux-thunk
- Build Tools: webpack, TypeScript compiler
- Testing: Mocha + Chai for unit tests, Playwright for browser mode tests
- Node.js: Version 24 or higher required
Project Setup
Building and Running
# Install dependencies
yarn install
# Build the project
yarn build
# Set password for browser testing
export MQTT_EXPLORER_USERNAME=admin
export MQTT_EXPLORER_PASSWORD=secretpassword
# Start the application
yarn start
# Start in development mode
yarn dev
Running with MCP Introspection (for testing)
# Build first
yarn build
# Start with MCP introspection enabled
electron . --enable-mcp-introspection
# Or with custom port
electron . --enable-mcp-introspection --remote-debugging-port=9223
See mcp.json in the repository root for MCP configuration.
Writing Tests
Requirements for All Tests
- Tests MUST be deterministic - They should produce the same results every time they run
- Tests MUST be independent - Each test should be able to run in isolation without depending on other tests
- Include screenshots - Visual verification is required for UI changes
- Handle asynchronous operations properly - This is an MQTT message queue tool
Best Practices for UI Tests
1. Use Given-When-Then Pattern
Structure tests with clear Given-When-Then comments to make them readable:
it('Given a JSON message sent to topic foo/bar/baz, the tree should display nested topics', async function () {
// Given: Mock MQTT publishes JSON to foo/bar/baz
// When: We wait for the topic to appear in the tree
// Then: Topic hierarchy should be visible (foo -> bar -> baz)
})
2. Wait for Elements, Don't Use Fixed Delays
Prefer waitFor over sleep whenever possible:
// ✓ Good: Wait for specific element
const topic = await page.locator('span[data-test-topic="kitchen"]')
await topic.waitFor({ state: 'visible', timeout: 5000 })
// ✗ Bad: Fixed delay without verification
await sleep(5000)
3. Use Meaningful Assertions
Every test should have explicit assertions that verify the expected state:
// ✓ Good: Explicit assertion with meaningful message
const treeNodes = await page.locator('[class*="TreeNode"]')
const count = await treeNodes.count()
expect(count).to.be.greaterThan(0, 'Topic tree should contain nodes')
// ✗ Bad: No assertion, only screenshot
await page.screenshot({ path: 'test.png' })
4. Test Data-Driven Scenarios
Write tests that describe the data flow:
it('Given messages sent to livingroom/lamp/state and livingroom/lamp/brightness, both should appear under livingroom/lamp', async function () {
// Test implementation verifies the specific data flow
})
5. Use Data Test Attributes
Leverage data-test-* attributes for reliable selectors:
// ✓ Good: Use data-test attributes
const topic = await page.locator('span[data-test-topic="kitchen"]')
// ⚠ Acceptable: Use role/text when data attributes aren't available
const button = await page.locator('//button/span[contains(text(),"Connect")]')
// ✗ Bad: Rely on CSS classes that may change
const topic = await page.locator('.MuiTreeItem-label')
6. Verify Multiple Aspects
Test should verify both state and UI:
// Verify the action completed
const isVisible = await disconnectButton.isVisible()
expect(isVisible).to.be.true
// Capture screenshot for visual verification
await page.screenshot({ path: 'test-screenshot-connection.png' })
7. Handle MQTT Asynchronous Nature
Account for message propagation time:
// Publish message
await mockClient.publish('topic/name', 'value')
// Wait for UI to update
await page.locator(`text="value"`).waitFor({ timeout: 5000 })
// Verify state
const value = await page.textContent('.message-value')
expect(value).toBe('value')
Handling MQTT Asynchronous Operations
MQTT is inherently asynchronous. When writing tests:
- Wait for message propagation: Use proper wait strategies (e.g.,
await page.waitForSelector(),await sleep()) - Don't assume immediate updates: Messages take time to send, receive, and update the UI
- Use event-based waiting: Wait for specific UI elements or state changes rather than fixed timeouts when possible
- Account for network latency: MQTT broker communication involves network round trips
Example Test Pattern
// 1. Perform action (e.g., publish message)
await publishMessage(topic, payload)
// 2. Wait for UI to update (not just arbitrary sleep)
await page.waitForSelector(`text="${expectedValue}"`, { timeout: 5000 })
// 3. Verify state
const value = await page.textContent('.message-value')
expect(value).toBe(expectedValue)
// 4. Take screenshot for verification
await page.screenshot({ path: 'test-result.png' })
Running Tests
# Run all tests
yarn test
# Run specific test suites
yarn test:app
yarn test:backend
yarn test:mcp
# Run linters
yarn lint
yarn lint:fix
Running UI Tests (yarn test:ui)
The UI tests require specific setup in the test environment:
Prerequisites:
-
Xvfb (X Virtual Framebuffer) - Required for headless Electron testing
# Start Xvfb on display :99 Xvfb :99 -screen 0 1024x720x24 -ac & export DISPLAY=:99 -
Mosquitto MQTT Broker - Required for MQTT message testing
# Install mosquitto sudo apt-get install -y mosquitto mosquitto-clients # Start mosquitto service sudo systemctl start mosquitto # Verify it's running on port 1883 sudo systemctl status mosquitto -
@types/node - Required for TypeScript compilation
yarn add -D @types/node
Running UI Tests:
# Build the application first
yarn build
# Run UI tests with proper display
DISPLAY=:99 yarn test:ui
Common Issues:
- "Timeout exceeded" in before hook: Mosquitto is not running or not accessible on port 1883
- "Cannot find type definition file for 'node'": Run
yarn add -D @types/node - Electron fails to launch: Xvfb is not running or DISPLAY variable not set
- Tests hang: Check if old Electron/mosquitto processes are still running and kill them
Environment Cleanup:
# Kill old Electron processes
ps aux | grep electron | grep -v grep | awk '{print $2}' | xargs kill -9 2>/dev/null
# Kill old mosquitto processes (if running custom instance)
ps aux | grep mosquitto | grep -v grep | awk '{print $2}' | xargs kill -9 2>/dev/null
MCP Introspection Testing
The project supports MCP (Model Context Protocol) for automated testing:
- Run tests:
yarn test:mcp - Configuration:
mcp.jsonin repository root - Tests launch the app with remote debugging on port 9222
Project Structure
app/- Frontend React applicationbackend/- Backend models, tests, and connection managementsrc/- Electron main process and bindingssrc/spec/- Test specifications including MCP introspection tests
Code Style and Formatting
Linting
The project uses TSLint with Airbnb config and Prettier for code formatting:
# Run all linters
yarn lint
# Run linters individually
yarn lint:prettier # Check Prettier formatting
yarn lint:tslint # Check TSLint rules
yarn lint:spellcheck # Check spelling in code
# Auto-fix issues
yarn lint:fix # Fix TSLint and Prettier issues
yarn lint:tslint:fix # Fix TSLint issues only
yarn lint:prettier:fix # Fix Prettier issues only
Code Style Rules
- Semicolons: Never use semicolons (enforced by TSLint and Prettier)
- Quotes: Single quotes for strings
- Indentation: 2 spaces
- Line length: Maximum 120 characters (Prettier) / 200 characters (TSLint)
- Arrow functions: No parentheses for single parameters (
x => x + 1) - Trailing commas: Required for multiline objects and arrays (ES5 compatible)
TypeScript Guidelines
- Enable strict null checks and no implicit any
- Use TypeScript interfaces for data structures
- Prefer
constoverlet, avoidvar - Use type inference when possible, explicit types when clarity is needed
Dependency Management
Adding Dependencies
# Add to root project
yarn add <package-name>
# Add to app (frontend)
cd app && yarn add <package-name>
# Add to backend
cd backend && yarn add <package-name>
# Add dev dependencies
yarn add -D <package-name>
Important Dependency Notes
- Main dependencies are in the root
package.json - Frontend React app has its own dependencies in
app/package.json - Backend models and logic have dependencies in
backend/package.json - Always use
--frozen-lockfilein CI to ensure reproducible builds - Run
yarn installafter pulling changes that modifyyarn.lock
Debugging
Development Mode
# Start with hot reload for frontend
yarn dev
# This runs two processes in parallel:
# 1. webpack-dev-server for the React app (port varies)
# 2. Electron in development mode with the --development flag
Debugging TypeScript
- Source maps are enabled in
tsconfig.json - Use
ts-nodefor running TypeScript files directly - Backend tests can be debugged with:
cd backend && yarn test-inspect
Common Issues
- Build fails: Clear
dist/andapp/build/directories, then rebuild - Electron won't start: Ensure
yarn buildcompleted successfully - Tests fail: Check if MQTT broker (mosquitto) is running for integration tests
- UI not updating: In dev mode, ensure webpack-dev-server is running
Deployment and Packaging
Creating Releases
# Prepare release (updates version, changelog)
yarn prepare-release
# Package the application for distribution
yarn package
# Package with Docker (for consistent builds)
yarn package-with-docker
Release Workflow
- Semantic commits required: All commits must use semantic format (
feat:,fix:, etc.) - Beta releases: Create PR to
betabranch with semantic commits - Production releases: Create PR to
releasebranch with semantic commits - Semantic-release automatically handles versioning and changelog based on commit messages
- Builds are created for Windows, macOS, and Linux
Build Artifacts
- Output directory:
build/ - Supported formats: DMG (macOS), EXE/NSIS (Windows), AppImage/Snap (Linux), AppX (Windows Store)
- Code signing is configured via
res/directory certificates and provisioning profiles
Important Notes
- Always build first: Run
yarn buildbefore starting the application - Node.js requirement: Version 24 or higher
- Linting: All code changes must pass
yarn lint - MQTT library: Communication handled via mqttjs
- Workspace structure: Separate package.json files for root, app, and backend
- Provide screenshots in each PR to show that the application still works, if the PR is about a feature the screenshot should depict the feature if possible.
- Resolve all errors during build, especially typescript and webpack builds.