Jump to
↵select↑↓navigateescclose

Development

Develop ERPNext TaskView

Set up a bench checkout, build the Vue and frappe-ui bundles, run the Python tests and JavaScript checks, and see what CI and pre-commit enforce.

Updated
On this page

This page is for people changing the app. How it works explains the design behind the code, and the changelog says where to find what changed.

Set up

You need a bench with Frappe v16 and ERPNext, plus Node 24 or newer and yarn. Work in the app's folder inside the bench, because the build writes into the bench's sites/assets:

cd apps/erpnext_taskview
yarn install
yarn build

yarn build builds the desk bundles and the portal, then runs scripts/vite-register.mjs, which registers the bundles in the bench's sites/assets/assets.json. It finds the bench from the app folder (apps/erpnext_taskview) or $FRAPPE_BENCH_ROOT, and skips itself outside one. The first bench build of the app installs the dependencies and runs the same command: see Install and upgrade.

Commands

Run these from the app folder.

CommandWhat it does
yarn buildBuilds the desk bundles and the portal, then updates sites/assets/assets.json
yarn devRebuilds the Task View bundle whenever a file changes
yarn dev:dockRebuilds the timer dock bundle whenever a file changes
yarn dev:portalRuns the portal on the Vite dev server, proxied to the bench
yarn build:taskviewBuilds the Task View bundle only
yarn build:timerdockBuilds the timer dock bundle only
yarn build:portalBuilds the portal only
yarn typecheckType-checks the desk code and the portal code
yarn lint, yarn lint:fixRuns oxlint on erpnext_taskview/public/js and portal/src
yarn format, yarn format:checkRuns oxfmt on the same two folders

The Python tests are run with bench: see Tests.

yarn dev:portal finds the bench by walking up from the app folder to a folder that holds sites and apps, reads the web server port from sites/common_site_config.json (8000 when there is none), and serves the portal on the matching Vite port: 8080 for web port 8000, 8081 for 8001, and so on. It proxies /api, /app, /assets and the other Frappe routes to the bench. Under the dev server it takes its boot data from erpnext_taskview.www.projects.get_context_for_dev instead of the Jinja page, and that method answers only on a site in developer mode.

Where things live

erpnext_taskview/
  hooks.py                          app hooks: bundles, class overrides, portal routes
  install.py                        creates the Client Portal Notification Type
  patches/v1_0/setup_client_portal.py   one-time portal switch-over for existing sites
  erpnext_taskview/                 the app's module
    api.py                          desk endpoints: tree data, saves, timers, pinning
    budget.py                       budget and logged-hours rollup
    models.py                       Pydantic models of the desk API
    custom/                         custom fields, property settings and class overrides
  portal/                           portal API
    access.py                       who may open what
    api.py                          endpoints behind /projects
    html.py                         sanitising rich text in and out
    notify.py                       notifying the team
    models.py                       Pydantic models of the portal API
  www/projects.py                   page shell and boot data for the portal
  public/js/                        desk bundles: TaskView.vue, components/, timer store
  tests/                            Python tests and their builders
portal/                             the portal app (Vue 3, frappe-ui)
vite.config.ts                      desk bundles
vite.portal.config.ts               portal build
scripts/vite-register.mjs           registers the Vite bundles in sites/assets/assets.json (managed)

How the build fits together

  • Desk bundles. vite.config.ts builds two IIFE bundles from erpnext_taskview/public/js: taskview.entry.ts and timerdock.entry.ts. The sources are not named *.bundle.*, so Frappe's esbuild leaves them to Vite. Frappe is declared external, because the desk provides it as a global. Both write into erpnext_taskview/public/dist one after the other, so neither empties the folder. scripts/vite-register.mjs, which frappe-nix manages, then reads the Vite manifests and writes the taskview.bundle.js, taskview.bundle.css, timerdock.bundle.js and timerdock.bundle.css keys that hooks.py lists in app_include_js and app_include_css into the bench's sites/assets/assets.json. Outside a bench it skips itself, so yarn build works in a bare checkout.

  • Portal. vite.portal.config.ts builds portal/ into erpnext_taskview/public/portal/ and copies its index.html to erpnext_taskview/www/projects.html. Frappe renders that file as a Jinja template with the context from www/projects.py, and frappe-ui's build plugin turns every key of context.boot into a window global. Both outputs are generated and git-ignored. Do not name portal files *.bundle.*: Frappe's esbuild compiles every such file under public/ on its own.

  • Two TypeScript versions. The app's typescript is the native TypeScript 7 build, which has no JavaScript API. vue-tsc and Vue's single-file-component compiler need one, so typescript5 is an alias of TypeScript 5. scripts/vue-tsc.cjs runs vue-tsc against it, and vite.portal.config.ts registers it with the SFC compiler, which frappe-ui's components need to resolve their prop types.

  • Frappe's types. The desk code is checked against frappe-types (tsconfig.json extends its strict preset and loads its frappe global). What the app hangs on frappe's namespaces (frappe.views.TasksView, frappe.views.TaskViewSelect, frappe.ui.TaskViewSortSelector) is declared in types/erpnext_taskview.augment.d.ts, together with the one frappe member frappe-types does not declare yet.

  • Tailwind 3. frappe-ui is still a Tailwind 3 consumer, so Dependabot is told to ignore Tailwind 4 and later.

Tests

The Python tests are Frappe IntegrationTestCases in erpnext_taskview/tests/, with record builders in fixtures.py:

FileCovers
test_api.pyThe desk endpoints: filters and sorting, every save_doc route (projects, tasks, drag and drop, timers, manual logs), bulk creation, deleting, assigning and pinned order
test_hooks.pyThe install hook, the hooks the app registers, and the Task extension's permission bypass
test_budget.pyRollup of budgets and logged hours, own estimates beating subtask sums, Cancelled Timesheets, open timers, rollup by parent_task, and a parent cycle that must not hang
test_portal.pyWho can open what, template tasks hidden, project and task reads, status changes and closing assignments, creating tasks, comments, attachments, boot data and the legacy link redirect
test_pinned.pyQuick entry, placing after an anchor, giving a task a project, and the cases that are refused
test_timesheet_guard.pyA submitted Timesheet never moves a task backwards

Frappe only runs tests on a site that allows them. Use a development or test site, never production:

bench --site <TEST_SITE> set-config allow_tests true
bench --site <TEST_SITE> run-tests --app erpnext_taskview
bench --site <TEST_SITE> run-tests --module erpnext_taskview.tests.test_budget

In the frappe-nix dev shell, frappe-test --reuse-site runs the same tests with coverage of the app alone (the floor is fail_under in pyproject.toml), checks that every whitelisted method and hook ran, and that the controller extensions compose with other apps'.

There are no JavaScript unit tests. yarn typecheck, yarn lint and yarn format:check are the front-end gates.

Continuous integration

The check workflow (.github/workflows/check.yml) runs on every pull request and every push to main. Its build job uses Node 24 and runs, in order:

  1. yarn install --frozen-lockfile

  2. yarn typecheck, yarn lint and yarn format:check

  3. yarn build, whose scripts/vite-register.mjs step finds no bench in CI and skips itself

  4. A check that erpnext_taskview/www/projects.html exists and does not contain .__, because Frappe renders it as a Jinja template and refuses any template that does

Dependabot updates the JavaScript dependencies daily, in one group, and a workflow turns on auto-merge for its pull requests, so a pull request merges once its required checks pass.

The Docs workflow (.github/workflows/docs.yml) checks this documentation when docs/ changes. It is described below.

Conventions

  • Server code uses the Frappe ORM and Query Builder only, never raw SQL.

  • Python is formatted with ruff: tabs, double quotes and a line length of 110. Every source file starts with the copyright header the validate_copyright pre-commit hook checks.

  • The API shapes are written twice, in Pydantic and in TypeScript, and kept in step by hand: erpnext_taskview/erpnext_taskview/models.py with erpnext_taskview/public/js/types.ts, and erpnext_taskview/portal/models.py with portal/src/types.ts. Change both together.

  • The Python code declares no dependencies of its own. It uses Pydantic, nh3 and BeautifulSoup, which Frappe v16 already depends on.

  • Patches are listed in patches.txt. A fresh install marks every patch as done without running it, so anything a new site needs must also happen in after_install (install.py): see Install and upgrade.

Pre-commit

.pre-commit-config.yaml is managed by frappe-nix (frappe-init --sync writes it from the app standards profile) and runs with prek. It runs ruff, ssort, oxlint and oxfmt, the standard file checks, workflow and shell linting, the conventional-commit check, frappe-nix compat, and Frappe's test_utils hooks (validate_copyright, validate_patches, static_analysis and others). ruff and the other Python tools are pinned in tools/uv.lock, oxlint and oxfmt in package.json. The dev shell (direnv allow, or nix develop --no-pure-eval) installs the hooks; uv run --frozen --project tools prek run --all-files runs them by hand. The documentation is excluded from the hooks that would otherwise rewrite it: validate_copyright stamps a copyright comment above the frontmatter of every Markdown file, so it skips docs/.

Working on these docs

The documentation is Markdown in docs/. It is published with the other Avunu projects at docs.avunu.net/erpnext-taskview/ by Avunu/site-docs, which picks up the latest release of this repository. Write the files so that they read well on GitHub too: frontmatter, GitHub alerts and relative links.

To check a change as the pull request check does, clone Avunu/site-docs beside this repository and, from its folder (Node 22.19 or newer), run:

npm ci --ignore-scripts && npm run build
node build/cli.js check --only erpnext-taskview --source erpnext-taskview=../erpnext_taskview

Use build --lenient instead of check to write a preview of the site into dist/. See previewing.