# Metro Fabric Grid — Developer Guide

> For developers joining the project. This covers everything you need to get productive with the Grid dashboard codebase.

---

## Quick Start

**Live site:** https://grid-czb.pages.dev (password: `MetroGrid2024`)  
**GitHub:** https://github.com/Metro-Fabric/grid

```bash
# 1. Clone the repository
git clone https://github.com/Metro-Fabric/grid.git
cd grid

# 2. Start the dev server (no-cache headers for hot reload)
python3 -c "
from http.server import HTTPServer, SimpleHTTPRequestHandler
class H(SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Cache-Control','no-cache,no-store,must-revalidate')
        super().end_headers()
HTTPServer(('',8765),H).serve_forever()
"

# 3. Open in browser
open http://localhost:8765/grid-dashboard.html
open http://localhost:8765/grid-auth.html
```

**Demo login:** `demo@metrofabric.net` / `Demo1234!`

---

## Project Structure

```
Grid/
├── grid-dashboard.html   # Main app — single HTML file (~13,498 lines)
│   ├── <style>           # Lines 8–1546 — all CSS
│   ├── <body>            # Lines 1548–3437 — all HTML
│   └── <script>          # Lines 3438–9707 — all JavaScript
├── grid-auth.html        # Auth pages (login, signup, forgot password + SSO)
├── ui-elements.html      # Web UI Kit — components, brand guidelines, design tokens
├── demo.html             # Demo preview — links, brand, stats, pricing, all 18 sections
├── CLAUDE.md             # Project journal — architecture, status, roadmap
├── DEV-GUIDE.md          # This file
├── .cursorrules          # Cursor IDE rules — auto-loaded for AI assistance
└── backup/               # Previous versions
```

**Key principle:** Everything is in a single HTML file. No build step, no framework, no dependencies except D3.js (loaded from CDN for the map). This is intentional — it's a design prototype / demo, not a production app.

---

## Architecture Overview

### App Shell
```
.app (flex row, 100vh)
├── .sidebar (224px)
│   ├── .sidebar-logo
│   ├── .sidebar-nav (scrollable, nav groups + items)
│   └── .sidebar-footer (user menu + locale popup)
└── .main (flex column)
    ├── header (52px — breadcrumb, search, AI btn, popups, CTA)
    ├── #pinned-tabs (conditional — unsaved section tabs)
    └── .content (scrollable)
        └── .view divs (one per section, display:none unless .active)
```

### Navigation System
```javascript
nav(section, el)           // Switch to a section
navSub(section, sub, el)   // Switch to a sub-section (triggers section-specific renderer)
syncSidebarHighlight(section, sub) // Syncs sidebar nav highlighting + breadcrumb — called by ALL tab switchers
toggleNavGroup(section, el) // Expand/collapse sidebar nav group
currentSection             // Currently active section
currentSubSection          // Currently active sub-section (or null)
```

When you call `nav('billing', el)`, it:
1. Removes `.active` from all nav items and views
2. Adds `.active` to the target view (`#view-billing`)
3. Updates the header breadcrumb
4. Updates `currentSection`

When you call `navSub('billing', 'invoices', el)`, it additionally:
- Calls the section's sub-renderer (e.g., `renderBillingSub('invoices')`)
- Updates the breadcrumb with the sub-section name

### Section Types

**Simple sections** — one view, no sub-tabs:
- Overview, Network, BGP, Compute, Subnets, Sessions, Workloads, Analytics, Rules, Targets, Marketplace

**Nav group sections** — expandable sidebar with sub-items:
- Locations (Map / List) — toggles `setLocView()`
- Alerts & Events (Active Alerts / Alert Routing / Contacts / Templates / Schedule / System Logs / Abuse Reports) — 8 sub-tabs via `switchAlertsTab()`. System Logs aggregates 4 sources (Containers/Workloads/Compute/Auto-Scaling). Abuse Reports has case management with message threads.
- Workloads (Overview / Stacks / Containers / Images / Registry / Logs / Scaling) — toggles `switchWlTab()`
- Settings (Profile with Sign Out / Company / Team / Security with 2FA + recovery + disable + password + sessions + SSO + Delete Account with infra check / Notifications + newsletter / Preferences) — `openDeleteAccount()` checks WL_CONTAINERS/NETWORKS/WL_STACKS for active infra, blocks or allows with password + "DELETE" confirmation
- Billing (Overview / Usage / Invoices / Payment / Plans / Funds) — calls `renderBillingSub()`

### Data Pattern
All data is hardcoded in JavaScript arrays/objects at the top of each section's JS block:
```javascript
const LOCS = [...]          // 80 locations
const NETWORKS = [...]      // 14 networks
const ALERTS = [...]        // 12 alerts
const COMPUTE_NODES = [...]  // 21 nodes
const SESSIONS = [...]       // 20 sessions
// etc.
```

Each section has a `render*()` function that reads the data and builds HTML. Filters modify what `render*()` shows.

---

## Design Tokens

### Colors
```css
/* Use CSS variables everywhere — NEVER hardcode colors */
--blue: #0ea5e9;            /* Signal Blue — primary actions */
--orange: #f97316;          /* Metro Orange — traffic layer accent */
--ai-purple: #8b5cf6;      /* AI features accent */
--success: #16a34a;         /* Green — verified, active, healthy */
--error: #dc2626;           /* Red — critical, down, danger */
--warning: #d97706;         /* Orange — degraded, building, attention */

/* Every color has -bg (background), -bdr (border) variants */
--blue-bg: #f0f9ff;
--blue-bdr: rgba(14,165,233,0.2);

/* Surfaces */
--bg: #f4f4f5;              /* Page background */
--bg-card: #ffffff;         /* Card/panel background */
--bg-input: #ffffff;        /* Input background */
--bg-hover: #f4f4f5;       /* Hover state */

/* Text hierarchy */
--text: #18181b;            /* Primary text */
--text-body: #3f3f46;       /* Body text */
--text-muted: #71717a;      /* Secondary text */
--text-dim: #a1a1aa;        /* Tertiary / labels */
```

### Dark Mode
Every component must work in both themes. Dark mode is toggled via `[data-theme="dark"]` on `<html>`:
```css
[data-theme="dark"] {
  --bg: #070d1a;
  --bg-card: #0d1526;
  /* ... all tokens overridden */
}
```

**Rule:** If you add a new color, add its dark mode override too.

### Typography
- **Display/Body:** `'Geist', sans-serif`
- **Mono/Labels/Code:** `'Geist Mono', monospace`
- Font loaded from Google Fonts CDN in `<head>`

### Spacing & Radii
```css
--radius: 8px;      /* Default border radius */
--radius-lg: 12px;  /* Cards, modals */
/* Standard padding: 16px (card body), 24px (content area) */
```

---

## Component Patterns

### Cards
```html
<div class="card">
  <div class="card-header">
    <span class="card-title">Title</span>
    <a class="card-action">Action →</a>
  </div>
  <div class="card-body">Content</div>
</div>
```

### Stat Cards
```html
<div class="stats-grid">
  <div class="stat-card">
    <div class="stat-label">Label</div>
    <div class="stat-value blue">42</div>
    <div class="stat-delta up">↑ 12% this month</div>
  </div>
</div>
```
Value colors: `blue`, `green`, `orange`, `red`. Delta classes: `up`, `down`, `neutral`.

### Badges
```html
<span class="badge green"><span class="badge-dot"></span>Active</span>
<span class="badge orange"><span class="badge-dot"></span>Degraded</span>
<span class="badge red"><span class="badge-dot"></span>Critical</span>
<span class="badge blue"><span class="badge-dot"></span>Pending</span>
```

### Drawers (Right Panel)
```html
<div id="my-drawer" style="position:fixed;top:0;right:-480px;width:460px;height:100vh;background:var(--bg-card);border-left:1px solid var(--border);box-shadow:var(--shadow-md);z-index:200;transition:right .25s ease;overflow-y:auto;">
  <!-- content -->
</div>
<div id="my-overlay" onclick="closeMyDrawer()" style="display:none;position:fixed;inset:0;z-index:199;background:rgba(0,0,0,0.15);backdrop-filter:blur(1px);"></div>
```
Open: set `right: 0` + overlay `display: block`. Close: reverse.

### Modals
```html
<div id="my-modal" style="display:none;position:fixed;inset:0;z-index:200;background:rgba(0,0,0,0.45);backdrop-filter:blur(2px);align-items:center;justify-content:center;">
  <div style="background:var(--bg-card);border-radius:14px;width:480px;...">
    <!-- header, body, footer -->
  </div>
</div>
```
Open: set `display: flex`. Close: set `display: none`.

### Form Inputs
```html
<div class="wiz-input-group">
  <label>Field Name</label>
  <input type="text" value="" placeholder="">
</div>
```
Or inline with common styles:
```css
height:38px; padding:0 12px; border:1px solid var(--border-mid);
border-radius:var(--radius); background:var(--bg-input); color:var(--text);
font-family:'Geist',sans-serif; font-size:13px; outline:none; width:100%;
```

### Select Dropdowns
All selects use a custom chevron via SVG data URI in `background-image` + `appearance:none`.

### Notification Dot (Pulsing)
```html
<div class="notif-dot"></div>  <!-- red, pulsing animation -->
```

### Action Buttons (Table Rows)
Standardized action buttons for data table rows:
```css
.cr-btn      /* Small action button (View, Edit, Delete) */
.cr-manage   /* Primary manage/open action */
.cr-actions  /* Flex container for button group in Actions column */
```
Used across all table views (Network, Subnets, Sessions, Rules, Targets, BGP, Compute, etc.) for consistent sizing and hover states.

### Toast Notifications
```javascript
showToast('IP pool created successfully', 'success');
showToast('Failed to save changes', 'error');
showToast('Rate limit approaching', 'warning');
showToast('Export started', 'info');
```
Global function. Auto-dismiss after 4 seconds. Fixed bottom-right position. Stacks vertically. z-index 9999. Color-coded by type using semantic tokens.

### Assign IPs Modal
Multi-location IP assignment with cross-location memory:
```javascript
assignIpState = {
  selected: {}   // keyed by subnet ID, persists selections across location switches
}
```
Modal shows locations as tabs, each with available subnets. Checking/unchecking a subnet in one location is remembered when switching to another location. Submit aggregates all selected subnets.

### BYOIP Field Validation
```javascript
byoipValidatePrefix()  // Returns true/false, shows inline error messages
```
Checks: valid CIDR format, prefix length between /20 and /24, not a private/reserved range (10.x, 172.16-31.x, 192.168.x, 100.64-127.x), not a duplicate of existing BYOIP_PREFIXES or NETWORKS subnets.

---

## How To Add a New Section

### 1. Add sidebar navigation
In the `<nav class="sidebar-nav">`, add either a simple item or a nav group:

**Simple:**
```html
<a class="nav-item" onclick="nav('mysection',this)" href="#">
  <span class="nav-icon"><svg viewBox="0 0 24 24"><!-- icon --></svg></span>
  My Section
</a>
```

**With sub-items:**
```html
<div class="nav-group" id="nav-group-mysection">
  <button class="nav-group-toggle" onclick="toggleNavGroup('mysection',this)">
    <span class="nav-icon"><svg><!-- icon --></svg></span>
    My Section
    <span class="nav-group-chevron"><svg viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></span>
  </button>
  <div class="nav-sub-items">
    <a class="nav-sub-item" onclick="navSub('mysection','tab1',this)" href="#">
      <span class="nav-sub-icon"><svg><!-- icon --></svg></span>Tab 1
    </a>
  </div>
</div>
```

### 2. Add the view container
Inside `.content`:
```html
<div class="view" id="view-mysection">
  <div class="page-title">My Section</div>
  <p class="page-sub">Description</p>
  <div id="mysection-content"></div>
</div>
```

### 3. Add section metadata
In the `sectionMeta` object in JS:
```javascript
mysection: {label:'My Section', cta:'Add Item'},
```

### 4. Add CTA action
In the `ctaActions` object:
```javascript
mysection: () => myAction(),
```

### 5. Add rendering logic
```javascript
function renderMySection(){
  const el = document.getElementById('mysection-content');
  el.innerHTML = `...`;
}
```

### 6. Add lazy initialization
```javascript
(function(){
  const obs = new MutationObserver(()=>{
    if(document.getElementById('view-mysection').classList.contains('active')) renderMySection();
  });
  obs.observe(document.getElementById('view-mysection'),{attributes:true,attributeFilter:['class']});
  if(document.getElementById('view-mysection').classList.contains('active')) renderMySection();
})();
```

### 7. Wire sub-tab rendering (if applicable)
In the `navSub` function, add:
```javascript
if(typeof renderMySub === 'function' && section==='mysection') renderMySub(sub);
```

### 8. Update CLAUDE.md
Add entry to the build status table and document the data structures.

---

## Key Features Reference

### AI Features
| Feature | Trigger | Key Functions |
|---------|---------|---------------|
| Command Palette | `⌘K` | `openAiPalette()`, `closeAiPalette()`, `renderPaletteItems()` |
| AI Chat Panel | Purple sparkle button | `openAiChat()`, `closeAiChat()`, `sendAiMessage()` |
| Smart Search | Click search / type | `renderSearchDropdown()`, `searchGo()`, `closeSearchAndAsk()` |
| AI Insights | Overview page card | Static HTML, clickable items navigate to sections |

### BYOIP (Bring Your Own IP)
Under Subnets, a 4-step wizard for importing customer-owned IP prefixes:
1. **Prefix** — enter IP prefix with format/length/private-range/duplicate validation (`byoipValidatePrefix()`)
2. **Source** — IPXO transfer or third-party import
3. **ROA Verification** — Route Origin Authorization check with status polling
4. **Confirm** — summary + provision

Key functions: `openByoipWizard()`, `byoipStep(dir)`, `renderByoipStep()`, `byoipValidatePrefix()`
Data: `BYOIP_PREFIXES` array, `byoipWizData` state object

### Toast Notifications
```javascript
showToast(msg, type)  // type: 'success' | 'error' | 'warning' | 'info'
```
Global notification function. Auto-dismisses after 4 seconds. Stacks multiple toasts vertically. z-index 9999. Used across all create/save/delete actions.

### Harmonized Actions Column
All data tables use a standardized Actions column pattern with `.cr-btn` / `.cr-manage` / `.cr-actions` button classes. Ensures consistent sizing, spacing, and hover states across Network, Subnets, Sessions, Rules, Targets, BGP, Compute, and other table views.

### Walkthrough
```javascript
startWalkthrough()    // Begin tour
endWalkthrough()      // End and save to localStorage
restartWalkthrough()  // Clear localStorage and restart
```
On page load: **Setup Wizard** launches first (Country/TZ → 2FA → Newsletter), then **Walkthrough** starts after wizard completes/skips. Both have localStorage checks commented out for dev (`mf-grid-setup`, `mf-grid-walkthrough`).

### Persistent Tab Bar
Sections with form interactions auto-pin when navigating away:
```javascript
markSectionDirty(section, sub)  // Manually mark dirty
saveSectionPin(section, sub)    // Remove pin (after save)
unpinSection(key)               // Close with discard confirm
```
Tracked sections: `settings`, `billing`, `alerts`, `api`.

### Header Popups
```javascript
toggleHdrPopup('helpdesk')  // Helpdesk panel
toggleHdrPopup('status')    // System status
toggleHdrPopup('notifs')    // Notifications
```

### User Menu
```javascript
toggleUserMenu()      // Sidebar footer expand
toggleLocalePopup()   // Currency + language selector
```

### Alert Routing System
```javascript
switchAlertsTab(tab)          // 'feed', 'routing', 'contacts', 'templates', 'schedule'
renderAlertRouting()          // Channels, escalation policies, routing rules
renderAlertContacts()         // On-call contacts with quiet hours
renderTemplates()             // Message template list + editor
toggleEscPolicy(pid)          // Expand/collapse escalation workflow
openEscBuilder()              // New escalation policy builder modal
openEscBuilderEdit(pid)       // Edit existing policy
```

**Alert Routing sub-tabs:**
- **Active Alerts** — severity filters, bulk actions, 12 alerts
- **Alert Routing** — 6 delivery channels (Portal/Email/SMS/WhatsApp/Slack/PagerDuty), collapsible escalation policies with visual workflow, policy builder modal, 7 routing rules
- **Contacts** — team members with add/edit/delete modal (`openContactEditor()`, `saveContact()`, `deleteContact()`), full field validation (required: name, role, email, phone, country, timezone; email regex; red border + error summary; real-time reset on input), per-weekday quiet hours (Mon-Sun toggles), on-call/off-duty toggle, consistent colors via `getContactColor()`
- **Schedule** — week/month/year views; functional rotation types: weekly (same person all week) and daily (cycles each day) via shared `getRotationPrimary(date, contacts)`; visual states: primary=solid 85% opacity, backup=solid 25%, quiet=diagonal stripes 30%, off-duty=gray stripes; rotation roster (add/remove/reorder); bidirectional contacts sync; `schedAddToRotation()`, `schedRemoveFromRotation()`, `schedMoveUp/Down()`
- **Templates** — 6 message templates per channel, rich text editor (bold/italic/lists), variable library (13 vars), AI Generate, character counter with SMS segment tracking; bottom setup cards: firewall whitelist table (domains + IPv4 + IPv6, aligned copy buttons, Copy All), Slack Bot 4-step setup guide with Add to Slack button, Portal Push Notifications card with browser enable button

### Message Template Editor
```javascript
renderTemplateEditor()        // Renders editor for current template
insertVariable(varKey)        // Inserts {{variable}} tag at cursor
tplAiGenerate()               // AI-generates channel-appropriate content
tplFormat(cmd)                // execCommand for bold/italic/lists
copyToClip(btn, text)         // Copy to clipboard with visual feedback
```

**Channel limits:** SMS = 160 chars/segment, WhatsApp = 4,096 chars, Email/Slack/Portal = no limit.

**Template variables:** `{{alert.title}}`, `{{alert.severity}}`, `{{alert.type}}`, `{{alert.description}}`, `{{alert.location}}`, `{{alert.time}}`, `{{alert.status}}`, `{{alert.eta}}`, `{{alert.id}}`, `{{contact.name}}`, `{{contact.role}}`, `{{company.name}}`, `{{portal.url}}`

---

## Z-Index Layers

| Layer | Z-Index | Element |
|-------|---------|---------|
| Drawers overlay | 199 | `..*-overlay` |
| Drawers | 200 | All 6 drawers (top:0, height:100vh) |
| Quick Modal | 300 | `#quick-modal` |
| AI Chat | 300 | `#ai-chat` |
| Command Palette | 500 | `#ai-palette` |
| Discard modal | 600 | Discard confirm dialog |
| Walkthrough | 9000+ | `#wt-overlay`, `#wt-spotlight`, `#wt-balloon` |
| Toasts | 9999 | `showToast()` notifications |

---

## Common Gotchas

### Template Literals in `<script>`
Never write `</script>` inside a JS string. The browser will close the script tag. If you need it, escape: `<\/script>` or split: `'</sc'+'ript>'`.

### CSS Variable Usage
Never hardcode a color. Always use `var(--token)`. If a new color is needed, add it to `:root` AND `[data-theme="dark"]`.

### Overflow Hidden
`.card` has `overflow:hidden` by default. If you need tooltips or dropdowns inside a card to overflow, add `overflow:visible` to that specific card.

### Sortable Log Tables
All log/audit tables must have clickable sortable column headers. Pattern: declare a sort state object `let xLogSort = {field:'time',dir:-1}`, a `xLogSortBy(field)` function that toggles direction, and apply `.sort((a,b)=>(a[f]||'').localeCompare(b[f]||'')*d)` to filtered results. Active column shows blue with ↑/↓ arrow. Used in: Workloads Logs, Compute Logs, Containers list, Invoices.

### Collapsible Sidebar
The sidebar collapses to 56px icon-only mode via `.sidebar.collapsed`. Toggle with `toggleSidebar()`. Auto-collapses below 1200px (`checkSidebarAutoCollapse()`). Persists via `localStorage` key `mf-grid-sidebar`. When collapsed: nav text hidden, badges hidden, sub-items hidden, user info hidden, logo text hidden.

### Platform Tooltips
Add tooltips with `class="mf-tip" data-tip="Your text"`. Add a `<span class="mf-tip-icon">` with the info SVG for non-interactive elements. Tooltips are hidden when `body.tips-off` is set. Use `initTooltips()` at startup and `injectTooltips()` for dynamic elements. Click a tooltip icon to offer "Disable all" option. Use `enableTooltips()` / `disableTooltips()` programmatically.

### Sidebar Sync on Tab Switch
Every tab switcher function (`switchAlertsTab`, `switchWlTab`, `setLocView`, `renderSettingsSub`, `renderBillingSub`) must call `syncSidebarHighlight(section, sub)` to keep the sidebar nav highlighting in sync. Without this, clicking tabs in the content area won't update which sidebar item is active. Always add this call when creating new tab switchers.

### Dirty State — Selection Checkboxes
The global `input` listener marks sections dirty when form fields change. But selection checkboxes (invoice bulk select, alert bulk select) are NOT form data — they must be excluded. The listener skips: `.inv-chk` checkboxes, `th` header checkboxes, `#chk-all`, and the entire `billing/invoices` sub-tab. When adding new selection UIs, add similar exclusions.

### Modal Input Event Propagation
All modals with input fields MUST have `onkeydown="event.stopPropagation()"` on both the modal container and each input/select element. Without this, global keyboard handlers (⌘K, Escape) will intercept keystrokes like Backspace and close the modal. Also escape HTML in values with `.replace(/"/g,'&quot;')`.

### Escape Key Priority
The global Escape handler must check modals in z-index order (highest first): contact editor → escalation builder → AI palette. Each modal should return early to prevent closing multiple layers.

### SVG Icons
All icons use Feather-style SVGs:
```html
<svg viewBox="0 0 24 24" style="width:14px;height:14px;stroke:currentColor;stroke-width:1.75;fill:none;stroke-linecap:round;stroke-linejoin:round;">
  <!-- paths -->
</svg>
```
Icon source: [Feather Icons](https://feathericons.com/) — use their paths directly.

### Syntax Validation
Always validate JS after editing:
```bash
node -e "
const fs = require('fs');
const html = fs.readFileSync('grid-dashboard.html','utf8');
const scriptMatch = html.match(/<script>([\s\S]*?)<\/script>/);
try { new Function(scriptMatch[1]); console.log('OK'); } catch(e) { console.log('ERR:', e.message); }
"
```

### MutationObserver Pattern
Sections use `MutationObserver` to detect when they become active (`.active` class added to their `.view` div). This is the lazy-init pattern — don't render section content until it's visible.

### syncSidebarHighlight Matching
`syncSidebarHighlight()` uses a regex on the second `navSub` argument to match sidebar sub-items — it does NOT use `string.includes()`. This avoids false matches when a section name happens to appear inside an `onclick` attribute string. When adding new sub-items, make sure the sub-section name passed to `navSub` is distinct enough for regex matching.

### Drawer Geometry
All 6 drawers (Network, Locations, BGP, Compute, Pools, Subnets) use consistent positioning: `top:0; height:100vh; z-index:200`. Their overlay backdrops use `z-index:199`. This ensures drawers sit above page content but below modals and the AI chat panel.

### Quick Modal Scrollbar
Quick modals use hidden scrollbar styling: `scrollbar-width:none` for Firefox, plus a thin webkit scrollbar (6px wide, transparent track, rounded thumb) for Chrome/Safari. This keeps the modal looking clean while still being scrollable.

---

## Cursor IDE Tips

### Opening the Project
```bash
cursor "/Users/vincentas/dev/Metro Fabric/Grid"
```

### Useful Cursor Commands
- **Cmd+Shift+P → "Go to Line"** — jump to specific line numbers (referenced throughout CLAUDE.md)
- **Cmd+D** — select next occurrence (useful for renaming CSS variables across the file)
- **Cmd+Shift+F** — search across files (only 3 files to search)
- **Fold all** (Cmd+K, Cmd+0) — collapse everything, then unfold sections you're working on

### Working with the Single-File Architecture
The file is ~13,498 lines. To navigate efficiently:

1. **CSS sections** (lines 8–1546) are marked with `/* ══ SECTION NAME ══ */`
2. **HTML sections** (lines 1548–3437) are marked with `<!-- ══ SECTION ══ -->`
3. **JS sections** (lines 3438–9707) are marked with `/* ══ SECTION ══ */`

**Search patterns:**
- Find a view: search for `id="view-{section}"`
- Find a render function: search for `function render{Section}`
- Find data: search for `const {DATA_NAME} =`
- Find CSS for a component: search for `.{class-name}{`

### Using Cursor AI
When asking Cursor AI to modify this project:
- Always reference CLAUDE.md for context — tell it to read the file first
- Specify which section (CSS/HTML/JS) you're modifying
- Ask it to validate JS syntax after changes
- Remind it about dark mode when adding styles
- Remind it about the single-file constraint

### Recommended .cursorrules
Create `.cursorrules` in the project root with:
```
This is a single-file HTML dashboard (grid-dashboard.html). All CSS, HTML, and JS are inline.
Always use CSS variables (never hardcode colors). Every style must work in dark mode.
Use the existing component patterns (cards, badges, drawers, modals).
After any JS change, validate syntax with: node -e "..."
Read CLAUDE.md for full architecture and build status.
```

---

## File Map (grid-dashboard.html)

| Lines | Section | Content |
|-------|---------|---------|
| 8–80 | Tokens | CSS variables (light + dark) |
| 81–250 | Shell CSS | Reset, app, sidebar, nav, footer styles |
| 251–470 | Components CSS | Header, cards, stats, badges, tables, charts |
| 470–720 | Feature CSS | Locations, alerts, network, pools, workloads, sessions, analytics, BGP, geo, compute, subnets, rules, targets, marketplace |
| 720–1000 | AI CSS | AI accent tokens, AI button, command palette, AI chat panel, AI search dropdown, AI insights card |
| 1000–1170 | UX CSS | Header popups, user menu, locale popup, geo card grid |
| 1170–1265 | Schedule CSS | Schedule timeline, year heatmap, week hour grid, quiet day rows |
| 1265–1460 | Flow CSS | Template editor, persistent tabs, escalation flow, escalation builder |
| 1460–1546 | Walkthrough CSS | Overlay, spotlight, balloon, welcome |
| 1548–1700 | Sidebar HTML | Logo, nav groups (Alerts 5 sub-items, Locations 2, Settings 6, Billing 6), footer, user menu, locale popup |
| 1700–1900 | Header HTML | Breadcrumb, search box with AI dropdown, AI btn, helpdesk/status/notif popups, CTA |
| 1900–3300 | Views HTML | All 17 section view containers: Overview, Alerts (feed+routing+contacts+templates+schedule), Locations, Network, BGP, Geolocation, Compute, Subnets, Pools, Sessions, Workloads, Rules, Analytics, Targets, API, Marketplace, Settings, Billing |
| 3300–3437 | Overlays HTML | Contact editor modal, escalation policy builder, walkthrough elements, AI palette, AI chat |
| 3438–3770 | Core JS | Theme, nav, navSub, toggleNavGroup, CTA dispatcher, sectionMeta, bandwidth chart |
| 3770–4060 | Locations JS | LOCS data (80 PoPs), D3 map, list view, drawer, filters |
| 4060–4270 | Alerts JS | ALERTS data, filters, severity tabs, bulk actions, renderAlerts |
| 4270–4540 | Alert Routing JS | switchAlertsTab, channels, escalation policies, contacts rendering, quiet hours (per-weekday) |
| 4540–4640 | Contact Editor JS | openContactEditor, saveContact, deleteContact, toggleQuietDay |
| 4640–5050 | Schedule JS | Week/month/year views, schedNav, date picker, rotation roster management, add/remove/reorder, bidirectional sync |
| 5050–5100 | Copy JS | copyToClip, copyAllWhitelist, firewall IP rendering |
| 5100–5630 | Templates JS | Message templates, template editor, variable library, AI generate, channel limits |
| 5630–5870 | Escalation JS | Policy collapse, policy builder modal, step editor |
| 5870–6070 | Network JS | NETWORKS data, sparklines, detail drawer |
| 6070–6220 | Network Wizard JS | 5-step provision wizard |
| 6220–6550 | Pools JS | Pool data, drawer, pool wizard |
| 6550–7100 | Workloads (new) JS | Container Manager: switchWlTab, sortable container columns (wlCtSortBy), bulk actions, per-container terminal (680px, simulated shell) + log viewer, stack YAML editor, overview with expand/collapse + inline controls |
| 7050–7400 | Workloads (legacy) JS | Legacy images/deployments/wizard (hidden, kept for backward compat) |
| 6940–7250 | Sessions + Analytics JS | Session table, analytics charts |
| 7250–7510 | BGP JS | BGP sessions, drawer |
| 7510–7650 | Geolocation JS | GEO_DATA (7 providers), card grid with match% bars |
| 7650–7740 | Compute JS | 21 nodes, drawer |
| 7740–7920 | Remaining JS | Subnets, BYOIP (data + render + wizard + manage), settings (profile with country/timezone), rules, targets |
| 7920–8070 | API + Marketplace JS | API keys, webhooks, marketplace integrations |
| 8070–8160 | Billing JS | BILLING_PLAN, BILLING_USAGE, BILLING_INVOICES, BILLING_PAYMENTS |
| 8160–8340 | Billing Render JS | renderBillingSub (overview, usage, invoices with bulk select/pay/download, payment, plans, funds) |
| 8340–8480 | Walkthrough JS | WT_STEPS, startWalkthrough, spotlight, balloon positioning |
| 8480–8610 | Persistent Tabs JS | markSectionDirty, pinCurrentIfDirty, unpinSection, discard modal |
| 8610–8750 | Header Popups JS | toggleHdrPopup, user menu, locale selector |
| 8750–8820 | AI Palette JS | Command palette, AI_COMMANDS, keyboard nav |
| 8820–9100 | AI Chat JS | Chat panel, AI_RESPONSES, sendAiMessage, suggestions |
| 9100–9360 | AI Search JS | SEARCH_INDEX, renderSearchDropdown, searchGo |
| 9360–9707 | Walkthrough Init JS | Auto-start, restartWalkthrough |

---

## What's Built vs. What's Next

See **CLAUDE.md** → "Sidebar Sections & Build Status" for the full status table, and "What To Build Next" for priorities.

**Current priorities:**
1. Pricing Rationale Document (separate HTML file)
2. Walkthrough re-enable (localStorage check currently commented out for dev)
3. AI Chat backend integration (currently simulated responses)
4. Dark mode testing pass across all new features

---

## Contact

- **Product:** Vincentas (CEO context: Ramute)
- **Architecture:** Ignas and Mara (IPXO)
- **Interface designer:** Vincentas (IPXO)
- **Domain:** metrofabric.net / grid.metrofabric.net
- **GitHub:** https://github.com/Metro-Fabric/grid
- **Live site:** https://grid-czb.pages.dev (password: `MetroGrid2024`)
