Skip to content
cccskills
FreePublished on GitHub

new-page

Scaffold a new frontend page in ayunis-core. Use when adding a new route with its page component following Feature-Sliced Design conventions.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34 stars(GitHub 34 + cccskills 0)

Run score Not tested

See how to install

Files included (1)

  • SKILL.md8.2 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

New Frontend Page — ayunis-core-frontend

Prerequisites

  • Read the ayunis-core-frontend-dev skill for validation sequence and FSD rules
  • Ensure the dev stack is running (./dev up from repo root)

Working Directory

cd ayunis-core-frontend

Directory Structure

Every page follows this structure:

src/pages/<page-name>/
├── ui/
│   └── <PageName>Page.tsx       # Main page component (default export)
├── api/                          # Optional: mutation hooks
│   ├── useCreate<Entity>.ts
│   ├── useUpdate<Entity>.ts
│   └── useDelete<Entity>.ts
├── model/                        # Optional: types, constants
│   └── openapi.ts               # Re-exported types from generated API
└── index.ts                      # Barrel export

src/app/routes/_authenticated/
└── <page-name>.index.tsx          # TanStack Router route file

File Templates

1. Barrel Export

// src/pages/<page-name>/index.ts
export { default as MyPagePage } from './ui/MyPagePage';

2. Page Component

Pages compose layouts, widgets, and features. They receive data via props (from the route loader) or fetch it internally.

// src/pages/<page-name>/ui/MyPagePage.tsx
import AppLayout from '@/layouts/app-layout';
import ContentAreaLayout from '@/layouts/content-area-layout/ui/ContentAreaLayout';
import ContentAreaHeader from '@/widgets/content-area-header/ui/ContentAreaHeader';
import { useTranslation } from 'react-i18next';

interface MyPagePageProps {
  items: Item[];
}

export default function MyPagePage({ items }: MyPagePageProps) {
  const { t } = useTranslation('<page-name>');

  return (
    <AppLayout>
      <ContentAreaLayout
        contentHeader={
          <ContentAreaHeader title={t('page.title')} />
        }
        contentArea={
          <div>
            {items.map((item) => (
              <div key={item.id}>{item.name}</div>
            ))}
          </div>
        }
      />
    </AppLayout>
  );
}

3. Route File

Routes use TanStack Router's createFileRoute. Data fetching happens in the loader.

// src/app/routes/_authenticated/<page-name>.index.tsx
import { createFileRoute } from '@tanstack/react-router';
import { MyPagePage } from '@/pages/<page-name>';
import {
  getMyEntitiesControllerFindAllQueryKey,
  myEntitiesControllerFindAll,
} from '@/shared/api/generated/ayunisCoreAPI';

export const Route = createFileRoute('/_authenticated/<page-name>/')({
  component: RouteComponent,
  loader: async ({ context: { queryClient } }) => {
    const items = await queryClient.fetchQuery({
      queryKey: getMyEntitiesControllerFindAllQueryKey(),
      queryFn: () => myEntitiesControllerFindAll(),
    });
    return { items };
  },
});

function RouteComponent() {
  const { items } = Route.useLoaderData();
  return <MyPagePage items={items} />;
}

For routes with URL parameters:

// src/app/routes/_authenticated/<page-name>.$id.tsx
import { createFileRoute } from '@tanstack/react-router';
import { MyDetailPage } from '@/pages/<page-name>';
import {
  getMyEntitiesControllerFindOneQueryKey,
  myEntitiesControllerFindOne,
} from '@/shared/api/generated/ayunisCoreAPI';

export const Route = createFileRoute('/_authenticated/<page-name>/$id')({
  component: RouteComponent,
  loader: async ({ params: { id }, context: { queryClient } }) => {
    const item = await queryClient.fetchQuery({
      queryKey: getMyEntitiesControllerFindOneQueryKey(id),
      queryFn: () => myEntitiesControllerFindOne(id),
    });
    return { item };
  },
});

function RouteComponent() {
  const { item } = Route.useLoaderData();
  return <MyDetailPage item={item} />;
}

For routes with search params:

// src/app/routes/_authenticated/<page-name>.tsx
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
import { MyPagePage } from '@/pages/<page-name>';

const searchSchema = z.object({
  filter: z.string().optional(),
});

export const Route = createFileRoute('/_authenticated/<page-name>')({
  component: RouteComponent,
  validateSearch: searchSchema,
});

function RouteComponent() {
  const { filter } = Route.useSearch();
  return <MyPagePage filter={filter} />;
}

4. Mutation Hooks (Optional)

One hook per operation. Each encapsulates a TanStack Query mutation with cache invalidation.

// src/pages/<page-name>/api/useCreateMyEntity.ts
import {
  useMyEntitiesControllerCreate,
  getMyEntitiesControllerFindAllQueryKey,
} from '@/shared/api/generated/ayunisCoreAPI';
import { useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';

export function useCreateMyEntity(onSuccess?: () => void) {
  const queryClient = useQueryClient();
  const { t } = useTranslation('<page-name>');

  const mutation = useMyEntitiesControllerCreate({
    mutation: {
      onSuccess: () => {
        void queryClient.invalidateQueries({
          queryKey: getMyEntitiesControllerFindAllQueryKey(),
        });
        toast.success(t('toast.createSuccess'));
        onSuccess?.();
      },
      onError: () => {
        toast.error(t('toast.createError'));
      },
    },
  });

  return {
    createMyEntity: (data: { name: string }) =>
      mutation.mutate({ data }),
    isCreating: mutation.isPending,
  };
}

5. Model Types (Optional)

Re-export types from the generated API client for cleaner imports within the page module.

// src/pages/<page-name>/model/openapi.ts
export type {
  MyEntityResponseDto as MyEntity,
} from '@/shared/api/generated/ayunisCoreAPI.schemas';

Internationalization

Add translation keys for the new page:

# Create translation files
# src/shared/i18n/locales/de/<page-name>.json
# src/shared/i18n/locales/en/<page-name>.json
{
  "page": {
    "title": "My Page"
  },
  "toast": {
    "createSuccess": "Created successfully",
    "createError": "Failed to create"
  }
}

Register the namespace in src/shared/i18n/i18n.ts.

FSD Import Rules

Pages sit at the top of the dependency hierarchy:

pages → widgets → features → shared

A page can import from:

  • @/widgets/* — Composite UI components
  • @/features/* — Business logic features
  • @/shared/* — Primitives (UI, API, lib, i18n)
  • @/layouts/* — Layout components

A page cannot import from:

  • Other pages (@/pages/*) — pages are siblings, never dependencies
  • @/app/* — the app layer is above pages

Available Layouts

LayoutUse Case
AppLayoutStandard authenticated page with sidebar
ContentAreaLayoutPage with header + scrollable content area
FullScreenMessageLayoutCentered message (empty states, errors)
ChatInterfaceLayoutChat conversation layout with input area

Scaffold Checklist

  1. Create page files:

    • src/pages/<page-name>/ui/<PageName>Page.tsx
    • src/pages/<page-name>/index.ts
    • src/app/routes/_authenticated/<page-name>.index.tsx
  2. Create translations (if using i18n):

    • src/shared/i18n/locales/de/<page-name>.json
    • src/shared/i18n/locales/en/<page-name>.json
    • Register namespace in src/shared/i18n/i18n.ts
  3. Regenerate route tree:

    pnpm exec tsr generate
    

    This updates src/app/routeTree.gen.ts automatically.

  4. Validate:

    pnpm run lint
    pnpm exec tsc --noEmit
    pnpm run build
    
  5. Visual check: Navigate to the new route in the browser and verify it renders.

Reference Pages

Study these existing pages as examples:

  • Simple list page: src/pages/agents/ — list with tabs, create dialog
  • Detail page with params: src/pages/agent/ — single entity view with $id param
  • Search params: src/pages/install/ — page with search schema validation
  • Settings sub-pages: src/pages/settings/ — nested layout with multiple sub-routes
  • Data loading in route: src/app/routes/_authenticated/prompts.index.tsx — loader pattern with query prefetch

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Query Ayunis deployed-environment telemetry in AppSignal — incidents, occurrence counts and error traces via the `appsignal-cli` binary; structured log attributes (user.id, org.id, nestjs.context, custom metadata) via the v2 logs API. Use when investigating a production/staging incident or log data — NOT for local dev logs (see backend-debugging).

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Ayunis Core backend specifics — module boundaries, key files, and project conventions. Complements the nestjs-hexagonal-backend skill.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Frontend development in ayunis-core. Use when creating, modifying, or debugging frontend code (React, Feature-Sliced Design, API client).

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Debug backend runtime errors (500s, crashes, unexpected behavior). Use when something is broken at runtime — not for writing new code.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Shared test fixtures for backend specs — port mock factories, domain builders, and shared IDs in a per-module testing/ folder. Use when a module's *.use-case.spec.ts files repeat the same mock-repository literal, inline entity construction, or hardcoded UUIDs across files.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Analyze Cursor Bugbot PR comments through a root-cause lens: is the finding valid, and is the suggested fix the real fix or a bandaid over a deeper problem? Use when the user asks to check, assess, or triage bugbot comments.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

See all skills by ayunis-core

Report a problem with this skill