All Skills

Use when writing, editing, creating, planning, fixing, or refactoring any UI code in this project. Covers components, pages, routes, forms, tables, modals, buttons, queries, hooks, styles, layouts, loaders, and actions. Use when adding a feature, building a page, fixing a bug, styling UI, creating a modal, adding a table, writing a query, updating a route, or any work touching packages/app-builder or packages/ui-design-system.

M
$npx skills add checkmarble/marble-frontend --skill frontend-dev-guidelines

Frontend Development Guidelines

Comprehensive guide for React development in the Marble monorepo. All patterns below are derived from the actual codebase.

Tech Stack

  • React 18 + TypeScript (strict mode)
  • Remix (flat file routes with + folders)
  • Radix UI (headless primitives)
  • Tailwind CSS 4 (CSS variable-based theming)
  • TanStack Query (server state) + TanStack Form (form state)
  • Zod v4 (import { z } from 'zod/v4')
  • ts-pattern (pattern matching) + remeda (functional utils)
  • cn (class composition with Tailwind merge, from ui-design-system)

Monorepo Structure

packages/
  app-builder/         # Main Remix application
  ui-design-system/    # Reusable UI (Button, Modal, Table, MenuCommand)
  ui-icons/            # Icon components (SVG sprite)
  shared/              # Shared utilities
  marble-api/          # Generated API client
  tailwind-preset/     # Tailwind config + CSS variables
  typescript-utils/    # TS utilities

App-Builder Architecture

packages/app-builder/src/
  routes/           # Remix flat routes (_builder+, _auth+, ressources+)
  components/       # Feature components (Cases/, Analytics/, AstBuilder/)
  queries/          # TanStack Query hooks (one file per query)
  repositories/     # Data access layer (interfaces + factory functions)
  services/         # Business logic
  models/           # Types + adapter functions (DTO <-> domain)
  hooks/            # Custom React hooks
  contexts/         # React contexts (AgnosticNavigation, etc.)
  schemas/          # Zod schemas
  utils/            # Utilities (routes/, form.ts, format.ts)
  locales/          # i18n (en/, fr/, ar/ - all three required)
  middlewares/      # Server middleware (auth, redirect)

Core Conventions

Components

// Plain function declarations - NOT React.FC or FunctionComponent
// Named exports only - NO default exports
// Props via interface (or type)

interface CaseCardProps {
  caseData: Case;
  onSelect?: (id: string) => void;
  className?: string;
}

export function CaseCard({ caseData, onSelect, className }: CaseCardProps) {
  return (
    <div className={cn('rounded-lg border border-grey-border p-4', className)}>
      <h3 className="text-l font-semibold text-grey-primary">{caseData.name}</h3>
      <Button onClick={() => onSelect?.(caseData.id)}>Select</Button>
    </div>
  );
}

Imports

// Internal (path alias)
import { Case } from '@app-builder/models/cases';
import { useGetCasesQuery } from '@app-builder/queries/cases/get-cases';
import { getRoute } from '@app-builder/utils/routes';

// UI packages
import { Button, Modal, MenuCommand, Table, useVirtualTable, cn } from 'ui-design-system';
import { Icon } from 'ui-icons';

// External
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useForm } from '@tanstack/react-form';
import { z } from 'zod/v4';
import { match, P } from 'ts-pattern';
import * as R from 'remeda';
import toast from 'react-hot-toast';
// Note: prefer cn over clsx — cn resolves conflicting Tailwind classes

Query Hook (one file per query)

// queries/cases/get-case.ts
import { useQuery } from '@tanstack/react-query';
import { getRoute } from '@app-builder/utils/routes';

export function useGetCaseQuery(caseId: string) {
  return useQuery({
    queryKey: ['cases', 'get-case', caseId],
    queryFn: async () => {
      const response = await fetch(
        getRoute('/ressources/cases/:caseId', { caseId }),
      );
      return response.json() as Promise<CaseDetail>;
    },
    enabled: !!caseId,
  });
}

Loaders (server-side data)

// Using createServerFn with middleware (preferred for new code)
import { createServerFn } from '@app-builder/core/requests';
import { authMiddleware } from '@app-builder/middlewares/auth-middleware';

export const loader = createServerFn(
  [authMiddleware],
  async function casesLoader({ request, context }) {
    const inboxes = await context.authInfo.inbox.listInboxes();
    return { inboxes };
  },
);

Topic Guides

Read these when working on specific areas:

TopicResourceWhen to read
Component patternscomponent-patterns.mdWriting/editing components
Data fetchingdata-fetching.mdQueries, mutations, loaders, actions
Routingrouting-guide.mdRoutes, breadcrumbs, navigation
Stylingstyling-guide.mdColors, surface tokens, dark mode
Tables & selectstables-and-selects.mdVirtual tables, MenuCommand dropdowns
Forms & modalsforms-and-modals.mdTanStack Form, Modal, Panel
Common patternscommon-patterns.mdts-pattern, remeda, i18n, icons, toasts, dates
File organizationfile-organization.mdArchitecture layers, naming
TypeScripttypescript-standards.mdTypes, Zod v4, model adapters