-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Problem
monitor-forge currently renders a fixed 2-pane layout: map on the left, single sidebar (380px) on the right. All panels stack vertically in one scrollable sidebar. There is no way to organize panels into logical groups or switch between different "views" of the same data.
Current limitations:
- Flat panel array:
panels[]in config has apositionfield for ordering, but all panels render into the same<aside class="forge-sidebar">container - Single initialization path:
App.tscreates onePanelManagerthat manages all panels in one container - No layout flexibility: CSS has
--panel-width: 380pxhardcoded, no grid/multi-column support - No view switching: No tabs, no dropdown, no URL-based view routing
Real-world pain: A geopolitics dashboard might need an "Overview" view (news + instability index), an "Intelligence" view (AI brief + entity tracker), and a "Sources" view (service status + detailed feeds). Today, all 5-6 panels stack into one long sidebar, forcing users to scroll constantly.
Competitor comparison: worldmonitor solves this with VITE_VARIANT — 5 separate dashboard builds from one codebase. But this requires separate build & deploy per variant. monitor-forge can do better: multiple views within a single deployment, switchable at runtime, defined in config.
Solution
1. Schema Extension: views Array
Add an optional views field to the config schema:
// forge/src/config/schema.ts
const ViewSchema = z.object({
name: z.string().regex(/^[a-z0-9-]+$/),
displayName: z.string(),
panels: z.array(z.string()), // panel names to include
icon: z.string().optional(), // emoji or icon identifier
default: z.boolean().optional(), // first view to show
});
// In MonitorForgeConfigSchema:
views: z.array(ViewSchema).optional(),Config example:
{
"panels": [
{ "name": "news", "type": "news-feed", ... },
{ "name": "ai-brief", "type": "ai-brief", ... },
{ "name": "entities", "type": "entity-tracker", ... },
{ "name": "instability", "type": "instability-index", ... },
{ "name": "health", "type": "service-status", ... }
],
"views": [
{
"name": "overview",
"displayName": "Overview",
"panels": ["news", "instability"],
"default": true
},
{
"name": "intelligence",
"displayName": "Intelligence",
"panels": ["ai-brief", "entities"]
},
{
"name": "ops",
"displayName": "Operations",
"panels": ["health"]
}
]
}2. View Switcher UI
Add a tab bar or pill selector in the header area:
┌──────────────────────────────────────────────────────┐
│ 🌐 Korean Tech Watch [Overview] [Intel] [Ops] │
├────────────────────────────────┬─────────────────────┤
│ │ ┌─ News Feed ────┐ │
│ MAP │ │ ... │ │
│ │ └─────────────────┘ │
│ │ ┌─ Instability ──┐ │
│ │ │ ... │ │
│ │ └─────────────────┘ │
└────────────────────────────────┴─────────────────────┘
- Tabs render in
<header class="forge-header">alongside existingforge-header-actions - Active view highlighted with
--accentcolor - View switching is instant — panels are pre-rendered but hidden via CSS
display: none - Keyboard shortcut:
1/2/3to switch views
3. PanelManager Refactor
// Current: PanelManager renders all panels into sidebar
// New: PanelManager assigns panels to view containers
class PanelManager {
private views: Map<string, HTMLElement>; // view name → container
initializeViews(viewConfigs: ViewConfig[]) {
for (const view of viewConfigs) {
const container = document.createElement('div');
container.className = 'forge-view';
container.dataset.view = view.name;
// Only show default view initially
container.style.display = view.default ? 'flex' : 'none';
this.sidebar.appendChild(container);
this.views.set(view.name, container);
}
}
switchView(viewName: string) {
this.views.forEach((el, name) => {
el.style.display = name === viewName ? 'flex' : 'none';
});
// Update URL hash
history.replaceState(null, '', `#view=${viewName}`);
}
}4. Backward Compatibility
- If
viewsis omitted from config → all panels render in sidebar as before (single implicit "main" view) - No breaking changes to existing configs
positionfield still respected within each view
5. URL Hash Routing
#view=intelligenceloads the intelligence view directly- Shareable: copy URL → recipient sees the same view
- Works with feat: Interactive Map-Panel Intelligence Linking with Choropleth Heatmap & URL State Sharing #23 (URL state sharing) —
#view=intel&entity=TSMC&zoom=8
6. CLI Commands
# Add a new view
forge view add intelligence --display-name "Intelligence" --panels ai-brief,entities
# List views
forge view list
# ┌────────────────┬─────────────────┬──────────────────────┬─────────┐
# │ Name │ Display Name │ Panels │ Default │
# ├────────────────┼─────────────────┼──────────────────────┼─────────┤
# │ overview │ Overview │ news, instability │ ✓ │
# │ intelligence │ Intelligence │ ai-brief, entities │ │
# │ ops │ Operations │ health │ │
# └────────────────┴─────────────────┴──────────────────────┴─────────┘
# Remove a view (panels are NOT deleted, just unassigned)
forge view remove ops
# Set default view
forge view set-default intelligenceImplementation Notes
Files to modify:
forge/src/config/schema.ts— addViewSchemaandviewsfieldforge/src/commands/— addview.tscommand (add/remove/list/set-default)src/App.ts— initialize views, wire view switchersrc/core/panels/PanelManager.ts— view container management,switchView()src/styles/base.css— view tab styles,.forge-viewcontainer
Files to create:
forge/src/commands/view.ts— CLI command implementation
Validation rules:
- Every panel name in
views[].panelsmust exist inpanels[] - A panel can appear in multiple views (useful for "health" panel in every view)
- At most one view can have
default: true - View names must be unique