Imported from rahulgawale/fandryui (
AGENTS.md). Install upstream withnpx skills add rahulgawale/fandryui. Copyright stays with the author.
AGENTS.md — Fandry UI
This document defines how humans and AI agents should reason when working in this repository.
It exists to preserve architectural intent, not to optimize for speed.
If you are an AI assistant, code generator, or automated agent:
read this file first and follow it strictly.
Mental Model
Fandry UI is a single LWR OSS project with clear boundaries.
Primitives live in src/core/fandry/.
The application lives in src/modules/fandryui/.
Primitives are foundational components that:
- Must remain small, boring, and predictable
- Should not contain business logic
- Should be extensible via slots and composition
The application can be messy.
Primitives must not.
Structure Responsibilities
Primitives (src/core/fandry/)
Primitives are intentionally small.
Primitives provide:
- fandry-* components built with native LWC
- extensible structure via Base class
- slots, tokens, and contracts
- normalized semantic events
Primitives must remain:
- boring
- predictable
- opinionated
Primitives must not:
- contain business logic
- contain Salesforce-specific logic
- grow configuration surfaces casually
- use hard-coded values instead of design tokens
If you are unsure whether something belongs in core/fandry/, it probably does not.
Design Tokens (Critical)
Always use design tokens from fandry/base/tokens.css instead of hard-coded values.
This is a component library, not a production application.
✅ Good:
border-radius: var(--fd-radius-sm);
padding: var(--fd-space-2);
color: hsl(var(--fd-text));
❌ Bad:
border-radius: 4px;
padding: 8px;
color: #333;
Hard-coded values:
- Break theming
- Make maintenance difficult
- Create inconsistency
- Are not acceptable in primitives
If a token doesn't exist, add it to fandry/base/tokens.css first.
Motion
Motion is CSS-first and lives inside the component that owns it. Use the
--fd-duration-*/--fd-ease-* tokens and the keyframes in
fandry/base/motion.css; animate opacity and transforms, not layout. A
transient primitive (one that mounts/unmounts a panel) stays mounted until
its exit animation has finished, via exitFinished() from fandry/motion —
not animationend, and not a setTimeout guessing a duration. Reduced motion
is handled by the tokens themselves (they go to 0ms), so a component never
needs its own prefers-reduced-motion query. Do not add animation props,
a generic motion/transition component, or an animation dependency.
Base Class (src/core/fandry/base/)
All fandry-* primitives extend Base.
Base provides:
- shared styles via static stylesheets
- design tokens import
- consistent foundation
Base must not:
- contain component-specific logic
- grow beyond shared styling needs
Distribution (scripts/build-dist.mjs, packages/ui/bin/)
fandryui (npm, packages/ui/dist, gitignored) is generated from
src/core/fandry and src/salesforce/fandry, in two flavors: modules/fandry/*
for LWR / LWC OSS (<fandry-*>, kept as authored) and sfdx/lwc/fandry* for
Salesforce (renamed to c/fandry* / <c-fandry-*>, since on-platform code
lives in the default c namespace). Never edit generated output and never keep
a hand-maintained copy of a primitive. Author source with the fandry/*
namespace as usual. The fandry CLI (packages/ui/bin/fandry.mjs, no
dependencies) is hand-written; its dependency graph, registry.json, is
generated from what the bundles actually import and render, so a new
primitive needs no registration. On Salesforce nothing may import an npm
package: @tanstack/table-core is vendored as fandryTableCore, and the
build fails if any other package import appears.
npm run verify:npm and npm run verify:sfdx prove both platforms work.
Application (src/modules/fandryui/)
The application exists to:
- demonstrate primitives
- test behavior
- document usage
It is allowed to be messy.
Primitives are not.
Design Principles (Non-Negotiable)
1. Extensibility over completeness
We prefer:
- fewer features
- cleaner extension points
Over:
- feature-rich but rigid components
2. Composition over configuration
Prefer:
- slots
- composition
- small primitives
Avoid:
- flag-heavy APIs
- “just in case” options
- implicit behavior
Booleans are a last resort, not a first instinct. Before adding a
true/false @api property, ask whether the variation it controls
could instead be expressed as a slot, a named region, or an overridable
piece of the component. A boolean is acceptable when it toggles a
genuinely binary behavior (disabled, loading); it is not acceptable
as a substitute for letting the consumer supply their own markup.
Do not build a black box. lightning-* base components cannot be
extended, forked, or partially replaced by a consumer — that opacity is
exactly what this library exists to avoid. Every primitive should ship a
simple, ready-to-use default and a real seam for a consumer to override
or replace a piece of it without forking the whole component. Where a
component carries real internal state (not just presentation), prefer
splitting it into a template-less base class holding the state/behavior
and a thin default component that extends it with the ready-made
template — a consumer who needs different markup extends the same base
and writes their own template, instead of copying the component's
internals.
3. Outcomes over implementations
Do not copy implementations from other systems.
Instead:
- understand the outcome
- reproduce it cleanly
- delegate behavior where appropriate
4. Boring is success
If a change feels exciting:
- reconsider it
- simplify it
- or move it out of Core
Core should feel uneventful.
Event Model
- Primitives (
src/core/fandry/, single-concern components like inputs and buttons) normalize to the base semantic vocabulary only:inputchangefocusblur
- Application components (real state/behavior, like fandry-table's sorting,
pagination, selection, filtering) may introduce additional custom
events beyond that base vocabulary when the base vocabulary genuinely
can't express the domain event (e.g.
sortchange,pagechange,rowselectionchange,filterchange,rowclick) -- this is not a license to invent an event per prop; each one must earn its place the same wayinput/changedid. - Native DOM events bubble naturally
- Custom events must be semantic and well-documented
Do not proxy or re-emit DOM events unless there is a strong, documented reason.
TypeScript Guidance
TypeScript is used to:
- define public contracts
- protect refactors
- communicate intent
TypeScript must not be used to:
- create clever abstractions
- encode business logic
- hide architectural mistakes
If a type is hard to read, it is wrong.
How to Add a New Primitive
Before adding a new fandry-* component:
- Verify it is a primitive, not a solution
- Ensure it extends Base class from fandry/base
- Ensure it can be extended via slots
- Keep API surface minimal
- Normalize only semantic events
- Use design tokens from fandry/styles
- Import Base using namespace:
import Base from 'fandry/base'
If these conditions are not met, the component does not belong in core/fandry/.
How to Think About “AI Can Generate This”
If a component is:
- trivial to generate
- trivial to replace
- trivial to fork
Then it still needs:
- correct boundaries
- stable contracts
- predictable behavior
Generation does not remove the need for design.
Agent Behavior Rules
If you are an AI agent:
- Do not introduce new primitive APIs casually
- Do not add dependencies without justification
- Do not refactor structure unless explicitly asked
- Do not optimize prematurely
- Do not break primitive boundaries for convenience
- Always use namespace imports for components:
import Base from 'fandry/base' - Always use npm (not pnpm) for this project
If instructions conflict, preserve architecture over task completion.
Final Note
This repository optimizes for:
- longevity
- clarity
- discipline
Not for:
- velocity
- novelty
- trend alignment
If you follow these constraints, your contributions will fit naturally.
If you ignore them, the system will degrade quietly.
That outcome is unacceptable.