Files

18 KiB

Copilot Instructions for Frappe Helpdesk

Skills

For Frappe development tasks, reference the frappe-dev skill:

  • Location: ~/.agents/skills/frappe-dev/
  • Usage: When working on DocType, controllers, whitelisted APIs, background jobs, permissions, or frontend tasks, read the relevant skill reference files:
    • doctype.md — Creating/modifying DocTypes and fields
    • controllers.md — Document lifecycle and server logic
    • api.md — REST endpoints with @frappe.whitelist()
    • database.mdfrappe.db, queries, raw SQL
    • background-jobs.mdfrappe.enqueue, scheduled jobs
    • permissions.md — Roles, DocType permissions
    • frontend-desk.md, frontend-vue.md, frontend-portal.md — Frontend references

Project Overview

Frappe Helpdesk is an open-source ticket management tool built on the Frappe Framework (Python backend) and Frappe UI (Vue.js frontend). It provides agent/customer portals, SLAs, assignment rules, a knowledge base, and saved replies.

Tech Stack & Architecture

  • Backend: Frappe Framework with Python
  • Frontend: Vue 3 + TypeScript SPA with Vite dev server
  • Database: MariaDB with structured doctypes
  • Real-time: WebSockets via Socket.IO for live updates
  • UI Library: frappe-ui for consistent design components
  • Styling: Tailwind CSS with semantic design tokens

Architecture

  • Backend (Python): Located in helpdesk/.
    • API endpoints: helpdesk/api/
    • Business logic: helpdesk/utils.py, helpdesk/search.py, etc.
    • Extensibility: helpdesk/extends/, helpdesk/overrides/
  • Frontend (Vue.js): Located in desk/.
    • Main entry: desk/src/main.js, desk/src/App.vue
    • Components: desk/src/components/
    • State: desk/src/stores/
    • Routing: desk/src/router/
    • Styles: Tailwind via desk/index.css, config in desk/tailwind.config.js

Frontend: Vue 3 Development Standards

Frontend Project Context

  • desk/src/ contains the main Vue 3 frontend source code
  • Vue 3.x with Composition API as default approach
  • TypeScript for type safety and better development experience
  • Single File Components (.vue) with <script setup lang="ts"> syntax preferred
  • Vite as the build tool for fast development and optimized builds
  • Pinia for state management (unlike gameplan which avoids state libraries)
  • frappe-ui for UI components and data fetching utilities

Development Standards

Architecture & Component Design

  • Favor the Composition API (setup functions and composables) over the Options API
  • Organize components and composables by feature or domain for scalability
  • Separate UI-focused components (presentational) from logic-focused components (containers)
  • Use PascalCase for component names and file names
  • Keep components small and focused on one concern
  • Use <script setup lang="ts"> syntax for brevity and performance
  • Favor slots and scoped slots for flexible composition

TypeScript Integration

  • TypeScript is enabled but not in strict mode (as per actual tsconfig.json)
  • Use <script setup lang="ts"> with defineProps and defineEmits
  • Leverage PropType<T> for typed props and default values
  • Use interfaces or type aliases for complex prop and state shapes
  • Define types for event handlers, refs, and useRoute/useRouter hooks

Styling Guidelines

  • Always prefer Tailwind CSS for styling
  • Use utility classes for layout and spacing
  • Use semantic class names wherever possible:
    • Background colors: bg-surface-white, bg-surface-gray-1 through bg-surface-gray-9, bg-surface-black
    • Text colors: text-ink-white, text-ink-gray-1 through text-ink-gray-9, text-ink-black
    • Fill colors: fill-ink-*
    • Placeholder colors: placeholder-ink-*
    • Border colors: border-outline-white, border-outline-gray-1 through border-outline-gray-5, border-outline-black
    • Font sizes: text-xs, text-sm, text-base, text-lg, text-xl, text-2xl, text-3xl
    • Multiline text: text-p-xs, text-p-sm, text-p-base, text-p-lg, text-p-xl, text-p-2xl
  • Always use gray shades for everything, never use color shades even for primary states
  • Implement mobile-first, responsive design with CSS Grid and Flexbox
  • Ensure styles are accessible (contrast, focus states)

Data Fetching with frappe-ui

  • Use createResource, createListResource, and createDocumentResource from frappe-ui for data fetching
  • Handle loading, error, and success states explicitly
  • These are the older but stable composables used throughout the project

When working with a list of documents:

const tickets = createListResource({
  doctype: "HD Ticket",
  filters: { status: "Open" },
  fields: ["name", "subject", "modified"],
  limit: 20,
  auto: true,
});

// Access data
tickets.data; // Array of ticket objects
tickets.loading; // Boolean loading state
tickets.error; // Error object if any

// Operations
tickets.reload(); // Refresh the list
tickets.update({ name: "<id>", subject: "Updated Subject" }); // Update item
tickets.insert({ subject: "New Ticket" }); // Add new item
tickets.delete("<id>"); // Delete item

When working with a single document:

const ticket = createDocumentResource({
  doctype: "HD Ticket",
  name: "<ticket-id>",
  auto: true,
});

// Access data
ticket.doc; // Document object
ticket.loading; // Boolean loading state
ticket.error; // Error object if any

// Operations
ticket.get(); // Fetch/refresh document
ticket.setValue("status", "Resolved"); // Update field
ticket.save(); // Save changes
ticket.delete(); // Delete document

For API calls:

const apiCall = createResource({
  url: "helpdesk.api.ticket.assign_ticket_to_agent",
  onSuccess(data) {
    // Handle success
  },
  onError(error) {
    // Handle error
  },
});

// Make the call
apiCall.submit({ ticket_id: "<id>", agent_id: "<agent>" });

Icons

  • Primary icon system: Lucide icons via unplugin-icons for consistency and modern design
  • Secondary: FeatherIcon component from frappe-ui (legacy usage, prefer Lucide for new components)
  • Use Lucide icons for all new components:
    <template>
      <LucidePlus class="size-4" />
    </template>
    
  • For script usage, import Lucide icons like this:
    import LucidePlus from "~icons/lucide/plus";
    import LucideTicket from "~icons/lucide/ticket";
    
  • Only use FeatherIcon for maintaining existing components:
    <template>
      <FeatherIcon name="plus" class="h-4 w-4" />
    </template>
    

State Management

  • Use Pinia stores for complex shared state
  • For simple local state, use ref and reactive within setup
  • Use computed for derived state
  • Keep state normalized for complex structures

Performance & Accessibility

  • Apply v-once and v-memo for static or infrequently changing elements
  • Avoid unnecessary watchers; prefer computed where possible
  • Use semantic HTML elements and ARIA attributes
  • Manage focus for modals and dynamic content
  • Ensure keyboard navigation for interactive components

Backend: Frappe Framework Development Standards

Backend Project Context

  • Frappe Framework provides full-stack capabilities with ORM, permissions, and background jobs
  • MariaDB database with structured DocTypes for business entities
  • Redis for background workers and caching
  • Socket.IO for real-time updates

Critical Development Workflows

Local Development Setup:

  • Backend runs via bench start from frappe-bench directory
  • Python web server: Assume server is already started on port 8000
  • Frontend dev server: cd desk && yarn dev (typically runs on port 8080)
  • Site-specific commands: bench --site helpdesk.test <command>

Data Query Patterns:

  • Always prefer frappe.qb.get_query() over frappe.db.get_all() for new code
  • Use frappe.qb.get_query(..., ignore_permissions=False) when permission checks are needed
  • Example query patterns from helpdesk context

Debugging Workflow:

  • Create debug files like ./helpdesk/debug.py with def execute(): function
  • Run with bench --site helpdesk.test execute helpdesk.debug.execute
  • Use print() statements for console output during debugging

DocType Architecture Patterns

  • Follow Frappe's DocType conventions for business entities
  • Implement custom permission logic in has_permission hooks
  • Use proper field types and validation in DocType definitions
  • Leverage DocType methods for business logic encapsulation

Controller Code Structure

All controller logic must follow this structure strictly:

Rule: Lifecycle hooks call methods. Methods are defined below on the class.

class HDFoo(Document):
    # 1. Lifecycle hooks at the top — each hook calls methods, no inline logic
    def after_insert(self):
        self.do_a()
        self.do_b()

    def on_update(self):
        self.do_a()

    # 2. Methods below — one responsibility each
    def do_a(self):
        pass

    def do_b(self):
        pass


Rules:

  • Lifecycle hooks (after_insert, on_update, before_save, on_trash, after_rename, etc.) contain no inline logic — they only call self.method().
  • All logic lives in methods on the class, not in module-level helper functions.
  • Methods that mutate a related document (e.g. an Assignment Rule) receive that doc as an argument and mutate it in memory. The caller is responsible for the final doc.save() — this keeps saves to a minimum (one save per hook).
  • Never fetch the same document twice in one hook. Fetch once, pass the doc through the call chain.
  • @staticmethod is allowed for pure transformations (e.g. building a condition string) that don't touch self or any Frappe doc.

Save discipline:

  • Mutate all fields on a document before calling .save() once.
  • Never call .save() inside a helper method — save in the hook or the method that owns the full mutation.
  • Exception: if a method truly owns a complete document lifecycle (e.g. create_assignment_rule creates and fully initialises a new doc), it may save internally. Take inspiration from "helpdesk.doctype.hd_team.hd_team.HDTeam" for examples of this pattern in practice.

Conventions & Patterns

  • API Design: Python modules in helpdesk/api/ follow RESTful patterns. Extend via helpdesk/extends/.
  • Frontend: Vue 3 + Vite with TypeScript. Use Pinia for state management. Components auto-imported via desk/components.d.ts.
  • Testing: Follow Frappe's conventions for backend tests.
  • Localization: Add translations in helpdesk/locale/.
  • Custom Logic: Use helpdesk/overrides/ for customizations.

Utilities and Composables

  • @vueuse/core is available for common Vue utilities
  • Prefer vueuse composables over custom implementations where applicable
  • Common composables: useLocalStorage, useDebounce, useElementSize
  • Always use frappe-ui's data fetching composables (createResource, createListResource, createDocumentResource), never useFetch

Error Handling & Forms

  • Use global error handlers for uncaught errors
  • Wrap risky logic in try/catch with user-friendly messages
  • Build forms with controlled v-model bindings
  • Validate on blur or input with debouncing for performance
  • Ensure accessible labeling and error announcements

Integration Points

  • Frappe Framework: All backend logic integrates with Frappe ORM, permissions, and hooks (helpdesk/hooks.py).
  • Frappe UI: Frontend uses Frappe UI for consistent design components and data fetching.
  • Real-time Updates: WebSockets via Socket.IO for live ticket updates.
  • Crowdin: Translation sync via crowdin.yml.

Examples

Adding a New API Endpoint

  1. Create a Python file in helpdesk/api/
  2. Add @frappe.whitelist() decorator to functions
  3. Register in helpdesk/hooks.py if needed
  4. Use frappe.qb.get_query() for database queries

Adding a Frontend Component

  1. Create Vue component in desk/src/components/ or desk/src/pages/
  2. Use <script setup lang="ts"> syntax
  3. Follow Tailwind semantic class conventions
  4. Use frappe-ui composables for data fetching
  5. Add route in desk/src/router/ if it's a page component

Styling Example

<template>
  <div class="bg-surface-white border border-outline-gray-2 rounded-lg p-4">
    <h2 class="text-lg text-ink-gray-9 font-semibold mb-2">Ticket Details</h2>
    <p class="text-p-sm text-ink-gray-7">{{ ticket.doc.subject }}</p>
    <LucideTicket class="size-4 text-ink-gray-6" />
  </div>
</template>

<script setup lang="ts">
import { createDocumentResource } from "frappe-ui";
import LucideTicket from "~icons/lucide/ticket";

const ticket = createDocumentResource({
  doctype: "HD Ticket",
  name: props.ticketId,
  auto: true,
});
</script>

Key Files & Directories

Backend

  • helpdesk/api/ — Backend API endpoints with @frappe.whitelist() decorators
  • helpdesk/utils.py — Shared backend utilities and helper functions
  • helpdesk/hooks.py — Frappe hooks
  • helpdesk/overrides/ — Custom logic and extensions

Frontend

  • desk/src/ — Main Vue 3 frontend source code
  • desk/src/components/ — Reusable Vue components
  • desk/src/pages/ — Page-level components and routes
  • desk/src/stores/ — Pinia stores for state management
  • desk/tailwind.config.js — Tailwind CSS configuration with semantic tokens
  • desk/components.d.ts — Auto-generated component type definitions

Development

  • docker/ — Docker setup for containerized development

Code Quality & Standards

  • Follow Vue's official style guide and best practices
  • Use ESLint with Vue 3 recommended rules and Prettier for consistency
  • Write meaningful commit messages and maintain clean git history
  • Document complex logic with JSDoc/TSDoc comments
  • Only add comments that explain why something is done, not what is done
  • Keep dependencies updated and audit for security vulnerabilities

References


Agent Working Rules

These rules apply to all agents working in this codebase. Follow them without exception.

1. Site Data — Read Before You Write

When a Frappe site is referenced in the conversation (e.g. erp-hd.localhost, hd-tests), treat it as the source of truth for current data state.

  • Before assuming anything about existing records, custom fields, settings, or DocType state — query the site first using the bench venv Python or bench execute.
  • Never write data to a site unless the user explicitly asks you to. Querying is always safe. Mutations (insert, update, delete, migrate, patch) require explicit user instruction.
  • If you need to verify a fix worked, re-query the site — do not assume success from code inspection alone.
# Safe pattern for querying a site
import sys, os
sys.path.insert(0, 'apps/frappe')
os.chdir('sites')
import frappe
frappe.init(site='<site-name>', sites_path='.')
frappe.connect()

# ... read-only queries here ...

frappe.destroy()

Run via: cd /path/to/frappe-bench && env/bin/python /tmp/your_script.py


2. Spec-Driven Development — Keep the Spec Current

When a feature has a spec document (e.g. docs/erpnext-customer-sync.md), the spec is the living source of truth for that feature. It must be kept in sync with the actual implementation at all times.

Rules:

  • Update the spec in the same session as the code change. Never defer spec updates to a later session.
  • The spec reflects what is implemented, not what was originally planned. If a decision changes mid-build (e.g. removing a scheduler job, changing how a field is set), update the spec immediately after the code change.
  • Removed features must be removed from the spec. Do not leave stale sections, blockers, or file manifest entries for things that no longer exist.
  • New features must be added to the spec. If a new API, component, or behaviour is introduced, add a corresponding section.
  • Decisions and their rationale belong in the spec. If something was done a specific way to avoid a bug or framework limitation (e.g. not setting customer_group to avoid the group-node error), document that in the relevant Blocker section.
  • When in doubt about whether a spec update is needed — update it.

3. Test Utilities — Always Use helpdesk/test_utils.py

All backend test helpers must live in helpdesk/test_utils.py and be imported from there. Never define helper functions (factories, enable/disable toggles, fixture builders) directly inside individual test files.

Rules:

  • Before writing a helper in a test file, check helpdesk/test_utils.py first — it may already exist.
  • If a helper doesn't exist, add it to helpdesk/test_utils.py and import it in the test file. Do not leave it inline.
  • If you write helpers inline during development, move them to test_utils.py before finishing the task.
  • Helper functions in test_utils.py must have a docstring explaining what they create or do. Only if the function is completely self-explanatory (e.g. enable_erpnext_sync) can the docstring be omitted.
  • Naming conventions:
    • Factories: make_<doctype>(...) — e.g. make_hd_customer, make_ticket, make_team
    • Toggle helpers: enable_<feature>() / disable_<feature>() — e.g. enable_erpnext_sync
    • Query helpers: descriptive verb — e.g. get_latest_ticket_communication

For updates, merge new conventions here. If anything is unclear or missing, ask for clarification.

In the end your code will be reviewed by Codex, but following these rules will help ensure a smooth review process and a high-quality codebase.