Add workflow infrastructure — ESLint, Prettier, Husky, Vitest, 7 new skills

Phase 1: Session log archived (1096→91 lines), D031 token access convention
Phase 2: ESLint v9 + Prettier + jsx-a11y, initial config and lint fixes
Phase 3: 7 new skills (polish, harden, normalize, clarify, typeset, quieter, adapt)
         + Vercel reference docs, updated audit/review-component refs
Phase 4: Husky + lint-staged pre-commit hooks, preflight updated to 8 checks
Phase 5: Vitest + Testing Library + /write-tests skill

- Badge.tsx colour maps unified to CSS variables (D031)
- 5 empty interface→type alias fixes (Switch, Radio, Divider, IconButton, Link)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 16:41:57 +11:00
parent c5bfeaee2f
commit aa7cdeecf0
33 changed files with 7685 additions and 1088 deletions

View File

@@ -24,37 +24,25 @@ export interface BadgeProps extends Omit<BoxProps, 'color'> {
// ─── Colour maps ─────────────────────────────────────────────────────────────
const filledColors: Record<BadgeColor, (t: Theme) => { bg: string; text: string }> = {
default: (t) => ({ bg: t.palette.grey[700], text: t.palette.common.white }),
brand: (t) => ({ bg: t.palette.primary.main, text: t.palette.common.white }),
success: (t) => ({ bg: t.palette.success.main, text: t.palette.common.white }),
warning: (t) => ({ bg: t.palette.warning.main, text: t.palette.common.white }),
error: (t) => ({ bg: t.palette.error.main, text: t.palette.common.white }),
info: (t) => ({ bg: t.palette.info.main, text: t.palette.common.white }),
const filledColors: Record<BadgeColor, { bg: string; text: string }> = {
default: { bg: 'var(--fa-color-neutral-700)', text: 'var(--fa-color-white)' },
brand: { bg: 'var(--fa-color-interactive-default)', text: 'var(--fa-color-white)' },
success: { bg: 'var(--fa-color-feedback-success)', text: 'var(--fa-color-white)' },
warning: { bg: 'var(--fa-color-feedback-warning)', text: 'var(--fa-color-white)' },
error: { bg: 'var(--fa-color-feedback-error)', text: 'var(--fa-color-white)' },
info: { bg: 'var(--fa-color-feedback-info)', text: 'var(--fa-color-white)' },
};
const softColors: Record<BadgeColor, (t: Theme) => { bg: string; text: string }> = {
default: (t) => ({ bg: t.palette.grey[200], text: t.palette.grey[700] }),
brand: () => ({
bg: 'var(--fa-color-brand-200)',
text: 'var(--fa-color-brand-700)',
}),
success: () => ({
const softColors: Record<BadgeColor, { bg: string; text: string }> = {
default: { bg: 'var(--fa-color-neutral-200)', text: 'var(--fa-color-neutral-700)' },
brand: { bg: 'var(--fa-color-brand-200)', text: 'var(--fa-color-brand-700)' },
success: {
bg: 'var(--fa-color-feedback-success-subtle)',
text: 'var(--fa-color-feedback-success)',
}),
warning: () => ({
bg: 'var(--fa-color-feedback-warning-subtle)',
text: 'var(--fa-color-text-warning)',
}),
error: () => ({
bg: 'var(--fa-color-feedback-error-subtle)',
text: 'var(--fa-color-feedback-error)',
}),
info: () => ({
bg: 'var(--fa-color-feedback-info-subtle)',
text: 'var(--fa-color-feedback-info)',
}),
},
warning: { bg: 'var(--fa-color-feedback-warning-subtle)', text: 'var(--fa-color-text-warning)' },
error: { bg: 'var(--fa-color-feedback-error-subtle)', text: 'var(--fa-color-feedback-error)' },
info: { bg: 'var(--fa-color-feedback-info-subtle)', text: 'var(--fa-color-feedback-info)' },
};
// ─── Component ───────────────────────────────────────────────────────────────
@@ -82,18 +70,7 @@ const softColors: Record<BadgeColor, (t: Theme) => { bg: string; text: string }>
* provide an `aria-label` prop so screen readers can announce the status.
*/
export const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
(
{
color = 'default',
variant = 'soft',
size = 'medium',
icon,
children,
sx,
...props
},
ref,
) => {
({ color = 'default', variant = 'soft', size = 'medium', icon, children, sx, ...props }, ref) => {
const sizeMap = {
small: {
height: 'var(--fa-badge-height-sm)',
@@ -123,9 +100,7 @@ export const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
component="span"
sx={[
(theme: Theme) => {
const colors = variant === 'filled'
? filledColors[color](theme)
: softColors[color](theme);
const colors = variant === 'filled' ? filledColors[color] : softColors[color];
return {
display: 'inline-flex',

View File

@@ -5,7 +5,7 @@ import type { DividerProps as MuiDividerProps } from '@mui/material/Divider';
// ─── Types ───────────────────────────────────────────────────────────────────
/** Props for the FA Divider component */
export interface DividerProps extends MuiDividerProps {}
export type DividerProps = MuiDividerProps;
// ─── Component ───────────────────────────────────────────────────────────────
@@ -31,11 +31,9 @@ export interface DividerProps extends MuiDividerProps {}
* <Divider variant="inset" />
* ```
*/
export const Divider = React.forwardRef<HTMLHRElement, DividerProps>(
(props, ref) => {
return <MuiDivider ref={ref} {...props} />;
},
);
export const Divider = React.forwardRef<HTMLHRElement, DividerProps>((props, ref) => {
return <MuiDivider ref={ref} {...props} />;
});
Divider.displayName = 'Divider';
export default Divider;

View File

@@ -5,7 +5,7 @@ import type { IconButtonProps as MuiIconButtonProps } from '@mui/material/IconBu
// ─── Types ───────────────────────────────────────────────────────────────────
/** Props for the FA IconButton component */
export interface IconButtonProps extends MuiIconButtonProps {}
export type IconButtonProps = MuiIconButtonProps;
// ─── Component ───────────────────────────────────────────────────────────────
@@ -30,11 +30,9 @@ export interface IconButtonProps extends MuiIconButtonProps {}
* </IconButton>
* ```
*/
export const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
(props, ref) => {
return <MuiIconButton ref={ref} {...props} />;
},
);
export const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
return <MuiIconButton ref={ref} {...props} />;
});
IconButton.displayName = 'IconButton';
export default IconButton;

View File

@@ -5,7 +5,7 @@ import type { LinkProps as MuiLinkProps } from '@mui/material/Link';
// ─── Types ───────────────────────────────────────────────────────────────────
/** Props for the FA Link component */
export interface LinkProps extends MuiLinkProps {}
export type LinkProps = MuiLinkProps;
// ─── Component ───────────────────────────────────────────────────────────────
@@ -28,11 +28,9 @@ export interface LinkProps extends MuiLinkProps {}
* For button-styled links, use `Button` with `component="a"` and `href`.
* For navigation menu items, use Link with `underline="none"`.
*/
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
(props, ref) => {
return <MuiLink ref={ref} {...props} />;
},
);
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
return <MuiLink ref={ref} {...props} />;
});
Link.displayName = 'Link';
export default Link;

View File

@@ -5,7 +5,7 @@ import type { RadioProps as MuiRadioProps } from '@mui/material/Radio';
// ─── Types ───────────────────────────────────────────────────────────────────
/** Props for the FA Radio component */
export interface RadioProps extends MuiRadioProps {}
export type RadioProps = MuiRadioProps;
// ─── Component ───────────────────────────────────────────────────────────────
@@ -32,11 +32,9 @@ export interface RadioProps extends MuiRadioProps {}
* </RadioGroup>
* ```
*/
export const Radio = React.forwardRef<HTMLButtonElement, RadioProps>(
(props, ref) => {
return <MuiRadio ref={ref} {...props} />;
},
);
export const Radio = React.forwardRef<HTMLButtonElement, RadioProps>((props, ref) => {
return <MuiRadio ref={ref} {...props} />;
});
Radio.displayName = 'Radio';
export default Radio;

View File

@@ -5,7 +5,7 @@ import type { SwitchProps as MuiSwitchProps } from '@mui/material/Switch';
// ─── Types ───────────────────────────────────────────────────────────────────
/** Props for the FA Switch component */
export interface SwitchProps extends MuiSwitchProps {}
export type SwitchProps = MuiSwitchProps;
// ─── Component ───────────────────────────────────────────────────────────────
@@ -29,11 +29,9 @@ export interface SwitchProps extends MuiSwitchProps {}
* <FormControlLabel control={<Switch />} label="Include catering" />
* ```
*/
export const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
(props, ref) => {
return <MuiSwitch ref={ref} {...props} />;
},
);
export const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>((props, ref) => {
return <MuiSwitch ref={ref} {...props} />;
});
Switch.displayName = 'Switch';
export default Switch;

1
src/test/setup.ts Normal file
View File

@@ -0,0 +1 @@
import '@testing-library/jest-dom/vitest';