diff --git a/.github/instructions/frontend-core.instructions.md b/.github/instructions/frontend-core.instructions.md new file mode 100644 index 000000000..5b0879561 --- /dev/null +++ b/.github/instructions/frontend-core.instructions.md @@ -0,0 +1,224 @@ +--- +description: Core frontend development patterns for Vue 3 + Nuxt 3 applications +applyTo: 'packages/frontend-2/**/*,packages/ui-components/**/*' +--- + +# Frontend Development Rules + +## Tech Stack + +### Frontend App (packages/frontend-2) + +- **Framework**: Nuxt 3 with Vue 3 composition API +- **Styling**: Tailwind CSS with custom theme from @speckle/tailwind-theme +- **State**: Vue composables, no Vuex/Pinia +- **Forms**: vee-validate for form validation +- **GraphQL**: Apollo Client with @vue/apollo-composable +- **Icons**: Lucide icons (Heroicons are deprecated) +- **Rich Text**: TipTap editor +- **Build**: Vite with TypeScript + +### Component Library (packages/ui-components) + +- **Framework**: Vue 3 with composition API +- **Build**: Vite with TypeScript for library builds +- **Styling**: Tailwind CSS with custom theme +- **Development**: Storybook for component development and documentation + +## Code Style & Formatting + +### TypeScript + +- **Strict mode enabled** - Use strict TypeScript everywhere +- **ES2021 target** with modern features +- **Module resolution**: "bundler" for modern imports +- **Path aliases**: Use `~/` for Nuxt auto-imports in frontend-2 +- **Import style**: Prefer named imports, consistent type imports +- **Type definitions**: Explicit types over `any`, use utility types +- **Type guards over type assertions** - Use type narrowing instead of `as` casting +- **Explicit return types** for exported/public functions + +### Formatting & Linting Configuration + +**For up-to-date formatting and linting rules, reference these files:** + +- **Prettier configuration**: `.prettierrc` + + - Contains formatting rules for quotes, semicolons, indentation, print width + - Enforced via pre-commit hooks and CI + +- **ESLint configurations**: + + - **Frontend-2**: `packages/frontend-2/eslint.config.mjs` + - **UI Components**: `packages/ui-components/eslint.config.mjs` + - **Base config**: `eslint.config.mjs` (shared rules) + +- **TypeScript configuration**: + - **Frontend-2**: `packages/frontend-2/tsconfig.json` + - **UI Components**: `packages/ui-components/tsconfig.json` + +### Constants and Magic Strings + +- **Use enums or constants** instead of duplicating string literals +- **Co-locate types** with implementations when not domain-specific +- **Object parameters** over positional parameters for functions +- **First parameter** for main params, **second optional** for options + +### Icons + +- **Always use Lucide icons** - Heroicons are deprecated +- **Import from** `lucide-vue-next` package +- **Consistent sizing** using Tailwind classes + +## File & Directory Conventions + +### Naming + +- **kebab-case** for file names +- **PascalCase** for Vue component files +- **camelCase** for TypeScript/JavaScript files +- **kebab-case** for directories + +### Path Resolution & Imports + +- **Workspace packages**: `@speckle/package-name` +- **Frontend-2 paths**: Use `~/` for Nuxt auto-imports and absolute paths +- **Type imports**: Use `type` keyword for type-only imports +- **Always use alias imports** - Never use relative paths + +```typescript +// 1. Node modules +import { computed, ref } from 'vue' +import { useQuery } from '@vue/apollo-composable' + +// 2. Internal packages +import { type Nullable } from '@speckle/shared' +import { FormButton } from '@speckle/ui-components' + +// 3. Local imports +import { useProjectData } from '~/lib/projects/composables' +import type { ProjectFragment } from '~/lib/common/generated/gql/graphql' +``` + +## Common Patterns & Decision Making + +### When Creating New Files + +- **Components**: Use PascalCase, place in feature-based directories +- **Composables**: Use camelCase with `use` prefix, group by domain +- **Types**: Co-locate with implementation unless domain-specific +- **Always check existing patterns** before creating new ones + +### Component Composition Patterns + +- **Small, focused components** over large multi-purpose ones +- **Props for data down**, **emits for events up** +- **Composables for logic sharing** between components +- **Fragments for data requirements** rather than over-fetching + +### Import Resolution Priority + +1. **Workspace packages** (`@speckle/package-name`) +2. **Nuxt auto-imports** (`~/lib/...`) +3. **Node modules** (external packages) +4. **Never use relative imports** beyond same directory + +## Performance Guidelines + +### General Performance Rules + +- **Use `computed`** for derived reactive data +- **Use `ref`** for simple reactive values +- **Use `shallowRef`** for large objects that change by reference +- **Lazy loading** for routes and components +- **Virtual scrolling** for lists > 100 items + +### Frontend-2 (Application Performance) + +- **Image optimization** with Nuxt Image if relevant +- **Code splitting** at route level +- **Bundle analysis** to monitor size + +### UI Components (Library Performance) + +- **Efficient component composition** to avoid unnecessary re-renders +- **Prop validation** only in development mode +- **Minimal dependencies** to keep bundle size small +- **Tree-shakeable exports** for optimal bundling + +## Logging Patterns + +### Structured Logging + +- **Structured logging** with Pino for production +- **useLogger()** composable for standard logging +- **useSafeLogger()** when you need a logger potentially outside of useNuxtApp() scope +- **devLog()** or **useDevLogger()** for development-only logging +- **Never use console.log** - use logging composables instead +- **Development logging** is automatically skipped in production +- **Log levels** appropriate to environment +- **Error context** for debugging + +### Analytics & Tracking + +- **Don't add Mixpanel events** unless specifically requested +- **Event naming convention**: Past tense with every first letter capitalized + - Examples: "Button Clicked", "Dialog Dismissed", "Form Submitted" +- **Use mixpanel composable** from `~/lib/core/composables/mp` + +## Error Handling + +### Frontend Applications + +- **Try-catch** for async operations +- **Loading states** for all async actions +- **User feedback** for errors via toast/notification +- **Graceful degradation** when features fail +- **Error boundaries** for component-level error handling + +## Development Workflow + +### Code Quality + +#### Pre-commit Checks + +- **ESLint** must pass before commits +- **TypeScript** compilation without errors +- **Prettier** formatting enforced +- **Husky hooks** for automated checks + +## Accessibility Guidelines + +### Requirements + +- **ARIA labels** for interactive elements +- **Keyboard navigation** support +- **Screen reader** compatibility +- **Color contrast** compliance when possible +- **Focus management** in modals/dialogs + +## Working with This Codebase + +### Key Principles + +1. **Follow TypeScript strict mode** - No any types without good reason +2. **Use composition API** - Prefer composables over mixins +3. **Keep components focused** - Single responsibility principle +4. **Consider accessibility** +5. **Document complex logic** - Document business logic, but don't add redundant comments +6. **Follow existing patterns** + +### When in Doubt + +- **Check existing implementations** for similar functionality +- **Ask questions** in code reviews or discussions +- **Refer to the design system** before creating custom styles +- **Use the logging composables** instead of console.log + +@.prettierrc +@eslint.config.mjs +@packages/frontend-2/eslint.config.mjs +@packages/ui-components/eslint.config.mjs +@packages/frontend-2/tsconfig.json +@packages/ui-components/tsconfig.json +@packages/frontend-2/composables/logging.ts diff --git a/.github/instructions/graphql-patterns.instructions.md b/.github/instructions/graphql-patterns.instructions.md new file mode 100644 index 000000000..2e8a42e3d --- /dev/null +++ b/.github/instructions/graphql-patterns.instructions.md @@ -0,0 +1,84 @@ +--- +description: GraphQL patterns and fragment-based architecture +applyTo: 'packages/frontend-2/**/*.vue,packages/frontend-2/**/*.ts,packages/frontend-2/**/*gql*/**/*,packages/frontend-2/**/*generated*/**/*,packages/ui-components/**/*.vue,packages/ui-components/**/*.ts' +--- + +# GraphQL Patterns + +## Code Generation + +- **Generated types** from GraphQL schema +- **Typed hooks** from @vue/apollo-composable +- **Fragment colocation** with components +- **Operation naming** follows schema conventions + +## Fragment-Based Architecture + +```typescript +// Define component data requirements via fragments +graphql(` + fragment SomeComponent_Project on Project { + id + name + # Only fields this component needs + } +`) + +defineProps<{ + project: SomeComponent_Project +}>() +``` + +## Query/Mutation Patterns + +```typescript +// Use generated hooks +const { result, loading, error } = useQuery(SomeQuery) +const { mutate, loading: mutating } = useMutation(SomeMutation) + +// Handle loading states +const isLoading = computed(() => loading.value || mutating.value) +``` + +## Data Requirements + +- **Always include `id` field** in queries for Apollo cache management +- **Mutations return updated objects** instead of just success booleans +- **Use fragments** to define component data requirements +- **Fragment naming**: `{ComponentName}_{GraphQLType}` + +## Examples + +### Fragment Definition + +```typescript +// UserCard.vue +graphql(` + fragment UserCard_User on User { + id + name + email + avatar + } +`) + +defineProps<{ + user: UserCard_User +}>() +``` + +### Query with Fragments + +```typescript +// UsersPage.vue +const { result: usersResult } = useQuery( + graphql(` + query UsersPage_Users { + users { + id + ...UserCard_User + } + } + `) +) +``` diff --git a/.github/instructions/review-guide/frontend.instructions.md b/.github/instructions/review-guide/frontend.instructions.md new file mode 100644 index 000000000..1ed60fa4e --- /dev/null +++ b/.github/instructions/review-guide/frontend.instructions.md @@ -0,0 +1,222 @@ +--- +description: Frontend development best practices for Vue 3, Nuxt 3, TypeScript, and component architecture +applyTo: 'packages/frontend-2/**/*,packages/ui-components/**/*,packages/preview-frontend/**/*,packages/viewer/**/*,packages/viewer-sandbox/**/*' +--- + +# Frontend Development Best Practices + +## Vue 3 & Nuxt 3 Patterns + +### Script Setup Organization + +Organize code in Vue ` + + +``` + +More info: https://vuejs.org/guide/reusability/composables#usage-restrictions + + + +### Single Root Node Requirement + +Vue components should always have a single root node. + +Having no root element, a root element that can conditionally disappear or multiple root elements, can cause bugs when rendering that component. The bugs can be hard to figure out - just parts of the UI randomly not rendering with a low-level Vue error. + +Always ensure that if the root node is conditional, there is a `v-else` that renders a fallback root node. + +```tsx +// Bad: If !someCondition, there's no root node + + +// Good: There's always a root node no matter what + +``` + +### Reactive Data Management + +Make sure all Vue component data is reactive, unless if you're absolutely sure that it will never change during the app's lifecycle. + +If you just store some data in a basic constant, not a ref or a computed, then it's not reactive, and any other templates/computeds/watchers that depend on it will not update when the value changes. + +For the most part you want to store everything in refs/computeds/shallowRefs. + +```tsx +// BAD: favoriteCount will never update, even if props change + + +// GOOD: favoriteCount will update when its dependencies update + +``` + + + +### Absolute Imports + +Always use absolute imports with the `~` or `~~` prefix for project root references. + +This makes it possible to specify all imports with an absolute path like `import { Foo } from ~/lib/foo/types.ts` , so that all imports to `Foo` are importing from the same path. + +Relative paths on the other hand are relative, and ever changing. This makes it a lot harder to discover and refactor each import reference of something. + +## List Rendering & Keys + +### Avoid Array Indices as Keys + +Do not use array indices as keys, if possible. + +You should not use array indexes as keys since indexes are only indicative of an items position in the array and not an identifier of the item itself. + +```tsx +❌
+``` + +Why does that matter? Because if a new item is inserted into the array at any position other than the end, when Vue patches the DOM with the new item data, then any data local in the iterated component will not update along with it. + +Ideally you should always use some kind of unique identifier associated with each array item as a key instead. + +More info on this + a demo on how index keys can cause bugs: https://vueschool.io/articles/vuejs-tutorials/tips-and-gotchas-for-using-key-with-v-for-in-vue-js-3/ + +## GraphQL Frontend Patterns + +### Fragment-Based Architecture + +Define GQL requirements for your Vue components (& functions) through GQL fragments. + +GQL fragments are re-usable GQL field definitions that can be used in queries, mutations etc. Fragments are ideal for defining the API/data requirements of a Vue component (or composable) - you define a fragment only with the fields you need, and then you can use the GQL Codegen auto-generated type in your `defineProps` call. + +When all components are built this way, it's easy to build queries that only ask for what is needed and not more - you just build a query out of all the fragments for the components you're rendering. + +If you don't do this, however, all of requirements of your Vue components are expressed directly in GQL operation strings without any link back to the component that needs them. Thus it's completely unclear which fields are actually needed and why. As the app grows in size and complexity, there tends to be more overfetching of data and wasteful API calls. + +```tsx +// some/random/Component.vue + + +``` + +Read more about this approach here: https://the-guild.dev/blog/unleash-the-power-of-fragments-with-graphql-codegen + +### Always Include ID Fields + +Always ask back for an `id` field for any GQL objects you query to ensure Apollo cache can be updated, where necessary. + +More info: https://www.apollographql.com/docs/react/data/mutations/#include-modified-objects-in-mutation-responses + +Whenever a query/subscription/mutation response is received back from server, Apollo Client will see if there are any objects in the local cache that need updating, and doing that relies on the objects having a clear type name and ID. The type name can be inferred automatically, but the `id` field is something you have to ask for explicitly in your operations. + +```tsx +// BAD +graphql(` + query Project($id: String!) { + project { + name + ...SomeFragment + } + } +`) + +// GOOD +graphql(` + query Project($id: String!) { + project { + id + name + ...SomeFragment + } + } +`) +``` + +### Vue component naming in templates + +Component names from `./components/**` should be used in PascalCase in templates and their names are built out of their ancestor directories. + +Examples and patterns: + +``` +// ./components/Project/DetailsCard.vue -> +// ./components/Project/Settings/PrivacySettings.vue -> +// ./components/Project/Settings/SettingsView.vue -> +// ./components/global/Button.vue ->