Skip to content

feat: Custom Panel Plugin SDK & Multi-View Dashboard (#32, #33)#35

Merged
alohays merged 4 commits intomainfrom
feat/panel-sdk-and-views
Mar 8, 2026
Merged

feat: Custom Panel Plugin SDK & Multi-View Dashboard (#32, #33)#35
alohays merged 4 commits intomainfrom
feat/panel-sdk-and-views

Conversation

@alohays
Copy link
Owner

@alohays alohays commented Mar 7, 2026

Summary

Two tightly coupled features implemented together to avoid file conflicts across shared code paths (PanelManager, manifest-generator, schema).

Custom Panel Plugin SDK (closes #32)

  • forge panel create <name> scaffolds src/custom-panels/<PascalCase>.ts extending PanelBase with render()/update()/destroy() stubs and JSDoc
  • Manifest generator auto-imports custom panels from src/custom-panels/ and registers them by panel name
  • panel-registry handles type: 'custom' lookup via config.name instead of literal 'custom'
  • PanelSchema gains optional customModule field with PascalCase validation
  • Validation warns on type: 'custom' panels missing customModule

Multi-View Dashboard Composition (closes #33)

  • ViewSchema: name, displayName, panels[], icon?, default?
  • forge view add/remove/list/set-default CLI commands with full --format json --dry-run support
  • PanelManager refactored: view containers, switchView(), keyboard shortcuts (1/2/3), URL hash routing (#view=<name>)
  • View tabs rendered in header alongside existing health indicator and layer toggle
  • Panels can appear in multiple views (separate DOM instances, all receive data updates)
  • Backward compatible: empty views[] = current flat sidebar behavior, zero migration needed

Validation enhancements

  • Duplicate view names → error
  • View referencing unknown panel → error
  • Multiple default: true views → error
  • Orphan panels (not in any view when views are defined) → warning
  • Custom panel without customModule → warning

Test plan

  • npx tsc --noEmit — typecheck clean
  • npx vitest run — 473/473 tests pass (3 new tests added)
  • forge build --skip-vite — generates 5 manifests (was 4)
  • forge panel create weather — scaffolds file + registers in config
  • forge view add overview --panels tech-news,weather --default — creates view
  • forge view list — displays views with panel membership
  • forge validate — passes, warns on orphan panels
  • Pre-push hook (manifests + typecheck + tests + validate) — all passed

🤖 Generated with Claude Code

@alohays
Copy link
Owner Author

alohays commented Mar 8, 2026

Code review

Found 3 issues:

  1. Direct edit of monitor-forge.config.json (AGENTS.md says "NEVER manually edit monitor-forge.config.json -- use npx tsx forge/bin/forge.ts <cmd>"). The PR adds "views": [] directly to the config file. Since the Zod schema already defines views: z.array(ViewSchema).default([]), the field defaults to [] when absent -- making this manual edit unnecessary. This same violation was flagged and fixed in PR feat: Live Dashboard Animations — Sparklines, Counters, Pulse & Skeleton #27.

],
"views": [],
"ai": {

  1. View tab active state desyncs on keyboard/hash navigation. renderViewTabs in App.ts only updates the tab active CSS class inside its click handler. When views are switched via keyboard shortcuts (1/2/3) or URL hash changes -- both handled directly by PanelManager.switchView() -- the content changes but the tab highlighting stays on the old tab. PanelManager.switchView() has no callback or event to notify App.ts of non-click view changes.

monitor-forge/src/App.ts

Lines 128 to 134 in 3dbab3b

tab.addEventListener('click', () => {
this.panelManager?.switchView(view.name);
tabContainer.querySelectorAll('.forge-view-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
});

switchView(viewName: string): void {
if (this.activeView === viewName) return;
this.viewContainers.forEach((el, name) => {
el.style.display = name === viewName ? 'flex' : 'none';
});
this.activeView = viewName;
history.replaceState(null, '', `#view=${viewName}`);
}

  1. Custom panel without customModule passes validation but crashes at runtime. A panel with type: 'custom' but no customModule only gets a warning in validation (not an error), so the build succeeds. However, the manifest generator skips the import/registerPanelType call for panels without customModule, so the registry lookup in panel-registry.ts throws "Unknown panel type" at runtime. Consider promoting this to a validation error.

// Check custom panels have customModule
for (const panel of config.panels) {
if (panel.type === 'custom' && !panel.customModule) {
warnings.push(`Custom panel "${panel.name}" is missing customModule field`);
}
}

export function createPanel(container: HTMLElement, config: PanelConfig): PanelBase {
const key = config.type === 'custom' ? config.name : config.type;
const cls = registry.get(key);

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

alohays added a commit that referenced this pull request Mar 8, 2026
1. Remove manual `views: []` from config (schema default suffices)
2. Add onViewChange callback so view tabs sync on keyboard/hash nav
3. Promote missing customModule to validation error (prevents runtime crash)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
alohays and others added 4 commits March 8, 2026 22:08
…#33)

Add two tightly coupled features in a single PR to avoid file conflicts:

Custom Panel Plugin SDK (#32):
- `forge panel create <name>` scaffolds src/custom-panels/<PascalCase>.ts
  extending PanelBase with render/update/destroy stubs
- Manifest generator auto-imports and registers custom panels by name
- panel-registry handles `type: 'custom'` lookup via config.name
- PanelSchema gains optional `customModule` field (PascalCase validation)
- Validation warns on custom panels missing customModule

Multi-View Dashboard (#33):
- ViewSchema: name, displayName, panels[], icon?, default?
- `forge view add/remove/list/set-default` CLI commands
- PanelManager refactored: view containers, switchView(), keyboard
  shortcuts (1/2/3), URL hash routing (#view=<name>)
- View tabs rendered in header alongside existing controls
- Panels support appearing in multiple views (Map<string, PanelBase[]>)
- Backward compatible: empty views array = current flat sidebar

Testing: 473 tests pass, typecheck clean, all presets validate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Remove manual `views: []` from config (schema default suffices)
2. Add onViewChange callback so view tabs sync on keyboard/hash nav
3. Promote missing customModule to validation error (prevents runtime crash)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…a enforcement

- Remove redundant active class toggle from tab click handler (onViewChange
  callback is single source of truth)
- Skip panel updates for hidden views to avoid wasted CPU on animations;
  cache last data and replay on view switch
- Add 150ms opacity fade transition between views
- Validate panel name format before file operations in `panel create`
  (prevents path traversal with --no-register)
- Pre-validate config before writing scaffold file; rollback on failure
- Enforce customModule via Zod refine() for custom panel type
- Add ARIA tablist/tab/tabpanel pattern with aria-selected
- Add keyboard shortcut hints as tab title tooltips
- Improve mobile tab scrolling with hidden scrollbar + touch support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…itching

- Add panel command tests (23 tests): create, add, remove, list,
  name validation, path traversal rejection, --no-register, --dry-run,
  config rollback on failure
- Add view command tests (16 tests): add, remove, list, set-default,
  panel validation, duplicate rejection, --dry-run
- Add view validation tests (6 tests): duplicate views, unknown panels,
  multiple defaults, orphan panels, customModule enforcement
- Add PanelManager view switching tests (9 tests): initializeWithViews,
  switchView, getActiveView, getViewNames, onViewChange callback,
  updateAll skips hidden views, destroy cleanup
- Fix schema test for custom panel type requiring customModule

Total: 492 → 546 tests (+54)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@alohays alohays force-pushed the feat/panel-sdk-and-views branch from 0b5f6ff to aa315cf Compare March 8, 2026 13:13
@alohays
Copy link
Owner Author

alohays commented Mar 8, 2026

QA Review & Fixes Applied

Performed a thorough QA review as a QA engineer. Resolved the merge conflict, fixed all bugs found, added UX/accessibility improvements, and wrote comprehensive test coverage.

Merge Conflict Resolution

Bugs Fixed

Issue Severity Fix
Tab click handler + onViewChange callback both toggling active classes → redundant double DOM update HIGH Removed manual toggle from click handler; onViewChange is now the single source of truth
updateAll() / updatePanel() updating ALL panel instances including hidden views → wasted CPU on pulse/skeleton animations HIGH Added panelToViews tracking map + isPanelInActiveView() gate; cached data replayed on view switch
Path traversal in panel create --no-register — no schema validation runs, names like ../../etc bypass all checks HIGH Added early /^[a-z0-9-]+$/ regex + defense-in-depth filePath.startsWith(customDir) check
Scaffold file written before config validation → orphaned file on config failure MEDIUM Pre-validate config, write file, update config with unlinkSync() rollback on failure
customModule optional even for type: 'custom' panels — schema allows invalid configs MEDIUM Added .refine() to PanelSchema enforcing customModule when type === 'custom'

UX & Accessibility Improvements

  • ARIA tab pattern: role="tablist" / role="tab" / role="tabpanel" with aria-selected, aria-controls, aria-labelledby
  • Keyboard shortcut hints: Tab title tooltips — e.g. "Overview (press 1)"
  • View fade transitions: 150ms opacity crossfade between views (CSS transition + setTimeout in switchView())
  • Mobile tab scrolling: Hidden scrollbar with -webkit-overflow-scrolling: touch support

Test Coverage

Area Before After
Total tests 492 546 (+54)
Panel command tests (panel create/add/remove/list) 0 23
View command tests (view add/remove/list/set-default) 0 16
Validate view tests (duplicates, orphans, defaults, customModule) 0 6
PanelManager view switching tests (switchView, ARIA, hidden updates) 0 9

Verification

  • npx tsc --noEmit — clean
  • npx vitest run — 546/546 pass
  • forge validate — passes
  • Pre-push hook (manifests + typecheck + tests + validate) — all passed
  • CI — all checks green

@alohays alohays merged commit f5c22a7 into main Mar 8, 2026
1 check passed
@alohays alohays deleted the feat/panel-sdk-and-views branch March 8, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant