Skip to content

feat: add styling_preflight single-call validation tool#163

Merged
himerus merged 2 commits intodevfrom
feature/styling-preflight
Mar 21, 2026
Merged

feat: add styling_preflight single-call validation tool#163
himerus merged 2 commits intodevfrom
feature/styling-preflight

Conversation

@himerus
Copy link
Contributor

@himerus himerus commented Mar 21, 2026

Summary

  • New styling_preflight MCP tool — one call that returns everything an agent needs
  • Combines: component API discovery (parts, tokens, slots), CSS reference resolution (valid vs hallucinated), Shadow DOM validation, theme compatibility checking, correct CSS snippet, and pass/fail verdict
  • Eliminates the "agent forgot to check the API before writing CSS" failure mode

Why this matters

Trip-planner agents were hallucinating part names and token names because they skipped calling get_component_quick_ref before writing CSS. Now they call styling_preflight once and get the full picture: what the component supports, what's wrong with their CSS, and a correct snippet to reference.

Test plan

  • 16 tests covering result shape, validation integration, HTML validation, bare components, verdict generation
  • TypeScript compiles clean
  • ESLint passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Introduced a styling validation tool that performs comprehensive CSS analysis, checking for shadow DOM anti-patterns, invalid CSS references, theme compatibility issues, and component API alignment mismatches. Returns detailed problem reports with correction suggestions and a pass/fail verdict.

Combines component API discovery, CSS reference resolution, Shadow DOM
validation, and theme checking into one call. Returns parts/tokens/slots
API surface, valid/invalid status for all references, issues, correct
CSS snippet, and pass/fail verdict. 16 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 21, 2026

Warning

Rate limit exceeded

@himerus has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 17 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 05b8c3fe-fbb9-4c12-a904-5c060d61c632

📥 Commits

Reviewing files that changed from the base of the PR and between 685eee2 and ec1b8a9.

📒 Files selected for processing (1)
  • tests/integration/server.test.ts

Walkthrough

A new styling_preflight MCP tool is introduced to perform single-call styling validation. The implementation adds a handler module that accepts CSS, optional HTML, and component metadata, resolving CSS APIs and checking for shadow DOM anti-patterns and theme compatibility issues, then returns a unified verdict with resolved issues and component API summary.

Changes

Cohort / File(s) Summary
Tool Definition & Integration
packages/core/src/tools/styling.ts, packages/core/src/handlers/styling-preflight.ts
Added new styling_preflight tool definition and handler that validates CSS input by resolving API references, checking shadow DOM usage, validating theme compatibility, and returning aggregated issues with a pass/fail verdict.
Tests & Metadata
tests/handlers/styling-preflight.test.ts, .changeset/styling-preflight.md
Added comprehensive test suite covering valid/invalid CSS patterns, shadow DOM anti-patterns, theme property validation, slot validation, and verdict generation; added changeset entry marking minor version bump.

Sequence Diagram

sequenceDiagram
    participant Client as MCP Client
    participant Tool as handleStylingCall
    participant Handler as runStylingPreflight
    participant CssApi as resolveCssApi
    participant ShadowDom as checkShadowDomUsage
    participant Theme as checkThemeCompatibility
    participant Verdict as buildVerdict

    Client->>Tool: styling_preflight request<br/>(cssText, tagName, htmlText)
    Tool->>Handler: runStylingPreflight({css, html, meta})
    Handler->>CssApi: resolveCssApi(css, metadata)
    CssApi-->>Handler: CssApiResolution
    Handler->>ShadowDom: checkShadowDomUsage(css, html)
    ShadowDom-->>Handler: shadow DOM issues
    Handler->>Theme: checkThemeCompatibility(css, metadata)
    Theme-->>Handler: theme issues
    Handler->>Verdict: buildVerdict(errorCount, warningCount, invalidRefs)
    Verdict-->>Handler: verdict string
    Handler-->>Tool: PreflightResult{<br/>componentApi,<br/>resolution,<br/>issues,<br/>correctSnippet,<br/>verdict}
    Tool-->>Client: MCP success response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the purpose and motivation clearly but does not follow the repository's required template structure with explicit checkboxes for type of change, related issue, and completion checklist. Restructure the description to match the template: add 'Type of change' section with checkboxes, 'Related issue' field, 'Tests added' section with checkboxes, and a 'Checklist' section with all required build/test steps.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add styling_preflight single-call validation tool' directly and clearly describes the main change: introducing a new styling_preflight tool for single-call validation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/styling-preflight

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/handlers/styling-preflight.ts`:
- Around line 65-93: The two empty catch blocks around checkShadowDomUsage and
checkThemeCompatibility are swallowing exceptions and allowing an incomplete
analysis to return a clean preflight; change each catch to capture the error
(catch (err)) and either rethrow or—preferably—append a new error issue to the
same issues array (use severity: 'error', category: 'shadowDom' or
'themeCompat', message: err.message or String(err), and optional suggestion) and
mark the overall result as failed (e.g., set the preflight status variable or
flag used by the function); update the catch bodies in the blocks surrounding
checkShadowDomUsage(...) and checkThemeCompatibility(...) and ensure the calling
code respects the failure flag or thrown error.
- Around line 141-143: The verdict text currently lumps all warnings as
"theme/dark mode risk"; inspect the collected issues (variable issues) and
compute counts by type (e.g., theme/dark-mode issues vs shadowDom/selector
issues) and then push an appropriate summary string into parts (instead of the
single generic message)—for example pluralized messages for theme/dark-mode
risks and separate messages for Shadow DOM or selector warnings; update the
logic that builds parts (the block using warnings and parts) to check issue.kind
or the specific fields that identify shadowDom/selector items and emit accurate
labels for each category.

In `@packages/core/src/tools/styling.ts`:
- Around line 1013-1018: The test suite currently calls runStylingPreflight()
directly and misses exercising the public MCP entry, so add one integration test
that invokes handleStylingCall() with a simulated call for the
'styling_preflight' tool: construct args matching StylingPreflightArgsSchema
(cssText, tagName, htmlText), pass the same cem value used in production, and
assert the returned value is the wrapped success response (created via
createSuccessResponse) containing the JSON-stringified runStylingPreflight
result; this ensures tool registration, argument parsing
(StylingPreflightArgsSchema.parse), and response wrapping are validated
end-to-end rather than only testing runStylingPreflight in isolation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a4b36466-bca3-4f99-81a2-58cd48113edc

📥 Commits

Reviewing files that changed from the base of the PR and between 09b9a98 and 685eee2.

📒 Files selected for processing (4)
  • .changeset/styling-preflight.md
  • packages/core/src/handlers/styling-preflight.ts
  • packages/core/src/tools/styling.ts
  • tests/handlers/styling-preflight.test.ts

Comment on lines +65 to +93
try {
const shadowResult = checkShadowDomUsage(css, meta.tagName, meta);
for (const issue of shadowResult.issues) {
issues.push({
severity: issue.severity === 'error' ? 'error' : 'warning',
category: 'shadowDom',
message: issue.message,
line: issue.line,
suggestion: issue.suggestion,
});
}
} catch {
// Shadow DOM check failed — skip
}

// 3. Run theme compatibility check
try {
const themeResult = checkThemeCompatibility(css);
for (const issue of themeResult.issues) {
issues.push({
severity: 'warning',
category: 'themeCompat',
message: issue.message,
line: issue.line,
});
}
} catch {
// Theme check failed — skip
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not return a clean preflight when a sub-check crashes.

Both catch blocks suppress validator failures and continue. If either checker throws on an unexpected input shape, this function can still return pass even though the analysis was incomplete.

⚠️ Proposed fix
-    } catch {
-      // Shadow DOM check failed — skip
+    } catch {
+      issues.push({
+        severity: 'warning',
+        category: 'internal',
+        message: 'Shadow DOM validation could not be completed; preflight result is incomplete.',
+      });
     }
@@
-    } catch {
-      // Theme check failed — skip
+    } catch {
+      issues.push({
+        severity: 'warning',
+        category: 'internal',
+        message: 'Theme compatibility validation could not be completed; preflight result is incomplete.',
+      });
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const shadowResult = checkShadowDomUsage(css, meta.tagName, meta);
for (const issue of shadowResult.issues) {
issues.push({
severity: issue.severity === 'error' ? 'error' : 'warning',
category: 'shadowDom',
message: issue.message,
line: issue.line,
suggestion: issue.suggestion,
});
}
} catch {
// Shadow DOM check failed — skip
}
// 3. Run theme compatibility check
try {
const themeResult = checkThemeCompatibility(css);
for (const issue of themeResult.issues) {
issues.push({
severity: 'warning',
category: 'themeCompat',
message: issue.message,
line: issue.line,
});
}
} catch {
// Theme check failed — skip
}
try {
const shadowResult = checkShadowDomUsage(css, meta.tagName, meta);
for (const issue of shadowResult.issues) {
issues.push({
severity: issue.severity === 'error' ? 'error' : 'warning',
category: 'shadowDom',
message: issue.message,
line: issue.line,
suggestion: issue.suggestion,
});
}
} catch {
issues.push({
severity: 'warning',
category: 'internal',
message: 'Shadow DOM validation could not be completed; preflight result is incomplete.',
});
}
// 3. Run theme compatibility check
try {
const themeResult = checkThemeCompatibility(css);
for (const issue of themeResult.issues) {
issues.push({
severity: 'warning',
category: 'themeCompat',
message: issue.message,
line: issue.line,
});
}
} catch {
issues.push({
severity: 'warning',
category: 'internal',
message: 'Theme compatibility validation could not be completed; preflight result is incomplete.',
});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/handlers/styling-preflight.ts` around lines 65 - 93, The
two empty catch blocks around checkShadowDomUsage and checkThemeCompatibility
are swallowing exceptions and allowing an incomplete analysis to return a clean
preflight; change each catch to capture the error (catch (err)) and either
rethrow or—preferably—append a new error issue to the same issues array (use
severity: 'error', category: 'shadowDom' or 'themeCompat', message: err.message
or String(err), and optional suggestion) and mark the overall result as failed
(e.g., set the preflight status variable or flag used by the function); update
the catch bodies in the blocks surrounding checkShadowDomUsage(...) and
checkThemeCompatibility(...) and ensure the calling code respects the failure
flag or thrown error.

Comment on lines +141 to +143
if (warnings > 0) {
parts.push(`${warnings} warning${warnings > 1 ? 's' : ''} (theme/dark mode risk)`);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The verdict text is mislabeling non-theme warnings.

issues can contain shadowDom warnings, but the summary always describes every warning as a theme/dark-mode risk. That makes the returned verdict inaccurate for selector and Shadow DOM findings.

📝 Proposed fix
   if (warnings > 0) {
-    parts.push(`${warnings} warning${warnings > 1 ? 's' : ''} (theme/dark mode risk)`);
+    parts.push(`${warnings} warning${warnings > 1 ? 's' : ''}`);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (warnings > 0) {
parts.push(`${warnings} warning${warnings > 1 ? 's' : ''} (theme/dark mode risk)`);
}
if (warnings > 0) {
parts.push(`${warnings} warning${warnings > 1 ? 's' : ''}`);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/handlers/styling-preflight.ts` around lines 141 - 143, The
verdict text currently lumps all warnings as "theme/dark mode risk"; inspect the
collected issues (variable issues) and compute counts by type (e.g.,
theme/dark-mode issues vs shadowDom/selector issues) and then push an
appropriate summary string into parts (instead of the single generic
message)—for example pluralized messages for theme/dark-mode risks and separate
messages for Shadow DOM or selector warnings; update the logic that builds parts
(the block using warnings and parts) to check issue.kind or the specific fields
that identify shadowDom/selector items and emit accurate labels for each
category.

Comment on lines +1013 to +1018
if (name === 'styling_preflight') {
const { cssText, tagName, htmlText } = StylingPreflightArgsSchema.parse(args);
const meta = parseCem(tagName, cem);
const result = runStylingPreflight({ css: cssText, html: htmlText, meta });
return createSuccessResponse(JSON.stringify(result, null, 2));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add one integration test through handleStylingCall().

The new suite only calls runStylingPreflight() directly. A regression in tool registration, argument parsing, or response wrapping here would still pass CI even though the public MCP tool is broken.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/tools/styling.ts` around lines 1013 - 1018, The test suite
currently calls runStylingPreflight() directly and misses exercising the public
MCP entry, so add one integration test that invokes handleStylingCall() with a
simulated call for the 'styling_preflight' tool: construct args matching
StylingPreflightArgsSchema (cssText, tagName, htmlText), pass the same cem value
used in production, and assert the returned value is the wrapped success
response (created via createSuccessResponse) containing the JSON-stringified
runStylingPreflight result; this ensures tool registration, argument parsing
(StylingPreflightArgsSchema.parse), and response wrapping are validated
end-to-end rather than only testing runStylingPreflight in isolation.

Adds resolve_css_api and styling_preflight to the
expected tools list (68 core + 2 token).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@himerus himerus merged commit a19bf71 into dev Mar 21, 2026
8 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant