affaan-m/ECC · error · Error

Unknown panel: . Valid panels

Error message

Unknown panel: ${selectedPanel}. Valid panels: ${Array.from(VALID_PANELS).join(', ')}

What it means

renderDashboard renders one of a fixed set of panels (or all when no panel is given) validated against the VALID_PANELS set. When options.panel names a panel that is not in that set, the dashboard cannot know what to render and throws, listing all valid panel names.

Solutions

  1. Use a panel name exactly as listed in the error message (e.g. 'amendments', 'versions'), matching lowercase spelling.
  2. Normalize user input before calling: options.panel = String(raw).trim().toLowerCase().
  3. Omit options.panel to render the full dashboard with all panels.
  4. If a needed panel is genuinely missing, update to a library version that includes it or add a panel renderer to VALID_PANELS.

Example fix

// before
renderDashboard({ panel: 'Health' });

// after
const valid = ['amendments', 'versions'];
const panel = String(process.argv[3] || '').trim().toLowerCase();
renderDashboard({ panel: valid.includes(panel) ? panel : undefined });
Defensive patterns

Strategy: validation

Validate before calling

const VALID_PANELS = new Set(['amendments', 'versions']);
const panel = opts.panel ? String(opts.panel).trim().toLowerCase() : null;
if (panel && !VALID_PANELS.has(panel)) throw new Error(`panel must be one of: ${[...VALID_PANELS].join(', ')}`);

Type guard

const isValidPanel = (p) => typeof p === 'string' && VALID_PANELS.has(p.trim().toLowerCase());

Try / catch

try {
  return renderDashboard({ panel });
} catch (err) {
  if (err.message.startsWith('Unknown panel')) {
    console.error(err.message); // includes valid panels list
    return renderDashboard({});
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling renderDashboard({ panel: 'summary' }) when 'summary' is not in VALID_PANELS (valid ones include e.g. 'amendments', 'versions'); passing a CLI --panel flag with a typo or different casing ('Health' vs 'health'); passing panel names from an older version that were renamed or removed.

Common situations: Users typing panel names from memory in CLI invocations; scripts generated against an older library version where panels were renamed; case-sensitive matching breaking on user input like 'Overview'.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/5ee116c365f177e0. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/skill-evolution/dashboard.js:353

    throw new Error(`Invalid now timestamp: ${now}`);
  }

  const dashboardOptions = { ...options, now };
  const records = tracker.readSkillExecutionRecords(dashboardOptions);
  const skillsById = health.discoverSkills(dashboardOptions);
  const report = health.collectSkillHealth(dashboardOptions);
  const summary = health.summarizeHealthReport(report);

  const panelRenderers = {
    'success-rate': () => renderSuccessRatePanel(records, report.skills, dashboardOptions),
    'failures': () => renderFailureClusterPanel(records, dashboardOptions),
    'amendments': () => renderAmendmentPanel(skillsById, dashboardOptions),
    'versions': () => renderVersionTimelinePanel(skillsById, dashboardOptions),
  };

  const selectedPanel = options.panel || null;
  if (selectedPanel && !VALID_PANELS.has(selectedPanel)) {
    throw new Error(`Unknown panel: ${selectedPanel}. Valid panels: ${Array.from(VALID_PANELS).join(', ')}`);
  }

  const panels = {};
  const textParts = [];

  const header = [
    'ECC Skill Health Dashboard',
    `Generated: ${now}`,
    `Skills: ${summary.total_skills} total, ${summary.healthy_skills} healthy, ${summary.declining_skills} declining`,
    '',
  ];

  textParts.push(header.join('\n'));

  if (selectedPanel) {
    const result = panelRenderers[selectedPanel]();
    panels[selectedPanel] = result.data;
    textParts.push(result.text);

View on GitHub (pinned to 8321021c54)