跳到正文
cccskills
免费在 GitHub 公开

components-architecture

Defines where UI components belong in Operately (TurboUI-first). Use when creating, changing, reviewing, or migrating UI components, adding features that need UI, or deciding whether to refactor legacy app UI in app/assets/js/components or app/assets/js/features. Covers pure TurboUI components, component reuse, the app bridge pattern, and legacy migration scenarios.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(2)

  • SKILL.md9.8 KB
  • reference.md9.1 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Components Architecture

All UI components belong in TurboUI as pure components. The app bridges the backend and TurboUI: it loads data, calls APIs, reads app contexts, and passes data and callbacks as props.

TurboUI was introduced after much of the app UI was built. app/assets/js/components/ and UI components under app/assets/js/features/ are deprecated for new UI. Non-UI code in app/assets/js/features/ (activity registration, API hooks, loaders, model hooks) still belongs in the app.

For detailed examples and the canonical component selection map, see reference.md.

Architecture

Data flows down as props; user actions flow up as callbacks. The app sits in the middle and talks to both sides.

LayerLocationResponsibility
UIturboui/src/Render UI; no API, routing, or app contexts
Bridgeapp/assets/js/pages/Load data, call APIs, read contexts, build props, handle callbacks
BackendElixir/GraphQLPersist and serve data

Pure Components

TurboUI components must not:

  • Import from @/… app paths
  • Call Api.*, use React Router hooks, or read app contexts (TimezoneContext, useMe, etc.)
  • Fetch data or perform server-side side effects

TurboUI components should:

  • Accept display data and user preferences via props
  • Use callbacks for actions (onSave, onDelete, onTaskUpdate)
  • Keep local UI state only (open/closed, draft input) and notify the parent via callbacks
  • Export from turboui/src/index.tsx
  • Include Storybook stories
  • Type props with turboui/src/ApiTypes (same shapes as app/assets/js/api/index.tsx) or a component-specific type when the UI needs a different shape

Translated copy

Use the internationalization skill for copy changes and new-language registration.

Hardcoded system-authored, user-visible text is forbidden, including accessible labels, tooltips, empty states, and errors. Follow the internationalization guide and its language glossaries. Use useTranslation, the relative i18n plural helper, and the shared Translate adapter; keep user content literal and escaped. Every copy change must include translations for all supported languages and regenerated catalogs/resources in the same PR. Never edit generated JSON manually or introduce independent locale detection.

Data types

turboui/src/ApiTypes/index.ts mirrors the types in app/assets/js/api/index.tsx. TurboUI components should expect those API shapes — the app fetches via TanStack Query and passes data through with minimal mapping.

  • Default: props use ApiTypes directly (e.g. Project, Person, Task)
  • Custom prop type: when the UI needs a view-specific shape, define it in the component's types.ts — re-export from ApiTypes where possible (see turboui/src/ResourceHub/types.ts)
  • Transform in TurboUI: when display logic applies to API data, do it inside the TurboUI component rather than in the app bridge

Do not add new parse*ForTurboUi or prepare* helpers in the app for new work. Legacy parsers still exist in older pages but are not the target pattern.

Component design

  • Self-contained: manage UI state locally; notify parents via callbacks
  • Generic callbacks: prefer onTaskUpdate(id, updates) over many specific handlers (onAssigneeChange, onDueDateChange, …)
  • Callback shape: (id: string, updates: Partial<Type>) => void for entity updates
  • No mock data in components: mock data belongs in Storybook stories only
  • Reuse TurboUI primitives: PrimaryButton, design-system colors (content-subtle, content-error), Tabler icons — check turboui/src/Colors/Colors.stories.tsx and turboui/src/icons/index.tsx before adding new ones

Component reuse gate

Complete this before writing JSX for new or changed UI:

  1. List every required control and interaction pattern: forms, fields, buttons, links, selectors, modals, empty states, validation, and loading feedback.
  2. Search turboui/src/index.tsx, relevant component directories, and existing usages for matching components.
  3. Compose the UI from those components. Use raw interactive elements only when implementing a TurboUI primitive or when the inventory confirms that no suitable primitive exists.
  4. If a raw <input>, <button>, <select>, <textarea>, dialog, link-like action, or validation message remains, document the reason in the implementation summary.

Use Forms.Form, Forms.FieldGroup, Forms.TextInput, and Forms.Submit as the default stack for new conventional forms. Use TextField for inline editing or existing surfaces already composed around it. Treat FormElements/Textfield as legacy and do not select it for new UI.

During code review, classify hand-rolled interactive UI that duplicates an existing TurboUI component as a P2 architecture issue.

File organization

turboui/src/ComponentName/
├── index.tsx           # Main component (+ exported Props type)
├── index.stories.tsx   # Storybook stories
├── mockData.ts         # Mock data for stories (optional)
└── types.ts            # Component-specific types (optional)

Storybook workflow

  1. Develop and test components in Storybook (make turboui.storybook)
  2. Create stories for empty, loading, error, and interactive states
  3. Before committing: make turboui.build && make turboui.test

App Bridge

App pages own all backend interaction. Typical page layout:

app/assets/js/pages/SomePage/
├── index.tsx      # Page module (loader + Page export)
├── loader.tsx     # Data fetching
├── navigation.tsx # Breadcrumbs / nav props (optional)
└── page.tsx       # Build props, render TurboUI component

Loaders and mutations use TanStack Query. Follow .agents/skills/tanstack-query/SKILL.md: prefetch with generated *Query helpers, read with useLoadedQuery, wrap mutations in *Lifecycle.ts. When adding a feature or fix on an existing page, migrate that page's API calls to TanStack in the same change.

Thin bridge — loader fetches data, page passes it through:

  • app/assets/js/pages/ReviewPage/index.tsx
  • app/assets/js/pages/ResourceHubDraftsPage/page.tsx

Standard bridge — props object + model hooks for mutations:

  • app/assets/js/pages/ResourceHubPage/page.tsx
  • app/assets/js/pages/SpaceWorkMapPage/page.tsx

Complex bridge — field state, API updates, large typed props:

  • app/assets/js/pages/ProjectPage/index.tsx
  • app/assets/js/pages/MilestonePage/index.tsx
  • app/assets/js/pages/GoalPage/index.tsx

App-side concerns that stay in the app (not TurboUI):

  • Data fetching: TanStack loaders (*Query + useLoadedQuery), model hooks
  • Mutations: *MutationOptions() plus cache invalidation (often via *Lifecycle.ts)
  • Routing: usePaths() — pass link strings or path-builder callbacks as props
  • App contexts: locale, timezone, current user — pass as props

Matching TurboUI components to study:

  • turboui/src/ResourceHubPage/ — page component with stories and mockData.ts
  • turboui/src/ProjectPage/, turboui/src/MilestonePage/, turboui/src/WorkMapPage/

Scenario 1: New UI Components

Always create in turboui/src/ComponentName/. Do not add new UI to app/assets/js/components/ or app/assets/js/features/.

App work for a new feature:

  1. Add or adjust a page in app/assets/js/pages/ (loader, navigation if needed)
  2. Prefetch in the loader with TanStack *Query helpers; pass API-shaped data to TurboUI (typed via ApiTypes)
  3. Wire callbacks to lifecycle mutateAsync / model hooks
  4. Render the TurboUI component

Scenario 2: Feature Needs an Existing App UI Component

First, grep imports to find call sites of the existing component.

2a — Few usages (small refactor)

Use when updating every call site is a small, reviewable change.

  1. Migrate the component to TurboUI as a pure component
  2. Adjust props to remove app dependencies (pass locale, timezone, etc. from app)
  3. Update all existing call sites to import from turboui
  4. Remove or thin the app copy
  5. Use the TurboUI component in the new feature

2b — Many usages (large refactor)

Use when a full migration would dominate the PR or touch unrelated features.

  1. Create a new pure version in TurboUI that is visually identical to the legacy component
  2. Use it only in the new feature for now
  3. Do not refactor all existing call sites in the same PR
  4. Leave the legacy app component in place until a dedicated migration

Checklist

  • Completed the component reuse inventory before writing JSX
  • Reused existing TurboUI controls and interaction patterns where available
  • Documented every remaining raw interactive element and why no suitable primitive exists
  • New or changed UI lives in turboui/src/, not in deprecated app UI folders
  • TurboUI component has no app imports or API calls
  • App page passes data and callbacks; API and context logic stays in app
  • Page loaders and mutations on the touched surface use TanStack Query (see tanstack-query skill)
  • Component exported from turboui/src/index.tsx
  • Storybook story added or updated (make turboui.build && make turboui.test)
  • Props typed with ApiTypes or a documented component-specific type — no new app-side parsers
  • Legacy migration uses the correct scenario (2a full migration vs 2b new-feature-only)

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Clean-code engineering standards for writing, refactoring, and reviewing code in any programming language. Use this whenever the user asks to write clean code, follow clean-code principles, refactor for clarity, improve naming, reduce complexity or duplication, separate concerns, tighten error handling, work test-first or do TDD, or otherwise raise code quality, readability, and maintainability. Also use when writing or reviewing Operately APIs or Ecto queries. Apply these rules by default when producing or changing code for a quality-conscious user.

中文简介正在准备中。当前显示原文说明。

operately/operately5872026年10月17日 更新

Rules for Operately schema migrations (app/priv/repo/migrations/) and data migrations (app/lib/operately/data/change_*.ex). Use when adding, renaming, reviewing, or generating database migrations, ecto.gen.migration, Operately.Data.Change* modules, backfills, schema_migrations version collisions, mix ecto.migrate, or make gen.migration.

中文简介正在准备中。当前显示原文说明。

operately/operately5872026年10月17日 更新

help-docs

免费

Discover help documentation work from operately git history. Use when the user asks to audit what needs documenting since a release, tag, or SHA, or to identify documentation gaps from code changes. Requires a baseline SHA or tag as input.

中文简介正在准备中。当前显示原文说明。

operately/operately5872026年10月17日 更新

Maintain Operately translations when adding or changing user-visible copy, fixing missing translations, or adding a supported language. Covers shared Gettext/i18next catalogs, glossaries, generation, and completeness checks; excludes translating user-authored content or general prose outside the product.

中文简介正在准备中。当前显示原文说明。

operately/operately5872026年10月17日 更新

mcp-tools

免费

Defines how to add Operately MCP tools (API-first wrappers). Use when creating, changing, or reviewing MCP tools under app/lib/operately_web/mcp/tools/, when the user mentions MCP tools, tool catalog, @expected_tool_names, or when exposing a new Operately capability to ChatGPT/Claude MCP clients.

中文简介正在准备中。当前显示原文说明。

operately/operately5872026年10月17日 更新

Operately frontend data fetching with TanStack Query. Use when adding or changing page loaders, model hooks, Api.* calls, mutations, useLoadedData, Pages.useRefresh, or any web UI backend request. New code must use TanStack. When fixing or extending an existing surface, migrate that surface's API calls to TanStack in the same change.

中文简介正在准备中。当前显示原文说明。

operately/operately5872026年10月17日 更新

查看 operately 的全部技能

报告此技能的问题