Ctrl K
ComunidadGuías…

CLAUDE.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Multiforum Development Guide

> Use **Node.js 26** for development (`nvm use`). Node.js ≥ 24.13.0 remains
> supported while Vercel deploys on Node 24; older majors break the tooling.

## Build & Run Commands

- `pnpm run graphql:check` - Validate production GraphQL documents against the pinned schema with no outstanding errors
- `pnpm run graphql:test` - Test GraphQL extraction, source locations, and baseline enforcement
- `pnpm run schema:check` - Verify the pinned backend schema and report its source commit
- `pnpm run schema:update /path/to/backend-artifact` - Update the schema pin; see [contract workflow](docs/graphql-contract-campaign.md)
- `pnpm run compile` - Generate schema and operation types offline from the pinned contract
- `pnpm run operations:check` - Verify typed-document freshness and production consumer contracts; see the [migration guide](docs/graphql-contract-campaign.md)
- `pnpm run dev` - Start development server
- `pnpm run build` - Build for production
- `pnpm run test:playwright` - Run Playwright tests
- `pnpm run build:playwright:mocked` - Build the mocked app once for fast parallel Playwright runs
- `pnpm run test:playwright:mocked:build` - Run the mocked Playwright suite against the prebuilt app
- `pnpm run test:unit` - Run unit tests with Vitest
- `pnpm run test:playwright:mocked -- tests/playwright/mocked/discussions/createEditDeleteDiscussions.spec.ts` - Run a specific mocked Playwright test
- `pnpm run tsc` - Run TypeScript type checking
- `pnpm exec eslint --fix path/to/file.vue` - Fix linting issues

## Playwright Testing (E2E)

- Use `installMockAuth()` to handle authentication for tests that need it
- Most Playwright tests use mocked GraphQL responses and require no backend.
- Cross-repository workflows that need to prove browser, API, and persistence
  behavior use the opt-in stateful suites under `tests/playwright/stateful`.
  Run the admin moderation suite with a backend checkout available via
  `PLAYWRIGHT_BACKEND_CWD`: start Neo4j with `pnpm run playwright:neo4j:up`,
  run `pnpm run test:playwright:stateful:admin`, then stop it with
  `pnpm run playwright:neo4j:down`.

### Playwright Test Optimizations

- **Replace arbitrary timeouts with network waits**:

  ```javascript
  // Before: Fixed timeout that might be too short or too long
  await page.getByRole('button', { name: 'Save' }).click();

  // After: Wait for the actual network request to complete
  await page.waitForResponse((response) => response.url().includes('/graphql'));
  ```

- **Validate network responses**: Add status code checks to ensure operations completed successfully

  ```javascript
  cy.wait('@graphqlRequest').its('response.statusCode').should('eq', 200);
  ```

- **Intercept specific GraphQL operations** by pattern-matching on the request body:

  ```javascript
  // Match specific GraphQL operations
  cy.intercept('POST', '**/graphql', (req) => {
    if (req.body.query.includes('createDiscussion')) {
      req.alias = 'createDiscussionRequest';
    }
  });

  // Later in the test
  cy.wait('@createDiscussionRequest');
  ```

## Vitest Testing (Unit)

- Unit tests live in `tests/unit` or colocated next to the file under test (`Foo.vue` → `Foo.spec.ts`)
- Run all unit tests with `pnpm run test:unit`
- Run specific tests with `pnpm run test:unit -- --run tests/unit/path/to/test.spec.ts`
- Use exactly one `expect(...)` assertion per `it(...)` test case
- If you need multiple checks, split into multiple `it(...)` blocks or combine into a single structured `expect(...)`
- Prefer `it.each(...)` tables when it helps keep tests DRY
- Keep `vi.mock(...)` at the module top level — Vitest hoists it, so calling it inside `beforeEach`/`describe` is deprecated. If the factory references a local variable, prefix its name with `mock` (e.g. `mockThemeRef`)
- Test thin page wrappers via `shallowMount` + `findComponent(Child).props(...)`; stub slot wrappers (`NuxtLayout`, `FormRow`) to render their slot, and `vi.stubGlobal('definePageMeta', vi.fn())` for pages that call it. See [CONTRIBUTING.md](./CONTRIBUTING.md#frontend-testing-patterns)

### Unit Test Best Practices

- **Test Real Code, Not Mocks**: Ensure tests verify actual application code rather than reimplementing logic in test files
  - Extract component logic into utility files when appropriate for better testability
  - Import and test the actual functions from your codebase rather than creating test-only implementations
- **Component Testing**:
  - Mount components to test actual validation logic, error messages, and UI state
  - Mock only external dependencies and services, not the core logic being tested
  - Verify that error/validation messages appear in the component when expected
- **Meaningful Tests**: Tests should validate application behavior, not trivial language features
  - Avoid tests that only check basic JavaScript functionality
  - Focus on edge cases, expected validations, and user interactions
- **Testable Code**: Refactor components to make logic more testable
  - Move complex formatting/validation logic to separate utility files
  - Use dependency injection to make components easier to test
- **Test Structure**: Organize tests to mirror the structure of the code being tested
  - Group related tests with descriptive names
  - Test both success and failure paths

#### Unit Test Gotchas

Hard-won pitfalls (full detail + code in [CONTRIBUTING.md](./CONTRIBUTING.md#frontend-testing-patterns)):

- **Mount the real component, never a hand-written stand-in.** A spec that defines `const FooTest = { template: '...' }` and mounts that adds **zero** coverage to `Foo.vue`. Mock the component's dependencies instead of reimplementing it.
- **Mock Apollo at module level.** `mountWithDefaults` does not wire Apollo. Mock `@vue/apollo-composable`; to run a mutation's `onDone` handlers, make `mutate()` synchronously fire the registered `onDone` callbacks, and return an empty `useQuery` result.
- **Seed hoisted `vi.fn` mocks with the full return shape.** `vi.fn(() => ({ valid: true }))` narrows the type, so a later `mockReturnValue({ valid: false, message })` fails CI's `vue-tsc` even when the test passes locally. Seed `{ valid: true, message: '' }`.
- **`@/utils` and `@/utils/index` are the same module** — if you mock one, mock both with every needed export.
- **Teleported UI** (`<Teleport to="body">` tooltips/modals) renders into `document.body`, not the wrapper — query the document.
- **Mocked GraphQL entities need `__typename`** or Apollo normalization hands the component only the key field.

## Pre-commit Workflow

- The pre-commit hook is intentionally fast and runs only `pnpm exec lint-staged`
- Full linting, type checking, unit tests, and Playwright checks run in CI
- To skip pre-commit hooks temporarily: `git commit --no-verify`

## Code Style Guidelines

- **TypeScript**: Use strict typing whenever possible, proper interfaces in `types/` directory
  - **Typed operations**: Import documents and exact selected result types from `@/__generated__/operations`; let Apollo infer variables/results. Prefer operation or fragment result subsets over full schema entities for migrated paths. See [the migration guide](docs/graphql-contract-campaign.md#phase-3-typed-operation-pilot).
  - **GraphQL results and variables**: Import documents and selected result types from `@/__generated__/operations`; let Apollo infer variables/results. Schema entities from `@/__generated__/graphql` describe the complete schema, not a query's selections. Use schema input types/enums for genuine inputs, and selected operation/fragment types for props and caches. Do not invent unselected fields to satisfy a schema entity.
  - **Avoid `any`**: `@typescript-eslint/no-explicit-any` is an **error** in application source (relaxed only in `*.spec.ts`). Only use `any` for genuinely untyped third-party interop, with a targeted `// eslint-disable-next-line` + reason — never for GraphQL data. See the `apollo-data` skill.
  - **Type Checking**: Use `pnpm run tsc` (which runs `vue-tsc --noEmit`) for proper Vue component type checking

### Common TypeScript Patterns and Fixes

- **GraphQL fixtures and cache writes**: Use the exact generated operation or fragment selection, including selected nullable fields. Never fabricate connection fields merely to satisfy a full schema entity. Preserve Apollo normalization keys and test cache writes against the real selected document.

- **Vue Router Type Safety**: Use proper route object structure for navigation

  ```typescript
  // Avoid: path property may not be compatible with router types
  routeAndClose({ path: '/account_settings' });

  // Prefer: use name-based routing
  routeAndClose({ name: 'account_settings' });
  ```

- **Error Type Handling**: GraphQL error objects may have inferred types as `never` in some contexts

  ```typescript
  // Cast to access message property when TypeScript can't infer the error type
  :text="(getCommentError as any)?.message || 'Error loading comment'"
  ```

- **Form Value Types**: Ensure form default values match the expected type interface completely

  ```typescript
  // If CreateEditChannelFormValues includes eventsEnabled and feedbackEnabled
  const defaultValues = {
    // ... other properties
    eventsEnabled: true, // Don't forget these!
    feedbackEnabled: true,
  };
  ```

- **Nuxt Page Meta**: Use proper placement and TypeScript handling

  ```typescript
  // Place at top of script setup block with TypeScript ignore
  // @ts-ignore - definePageMeta is auto-imported by Nuxt
  definePageMeta({
    middleware: 'some-middleware',
  });
  ```

- **GraphQL Query Imports**: When refactoring queries, check that exports exist in the target file

  ```typescript
  // Before using GET_MOD_SUSPENSION, verify it exists in the queries file
  // If not available, find similar queries like GET_SUSPENDED_MODS_BY_CHANNEL
  ```

- **Optional Chaining vs Null Coalescing**: Use appropriate patterns for component props
  ```typescript
  // For optional props that might be undefined
  :old-version="activeRevision.oldVersionData || {}"
  :new-version="activeRevision.newVersionData || {}"
  ```
- **Function Parameters**: For functions with more than one parameter, use a typed object instead of positional arguments

  ```typescript
  // Avoid:
  function updateUser(id: string, name: string, email: string) { ... }

  // Prefer:
  type UpdateUserParams = {
    id: string;
    name: string;
    email: string;
  };
  function updateUser(params: UpdateUserParams) { ... }
  ```

- **Vue Components**: Use script setup API with TypeScript and properly typed props/emits
- **RequireAuth slot pattern**: For auth-gated controls that should look identical in both auth states, extract a shared presentational child component.
  Use a thin functional wrapper for the authenticated slot when the real behavior needs to be attached, and render the shared child directly in the unauthenticated slot unless a placebo wrapper is genuinely useful.
- **Error Handling**: Use try/catch with specific error types, validate GraphQL responses
- **Apollo state (useQuery & useMutation)**: Always leverage the hook's built-in refs — `useMutation`'s `loading` / `error` / `onDone` and `useQuery`'s `result` / `loading` / `error` / `onResult` — instead of recreating that state in hand-written `ref`s. Bind UI disabled/loading/error states directly to the hook's refs, and put side effects in `onDone` / `onResult` / `onError`. Do not hand-roll `mutate()`/`refetch()` in try/catch to mirror state the hook already exposes. See the `apollo-data` skill and [docs/code-review-checklist.md](./docs/code-review-checklist.md).
- **Naming**: camelCase for variables/functions, PascalCase for components/interfaces
- **Imports**: Group imports by type (Vue, libraries, local components, utils)
- **Testing**: Each feature should have Playwright coverage, with shared seed data and cleanup helpers
- **CSS**: Use Tailwind utility classes, dark mode compatible with `dark:` prefix
- **Accessibility**: build to WCAG 2.2 AA. The `accessibility` skill carries the full
  contract, AA thresholds, and Vue patterns (invoke it for any UI/component work);
  `pnpm run lint:a11y` and the axe Playwright helper are the detection layers. Baseline:
  4.5:1 contrast for normal text; accessible names (`<label>` / `aria-label` /
  `aria-labelledby`) on every input and interactive control.
- **Composables**: Extract reusable logic into composables under `composables/` directory
- **Reactivity and Watchers**:
  - Avoid unnecessary watchers. Use Vue's built-in reactivity system (props, computed, refs) whenever possible
  - Use watchers only when absolutely necessary (e.g., for router param changes or external API calls)
  - When a component needs to react to prop changes, handle this through the component lifecycle or computed properties, not watchers
  - For individual item state that doesn't need to be shared, use local `ref` variables instead of Pinia store state

## Playwright Testing Guidelines

- Prefer explicit URLs and route helpers over hard-coded relative navigation
- Wait on requests or UI state instead of arbitrary sleeps
- Use `installMockAuth()` for browser-level auth setup and `resetStatefulBackendData()` for shared test data
- Replace UI-based authentication (`loginUser()`) with this programmatic pattern
- This pattern is faster and more reliable than UI-based authentication

### Dynamic Test IDs in Modals

Modals like `BrokenRulesModal.vue` use dynamic test IDs based on content type:

- For comments: `report-comment-input`
- For discussions: `report-discussion-input`
- For events: `report-event-input`

Always use the correct testid matching the content type being moderated.

## Permission System

The canonical reference for moderation permissions and suspension behavior is:

- [docs/moderation-architecture.md](./docs/moderation-architecture.md)

Keep `CLAUDE.md` concise and treat the moderation architecture doc as the single source of truth for:

- user vs moderator permission levels
- permission precedence and fallback order
- suspension lifecycle and expiry cleanup
- issue-linked moderation workflow
- server-scope vs channel-scope moderation model

Practical reminders:

- moderator menus in headers should show standard actions like "Give Feedback" and "Report" whenever the resolved permissions allow them
- the "Moderation Actions" section should appear for any actor with at least one moderation permission
- UI components should check specific permission flags rather than inferring from moderator status alone
- tests should verify that unprivileged users do not see moderation actions and that suspended moderators cannot use them

### Suspension System

The application enforces suspensions at both channel and server levels. Suspensions can be time-limited or indefinite.

#### How Suspensions Work

1. **Suspension Creation**: When a moderator suspends a user (e.g., via "Archive and Suspend" on a discussion), a `Suspension` node is created with:
   - `suspendedUntil` - Expiration date (for time-limited suspensions)
   - `suspendedIndefinitely` - Flag for permanent suspensions
   - Link to the related moderation issue

2. **Suspension Detection**: The backend checks for active suspensions when users attempt actions:
   - A suspension is active if `suspendedIndefinitely` is true OR `suspendedUntil` is in the future
   - Expired suspensions are automatically cleaned up (disconnected from channel relationships)

3. **Permission Enforcement**:
   - **Channel-level**: Suspended users use `SuspendedRole` permissions (typically very restricted)
   - **Server-level**: Users with any active suspension use `DefaultSuspendedRole` for server actions (e.g., creating new forums)

4. **User Notifications**: When a suspended user is blocked from an action, they receive an in-app notification explaining:
   - Which channel they're suspended in
   - What action was blocked
   - Reference to the related moderation issue

#### Unsuspension

Users can be unsuspended through the moderation issue interface, which removes the suspension relationship from the channel.

### Testing Moderator Permissions

When testing moderator permissions:

- Make sure the test user has the expected permission level
- Check that appropriate UI elements appear based on permission level
- Verify that unprivileged users don't see moderation options
- Test that suspended moderators can't access moderation features

## SSR and Hydration

### Preventing Hydration Mismatches

Hydration errors occur when the server-rendered HTML doesn't match what the client expects to render. Common causes:

1. **`v-if` conditions with async data**: If a condition depends on data that may differ between server and client (e.g., GraphQL query results, auth state), wrap the content in `<ClientOnly>`.

2. **Slot content with client-dependent conditions**: When passing slot content that has `v-if` conditions depending on async data, wrap the content in `<ClientOnly>` **at the source** (where the slot content is defined), not in the child component receiving the slot.

   ```vue
   <!-- WRONG: ClientOnly in the child component doesn't help -->
   <!-- components/Child.vue -->
   <template>
     <ClientOnly>
       <slot />
     </ClientOnly>
   </template>

   <!-- CORRECT: ClientOnly wraps the content at the source -->
   <!-- components/Parent.vue -->
   <template>
     <Child>
       <ClientOnly>
         <MyComponent v-if="asyncData" />
       </ClientOnly>
     </Child>
   </template>
   ```

3. **Auth-dependent UI**: Components that show different content based on authentication state should use `<ClientOnly>` since auth state is client-side only.

### Key Files with ClientOnly Wrappers

- `components/discussion/detail/DiscussionDetailContent.vue` - Comment form wrappers
- `components/discussion/detail/DiscussionCommentsWrapper.vue` - Subscribe button

## Project Structure

- Components in `components/` directory with subdirectories for features
- Pages in `pages/` directory matching route structure
- GraphQL queries/mutations in `graphQLData/` by domain
- Types in `types/` directory
- Utility functions in `utils/` directory (including permission utilities)

## Data Model Notes

- **Downloads**: There is no separate Download type. Downloads are discussions with the `hasDownload` field set to true. They use the Discussion type and are displayed with a different frontend skin in download-specific components.

## Working with Claude

- **Incremental Changes**: Make small, focused changes rather than large sweeping changes
  - Work on one test file at a time rather than multiple tests at once
  - Fix specific issues incrementally rather than rewriting multiple files
- **Step-by-Step Approach**: If multiple files need changes, address them one at a time
  - First understand the issue, then propose a targeted solution
  - Get confirmation before proceeding to the next file
- **Show Your Work**: Explain reasoning behind changes, especially for complex fixes
  - When troubleshooting, describe the issue and how the solution addresses it
  - Provide context for why a particular approach was chosen