3059c6cc77
Co-authored-by: Copilot <copilot@github.com>
526 lines
15 KiB
Markdown
526 lines
15 KiB
Markdown
# Component API Smells
|
|
|
|
This document catalogs potential complaints about the current component/template API.
|
|
|
|
Each item includes:
|
|
|
|
- Problem
|
|
- Why it hurts
|
|
- Potential solutions
|
|
- Consequences/costs
|
|
|
|
## 1) Magic Strings for Variants, Sizes, Types, Directions
|
|
|
|
Problem:
|
|
- Many components use raw strings for semantic options (`variant`, `size`, `type`, `direction`, `align`, etc.).
|
|
- Invalid values often silently fall back to defaults.
|
|
|
|
Why it hurts:
|
|
- No compile-time safety.
|
|
- Typos are easy to miss.
|
|
- Weak IntelliSense discoverability.
|
|
|
|
Potential solutions:
|
|
- Replace string options with enums for common semantic domains.
|
|
- Generate constants classes per component for non-breaking intermediate step.
|
|
- Add analyzers that validate allowed literals where strings are retained.
|
|
|
|
Consequences/costs:
|
|
- Enums can be breaking if public signatures change.
|
|
- Constants are low cost but do not fully prevent invalid values.
|
|
- Analyzer route adds tooling complexity.
|
|
|
|
## 2) Inconsistent Styling Extensibility (`extraClasses` and wrappers)
|
|
|
|
Problem:
|
|
- Some components have `extraClasses`; others do not.
|
|
- Developers often wrap components in outer `div` just to apply layout/styling.
|
|
|
|
Why it hurts:
|
|
- No consistent mental model.
|
|
- Extra wrapper markup increases noise and nesting depth.
|
|
|
|
Potential solutions:
|
|
- Add a standard `className` (or `extraClasses`) parameter to every component.
|
|
- Support class merging utility behavior in a shared helper.
|
|
|
|
Consequences/costs:
|
|
- Public constructor expansion across many components.
|
|
- Need policy for precedence (base classes first vs custom classes first).
|
|
|
|
## 3) Missing Uniform Attribute Pass-Through
|
|
|
|
Problem:
|
|
- Attribute extensibility is fragmented (`hxAttrs` in some places, none in others).
|
|
- No first-class support for arbitrary `aria-*`, `data-*`, test IDs, analytics attributes.
|
|
|
|
Why it hurts:
|
|
- Manual string composition is error-prone.
|
|
- Difficult accessibility and testing instrumentation.
|
|
|
|
Potential solutions:
|
|
- Add a shared attributes bag type (`IReadOnlyDictionary<string, string?>`).
|
|
- Keep `hxAttrs` temporarily as compatibility shim.
|
|
|
|
Consequences/costs:
|
|
- Larger refactor surface.
|
|
- Slight allocation/processing overhead.
|
|
- Requires HTML attribute encoding rules in one central place.
|
|
|
|
## 4) `hxAttrs` Raw String Footgun
|
|
|
|
Problem:
|
|
- Raw attribute strings allow malformed markup or accidental injection.
|
|
|
|
Why it hurts:
|
|
- Hard-to-debug render bugs.
|
|
- Security posture depends on each caller doing manual encoding correctly.
|
|
|
|
Potential solutions:
|
|
- Deprecate raw `hxAttrs` in favor of typed/structured attrs.
|
|
- Provide safe helper methods to construct HTMX attribute sets.
|
|
|
|
Consequences/costs:
|
|
- Migration needed for existing call sites.
|
|
- Potentially breaking unless a gradual fallback is kept.
|
|
|
|
## 5) Unsafe-by-Default Raw HTML Content Paths
|
|
|
|
Problem:
|
|
- Several components accept string content that is rendered as raw HTML.
|
|
- Caller must remember to encode user-provided values.
|
|
|
|
Why it hurts:
|
|
- XSS risk in real application code.
|
|
- Easy to misuse when moving quickly.
|
|
|
|
Potential solutions:
|
|
- Safe-by-default encoding for plain string inputs.
|
|
- Separate APIs for encoded text vs trusted HTML (explicit escape hatch).
|
|
- Introduce a `SafeHtml` wrapper type for intentional raw HTML.
|
|
|
|
Consequences/costs:
|
|
- Safe-by-default may break current behavior for callers relying on raw HTML.
|
|
- Trusted-HTML API adds conceptual complexity, but clearer intent.
|
|
|
|
## 6) Inconsistent Security Guidance in Component Docs
|
|
|
|
Problem:
|
|
- Some docs mention encoding; others do not provide clear warnings.
|
|
|
|
Why it hurts:
|
|
- Security correctness relies on tribal knowledge.
|
|
|
|
Potential solutions:
|
|
- Add a standardized "Security" section to every component doc.
|
|
- Include explicit examples: safe input, unsafe input, and fix.
|
|
|
|
Consequences/costs:
|
|
- Documentation maintenance overhead.
|
|
- Strong DX/security benefit for low implementation cost.
|
|
|
|
## 7) No Explicit "Component Props" Model in Markup
|
|
|
|
Problem:
|
|
- Slots replace placeholders, but there is no direct concept of passing typed props in `.htmx` markup itself.
|
|
- Dynamic behavior is mostly constructor-centric in `.htmx.cs`.
|
|
|
|
Why it hurts:
|
|
- Feels unlike modern component systems where props are explicit and local.
|
|
- New users may expect inline component parameterization and be surprised.
|
|
|
|
Potential solutions:
|
|
- Document this limitation clearly as a design constraint.
|
|
- Add a generated props record convention per component/page.
|
|
- Explore optional parameterized slot syntax in generator (long-term).
|
|
|
|
Consequences/costs:
|
|
- Props model requires generator design changes.
|
|
- Parameterized slot syntax is high complexity and may conflict with AOT simplicity.
|
|
|
|
## 8) Constructor Bloat and Low Readability
|
|
|
|
Problem:
|
|
- Some components expose many optional parameters, often multiple strings.
|
|
|
|
Why it hurts:
|
|
- Ambiguous calls and poor self-documentation.
|
|
- Easy to mis-order arguments.
|
|
|
|
Potential solutions:
|
|
- Favor required args + options record pattern.
|
|
- Add fluent builders for complex components.
|
|
|
|
Consequences/costs:
|
|
- Options records improve readability but introduce extra types.
|
|
- Builders can increase allocations and complexity.
|
|
|
|
## 9) Tuple-Based APIs for Complex Components
|
|
|
|
Problem:
|
|
- Components like tabs/accordion/dropdown/table rely on tuple collections.
|
|
|
|
Why it hurts:
|
|
- Tuples are easy to misuse and harder to read than named objects.
|
|
- Harder to evolve APIs without breaking all call sites.
|
|
|
|
Potential solutions:
|
|
- Replace tuple parameters with named records (`TabItem`, `AccordionItem`, etc.).
|
|
|
|
Consequences/costs:
|
|
- Migration churn across existing usage.
|
|
- Clear long-term maintainability win.
|
|
|
|
## 10) Missing Validation Feedback for Invalid Inputs
|
|
|
|
Problem:
|
|
- Invalid option values often degrade silently rather than failing fast.
|
|
|
|
Why it hurts:
|
|
- Bugs are hidden and discovered late.
|
|
|
|
Potential solutions:
|
|
- Add debug-time validation with clear exceptions/messages.
|
|
- Optionally emit logs/diagnostics in production with safe defaults.
|
|
|
|
Consequences/costs:
|
|
- Strict runtime validation can be breaking for existing invalid usages.
|
|
- Diagnostics-only mode is safer for migration.
|
|
|
|
## 11) Inconsistent Boolean Option Naming
|
|
|
|
Problem:
|
|
- Different components use varying naming styles for booleans and toggles.
|
|
|
|
Why it hurts:
|
|
- Low API predictability.
|
|
|
|
Potential solutions:
|
|
- Define naming conventions (`isX`, `hasX`, `enableX`) and enforce globally.
|
|
|
|
Consequences/costs:
|
|
- Rename churn if normalized retroactively.
|
|
|
|
## 12) CSS Contract Coupled to JS via Hidden Class Names
|
|
|
|
Problem:
|
|
- Interactive behavior relies on specific class/data selectors that are effectively API contracts.
|
|
|
|
Why it hurts:
|
|
- Refactoring classes can break behavior.
|
|
- Coupling is not obvious from constructor APIs.
|
|
|
|
Potential solutions:
|
|
- Document required selectors/events in each interactive component doc.
|
|
- Prefer stable `data-component`/`data-role` markers over purely visual class names.
|
|
|
|
Consequences/costs:
|
|
- Markup updates across components and JS.
|
|
- Better long-term resilience to style refactors.
|
|
|
|
## 13) Runtime Behavior Dependencies Not Surfaced in API
|
|
|
|
Problem:
|
|
- Components requiring JS initialization do not expose that requirement in code signatures.
|
|
|
|
Why it hurts:
|
|
- Silent "renders but does not work" failures.
|
|
|
|
Potential solutions:
|
|
- Add `Requires JavaScript` section in docs and XML comments.
|
|
- Add lightweight marker interface or metadata attribute for interactive components.
|
|
|
|
Consequences/costs:
|
|
- Minimal runtime cost; mostly documentation/tooling work.
|
|
|
|
## 14) Accessibility Ergonomics Gaps
|
|
|
|
Problem:
|
|
- No consistent way to pass `aria-*`, `id`, `for`, `describedby` across all components.
|
|
|
|
Why it hurts:
|
|
- Accessibility quality depends on manual wrapper hacks.
|
|
|
|
Potential solutions:
|
|
- Introduce shared accessible options type.
|
|
- Provide defaults and enforce required labels where relevant.
|
|
|
|
Consequences/costs:
|
|
- Constructor changes and additional validation logic.
|
|
|
|
## 15) Testability Friction (No Standard Test IDs)
|
|
|
|
Problem:
|
|
- No consistent `data-testid` or attribute pass-through strategy.
|
|
|
|
Why it hurts:
|
|
- E2E selectors become brittle (class/text-based selectors).
|
|
|
|
Potential solutions:
|
|
- Add standard attributes bag and testing guidance.
|
|
- Add `testId` convenience parameter in interactive/form primitives.
|
|
|
|
Consequences/costs:
|
|
- Minor API surface increase.
|
|
- Significant test stability benefit.
|
|
|
|
## 16) Documentation Discoverability Gaps
|
|
|
|
Problem:
|
|
- Component docs focus on usage but under-emphasize known limitations and smell areas.
|
|
|
|
Why it hurts:
|
|
- New contributors re-learn the same constraints repeatedly.
|
|
|
|
Potential solutions:
|
|
- Add dedicated docs for limitations, anti-patterns, and migration strategy.
|
|
- Add an index from component reference into Issues docs.
|
|
|
|
Consequences/costs:
|
|
- Ongoing documentation upkeep.
|
|
|
|
## 17) Inconsistent Naming (`extraClasses` vs alternatives)
|
|
|
|
Problem:
|
|
- Similar concepts have inconsistent parameter names.
|
|
|
|
Why it hurts:
|
|
- Context switching overhead.
|
|
|
|
Potential solutions:
|
|
- Standardize naming dictionary and enforce in reviews.
|
|
- Offer temporary backward-compatible aliases.
|
|
|
|
Consequences/costs:
|
|
- Alias support increases short-term complexity.
|
|
|
|
## 18) Lack of Strongly-Typed Domain Primitives
|
|
|
|
Problem:
|
|
- IDs, route paths, CSS classes, and labels are all plain strings.
|
|
|
|
Why it hurts:
|
|
- Accidental parameter swaps and weak intent signaling.
|
|
|
|
Potential solutions:
|
|
- Introduce lightweight value objects or records for high-value domains (`DialogId`, `CssClassList`, etc.).
|
|
|
|
Consequences/costs:
|
|
- Added type count and conversion code.
|
|
- Better readability and safer APIs.
|
|
|
|
## 19) Missing Centralized Class Composition Policy
|
|
|
|
Problem:
|
|
- Tailwind class strings are composed ad-hoc in constructors.
|
|
|
|
Why it hurts:
|
|
- Risk of duplicate/conflicting classes.
|
|
- Hard to audit variant behavior consistency.
|
|
|
|
Potential solutions:
|
|
- Add shared class composition helper utilities.
|
|
- Optionally adopt a deterministic merge utility pattern.
|
|
|
|
Consequences/costs:
|
|
- New utility dependency or internal helper maintenance.
|
|
|
|
## 20) Limited Error Reporting for Misconfigured Interactive Components
|
|
|
|
Problem:
|
|
- Missing/incorrect JS hooks often fail quietly.
|
|
|
|
Why it hurts:
|
|
- Time-consuming debugging.
|
|
|
|
Potential solutions:
|
|
- Development-only console warnings/assertions from `components.js` when expected markers are missing.
|
|
|
|
Consequences/costs:
|
|
- Slight JS complexity increase.
|
|
- Better troubleshooting experience.
|
|
|
|
## 21) Form Component API Inconsistency
|
|
|
|
Problem:
|
|
- Form primitives vary in how they accept value/default/checked/attrs/labels.
|
|
|
|
Why it hurts:
|
|
- Hard to predict usage patterns across components.
|
|
|
|
Potential solutions:
|
|
- Define a shared form control contract:
|
|
- `name`, `id`, `label`, `value`, `disabled`, `required`, `className`, `attributes`
|
|
|
|
Consequences/costs:
|
|
- Widespread API harmonization work.
|
|
- Major usability win once stabilized.
|
|
|
|
## 22) No First-Class Validation/Error State Patterns
|
|
|
|
Problem:
|
|
- Error display, invalid styling, and message linkage are largely ad-hoc.
|
|
|
|
Why it hurts:
|
|
- Inconsistent UX and accessibility for validation states.
|
|
|
|
Potential solutions:
|
|
- Add canonical form-field wrapper component and error semantics.
|
|
- Add helper patterns in docs for mapping server validation to components.
|
|
|
|
Consequences/costs:
|
|
- Additional abstractions and migration.
|
|
|
|
## 23) Table API Lacks Strong Cell/Column Models
|
|
|
|
Problem:
|
|
- Table inputs as nested strings are simplistic and rigid.
|
|
|
|
Why it hurts:
|
|
- Hard to represent links, badges, actions, and per-cell semantics safely.
|
|
|
|
Potential solutions:
|
|
- Introduce column and row models with typed cell renderers.
|
|
- Support text cell vs trusted HTML cell explicit APIs.
|
|
|
|
Consequences/costs:
|
|
- Significant redesign effort for table API.
|
|
- High payoff for real-world usage.
|
|
|
|
## 24) Potential Over-Eager Precomputation in Constructors
|
|
|
|
Problem:
|
|
- Some components precompute heavy HTML payloads in constructor.
|
|
|
|
Why it hurts:
|
|
- Allocation spikes for large datasets.
|
|
- Can surprise developers expecting render-time streaming.
|
|
|
|
Potential solutions:
|
|
- Lazy compute/cache expensive sections.
|
|
- Document performance profile and guardrails per component.
|
|
|
|
Consequences/costs:
|
|
- Possible complexity in caching invalidation.
|
|
- Better performance transparency.
|
|
|
|
## 25) No Compatibility Policy for API Evolution
|
|
|
|
Problem:
|
|
- No explicit deprecation policy for parameter renames or behavior changes.
|
|
|
|
Why it hurts:
|
|
- Contributors hesitate to improve APIs due to break risk.
|
|
|
|
Potential solutions:
|
|
- Define semver/deprecation policy in docs.
|
|
- Use staged migration with obsolete annotations.
|
|
|
|
Consequences/costs:
|
|
- Process overhead, but critical for long-term maintainability.
|
|
|
|
## 26) No Unified Component Design Principles Doc
|
|
|
|
Problem:
|
|
- Patterns are documented, but not as enforceable design principles.
|
|
|
|
Why it hurts:
|
|
- New components may diverge in API style.
|
|
|
|
Potential solutions:
|
|
- Publish a component API style guide with mandatory rules and preferred patterns.
|
|
|
|
Consequences/costs:
|
|
- Requires reviewer discipline.
|
|
|
|
## 27) Internationalization (i18n) Boundaries Not Explicit
|
|
|
|
Problem:
|
|
- Many labels/content are plain strings without explicit localization guidance.
|
|
|
|
Why it hurts:
|
|
- Inconsistent localization strategy across pages/components.
|
|
|
|
Potential solutions:
|
|
- Add docs for localizable boundaries and resource integration patterns.
|
|
|
|
Consequences/costs:
|
|
- Documentation and integration work.
|
|
|
|
## 28) Missing "Known Limitations" Section in Component Reference Entry Point
|
|
|
|
Problem:
|
|
- The main component reference does not prominently call out systemic limitations.
|
|
|
|
Why it hurts:
|
|
- Developers discover constraints by trial and error.
|
|
|
|
Potential solutions:
|
|
- Add up-front limitations and issue tracker links in component reference.
|
|
|
|
Consequences/costs:
|
|
- Low cost; immediate discoverability gains.
|
|
|
|
## 29) API Surface Differs Across Similar Components
|
|
|
|
Problem:
|
|
- Similar categories (display/form/interactive) do not expose comparable extension points.
|
|
|
|
Why it hurts:
|
|
- Surprising differences force re-learning per component.
|
|
|
|
Potential solutions:
|
|
- Define a baseline component contract by category:
|
|
- Display: `className`, `attributes`
|
|
- Form: baseline form control props + attrs
|
|
- Interactive: baseline + JS contract notes
|
|
|
|
Consequences/costs:
|
|
- Requires systematic API audit and staged rollout.
|
|
|
|
## 30) Missing Tooling Support for API Misuse
|
|
|
|
Problem:
|
|
- No analyzers/code fixes for common mistakes (invalid variant, unsafe content, missing encoding).
|
|
|
|
Why it hurts:
|
|
- Review burden remains manual.
|
|
|
|
Potential solutions:
|
|
- Introduce Roslyn analyzers for:
|
|
- magic string validation
|
|
- unsafe raw HTML from untrusted sources
|
|
- missing serialization registration patterns
|
|
|
|
Consequences/costs:
|
|
- Initial tooling investment is medium-high.
|
|
- Scales quality across the codebase after adoption.
|
|
|
|
---
|
|
|
|
## Cross-Cutting Improvement Patterns
|
|
|
|
1. Standardized base options record:
|
|
- `className`
|
|
- `attributes`
|
|
- `testId`
|
|
- `ariaLabel`
|
|
|
|
2. Strong typing for semantic options:
|
|
- enums/constants/analyzers
|
|
|
|
3. Safe content model:
|
|
- text-safe by default, explicit trusted-html escape hatch
|
|
|
|
4. Better docs contract:
|
|
- every component doc should include:
|
|
- security notes
|
|
- accessibility notes
|
|
- JS dependency notes (if interactive)
|
|
- extension points
|
|
|
|
5. Migration strategy:
|
|
- additive changes first
|
|
- obsolete old params
|
|
- remove deprecated paths in major version bump
|