| name | frontend |
| description | Frontend development context for the Megahub project. Use when working on the web-based IDE, Vite build system, Blockly visual programming, Web Bluetooth API integration, JavaScript frontend code, CSS/styles, UI components, or anything in the /frontend/ directory. |
Frontend Development — Megahub IDE
Tech Stack
- Vite — build tool and dev server
- Blockly 12.3.1 — visual programming editor; custom blocks in
/frontend/src/components/blockly/ — see blockly skill for block authoring, Lua generators, and BLOCKS.md generation
- Vanilla JS — no framework (intentionally lightweight for ESP32 flash size)
- Web Bluetooth API — browser BLE, Chrome/Edge/Opera only
- Prism.js — Lua syntax highlighting in the preview panel
- Web Components — all sidebar panels are custom elements with Shadow DOM
Build Modes
| Mode | Description |
|---|
dev | Hot reload, localStorage for persistence, no BLE |
bt | Production BLE mode, deployed to GitHub Pages |
web | WiFi/HTTP mode, served from firmware WebServer |
Mode is available at runtime as import.meta.env.VITE_MODE.
File Structure
frontend/
index.html Main entry point (HTML skeleton)
src/
index.js App controller, event wiring, all Application.* methods
bleclient.js BLE protocol client (fragmentation, streaming, events)
theme.css CSS design tokens (all --vscode-* variables)
styles.css Global styles, layout grid, all UI component CSS
components/
blockly/ Blockly editor + custom block definitions
files/ Project manager dialog (list/create/delete/autostart)
logger/ Terminal-style log output (max 50 entries)
luapreview/ Syntax-highlighted Lua code preview
portstatus/ Real-time LEGO port status cards
ui/ show_value block output display
btdevicelist/ Bluetooth Classic device list + discovery
sidebar-toggle/ Mobile sidebar toggle button
VS Code Dark Theme System
All design tokens are CSS custom properties defined in theme.css. Always use these — never hardcode colors or sizes.
Key Color Tokens
--vscode-bg-primary: #1e1e1e
--vscode-bg-secondary: #252526
--vscode-bg-elevated: #2d2d30
--vscode-bg-input: #3c3c3c
--vscode-text-primary: #cccccc
--vscode-text-secondary: #858585
--vscode-text-tertiary: #6a6a6a
--vscode-text-bright: #ffffff
--vscode-accent-blue: #007acc
--vscode-accent-blue-dark: #0078d4
--vscode-accent-blue-hover: #1177bb
--vscode-accent-teal: #4ec9b0
--vscode-accent-yellow: #dcdcaa
--color-success: #4ec9b0
--color-error: #f48771
--color-error-bg: #5a1d1d
--color-warning: #dcdcaa
--vscode-border-light: #3c3c3c
--vscode-border-medium: #3e3e42
Key Spacing / Sizing Tokens
--spacing-xs: 0.25rem --spacing-sm: 0.5rem
--spacing-md: 0.75rem --spacing-lg: 1rem
--spacing-xl: 1.25rem --spacing-2xl: 1.5rem
--transition-fast: 0.15s ease
--transition-medium: 0.3s ease
--header-height: 3rem
--z-modal: 1100 --z-tooltip: 2000
Application Mode System
Three modes, switched via Application.setMode(mode):
| Mode | View shown |
|---|
btconnect | Welcome screen + connect button |
management | Project list (files component) |
editor | Blockly editor + sidebar controls |
Visibility Mechanism
dynamicvisibility class (all modes):
⚠ setMode() always sets display: block — do NOT use dynamicvisibility for elements that need display: flex or display: grid.
body.dataset.mode (CSS-based, for flex/grid elements):
Layout Grid
┌──────────────────────────────────────┐ ← header (3rem)
│ [←] Megahub IDE / project [toggle] │
├──────────────────┬───────────────────┤ ← 1fr
│ │ sidebar buttons │
│ Blockly / │ ─────────────────│
│ Files / │ accordion panels │
│ Welcome │ lua / ports / │
│ │ ui / btdevices │
├──────────────────┴───────────────────┤ ← footer (8–12rem)
│ Logger (terminal output) │
├──────────────────────────────────────┤ ← statusbar (20px)
│ ● Connected to Megahub IDE │
└──────────────────────────────────────┘
Desktop (≥1512px): 75/25 content/sidebar split. Mobile: sidebar is a slide-in overlay.
Existing UI Patterns (use these, don't reinvent)
Notifications (Toast)
showNotification('success' | 'error' | 'warning' | 'info', title, message, durationMs)
Confirmation Dialog
const confirmed = await showConfirmDialog(title, message, {
confirmText: 'Delete',
cancelText: 'Cancel',
destructive: true
});
Top Progress Bar
Progress.show()
Progress.hide()
BLE Connection Modal
ConnectionModal.show()
ConnectionModal.setStep(stepId)
ConnectionModal.setAllDone()
ConnectionModal.setError(stepId, message)
ConnectionModal.hide()
VS Code Status Bar
StatusBar.setConnecting()
StatusBar.setConnected(deviceName)
StatusBar.setDisconnected()
StatusBar.setMessage(text)
StatusBar.clearMessage()
Sidebar Icon Buttons
.sidebar-icon-btn
.sidebar-icon-btn-primary
.sidebar-icon-btn-danger
.sidebar-icon-btn-save
.sidebar-icon-btn-toggle
.sidebar-icon-btn.btn-loading
BLE Client Architecture (bleclient.js)
Connection Flow
connect(onProgress?)
→ navigator.bluetooth.requestDevice() [requires user gesture]
→ _setupGattConnection(onProgress?)
→ gatt.connect()
→ getPrimaryService()
→ getCharacteristic() × 4
→ startNotifications() × 3
→ sleep(200) ← CRITICAL: BLE stack propagation delay
→ waitForMTU(2000)
→ testControlChannel()
→ emits onProgress callbacks at each step
Message Protocol
- 5-byte fragment header: type, messageId, fragmentNum(2), flags
- MTU: default 23 bytes, negotiated up to 517 bytes
- Fragmentation: automatic for all requests/responses
- Streaming protocol: for large file uploads (chunks + ACK window)
Event System
bleClient.addEventListener(APP_EVENT_TYPE_*, callback)
bleClient.removeAllEventListeners()
File Upload
await bleClient.uploadFileStreaming(projectId, filename, content, onProgress?)
Animation Principles
All animations must be purposeful — each one communicates meaning:
| Use case | Animation | Timing |
|---|
| State transitions | CSS transition | var(--transition-fast) = 0.15s ease |
| Indeterminate loading | Shimmer sweep (progress bar) | 1.4s linear infinite |
| Spinner (waiting) | Rotate 360° | 0.8s linear infinite |
| Step completion | Scale 0→1 + opacity | 0.15s ease-out (one-shot) |
| Status dot state change | Color transition | 0.3s ease |
| Connecting pulse | Opacity 1→0.3→1 | 1s ease-in-out infinite |
Always wrap looping animations in:
@media (prefers-reduced-motion: reduce) {
}
Component Pattern (Web Components)
All sidebar panels are custom elements with Shadow DOM:
class MyComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' });
const sheet = new CSSStyleSheet();
sheet.replaceSync(styleSheet);
shadow.adoptedStyleSheets = [sheet];
shadow.innerHTML = template;
}
}
customElements.define('custom-my-component', MyComponent);
Accordion behavior: dispatch accordion-expand / accordion-collapse custom events. Parent (initSidebarAccordion() in index.js) collapses all others on expand.
Important Constraints
- Bundle size: Keep JS small — it's embedded in 4MB ESP32 flash
- No framework: No React/Vue/Angular — intentional
- Shadow DOM: Component styles are isolated;
theme.css variables are inherited through the shadow boundary
- Web Bluetooth: User gesture required for
requestDevice() — never call without a click handler
- Supported browsers: Chrome, Edge, Opera only — Firefox/Safari don't support Web Bluetooth