Project: Kolibri - Offline learning platform for low-resource communities Stack: Python/Django backend, Vue.js 2.7 frontend, pytest/Jest testing Platforms: Linux, Windows, Mac, Android (via python-for-android)
pip install -r requirements/dev.txt # Python deps
pnpm install # Node deps
pre-commit install # Required — commits fail without this
export KOLIBRI_RUN_MODE=dev
kolibri manage migrate # Database migrationsDev servers (run in separate terminals):
pnpm run python-devserver # Django on port 8000
pnpm run watch # Webpack watcher→ Full setup: docs/getting_started.rst | Architecture: docs/stack.rst
Do not create a new component without first searching for an existing solution:
- Kolibri Design System (docs) —
KButton,KCircularLoader,KTextbox,KSelect,KModal,KCheckbox,KIcon, etc. packages/kolibri/components/—CoreTable,AuthMessage,BottomAppBar,AppBar, etc.packages/kolibri-common/components/—AccordionContainer,BaseToolbar, etc.
Use existing components (e.g., CoreTable for tabular data, KCircularLoader for loading states). If one does 80% of what you need, wrap it — do not rewrite.
Never use raw color values. Access theme colors via $themeTokens and $themePalette:
<template>
<div :style="{ color: $themeTokens.text, backgroundColor: $themeTokens.surface }">
<span :style="{ color: $themeTokens.annotation }">secondary text</span>
</div>
</template>For computed dynamic styles, use $computedClass. See docs/frontend_architecture/core.rst.
Non-dynamic styles go in <style> blocks. RTLCSS auto-flips directional properties (padding-left → padding-right) in style blocks but cannot flip inline styles. Dynamic directional styles must check isRtl. → docs/i18n.rst
New components must use setup(). Do not use Options API (data(), computed:, methods:).
Vuex is deprecated. Use Vue composables for state. → docs/frontend_architecture/composables.rst, docs/frontend_architecture/vuex.rst
Do not use CSS @media queries. Kolibri runs on Android and varied screen sizes. Use the responsive-window or responsive-element system for responsive layouts.
Use createTranslator — never hard-code strings in templates:
const strings = createTranslator('QuizStrings', {
title: { message: 'Quiz Results', context: 'Page heading' },
});
// In setup(), destructure with $ suffix:
const { title$ } = strings; // title$() returns translated stringUse Resource from kolibri/apiResource. Define in apiResources.js. Never use raw fetch or axios.
Use ValuesViewset (or ReadOnlyValuesViewset) from kolibri.core.api for new API endpoints — not ModelViewSet, ViewSet, or GenericViewSet:
from kolibri.core.api import ReadOnlyValuesViewset
class MyViewSet(ReadOnlyValuesViewset):
values = ("id", "title", "description")
# Define values tuple and annotate_queryset for computed fieldsViewset permissions use KolibriAuthPermissions from kolibri.core.auth.api. See docs/backend_architecture/api_patterns.rst.
- Python: pytest is the test runner. Django API tests extend
APITestCasefromrest_framework.test. Other Django tests extenddjango.test.TestCase. Only use bare pytest-style function tests for non-Django code. - Frontend: Jest runner + Vue Testing Library. Do NOT import from
vitestor@vue/test-utils.describe/it/expectare Jest globals (no import needed). Usejest.fn()andjest.mock():import { render, screen } from '@testing-library/vue'; // describe, it, expect are Jest globals — do NOT import them describe('MyComponent', () => { it('renders', () => { render(MyComponent, { props: { title: 'Hello' } }); expect(screen.getByText('Hello')).toBeTruthy(); }); });
- TDD: Write a failing test first, then make it pass. This is especially important for bug fixes — always write a test that reproduces the bug before fixing it.
When a commit fails: pre-commit auto-fixes files → git add the fixed files → re-commit.
kolibri/
├── kolibri/core/ # Core modules: auth/, content/, device/, lessons/, exams/, logger/, tasks/
├── kolibri/plugins/ # Frontend plugins: learn/, coach/, facility/, ...
│ └── <plugin>/ # api_urls.py, viewsets.py, kolibri_plugin.py, test/
│ └── frontend/ # app.js, views/, composables/, routes/, __tests__/
├── packages/ # JS packages: kolibri/, kolibri-common/, kolibri-tools/
├── docs/ # Developer docs (architecture, testing, i18n, etc.)
├── requirements/ # Python deps
└── test/ # Test utilities and fixtures
→ See docs/backend_architecture/plugins.rst for plugin layout and core-vs-plugins decision guide
→ See docs/code_quality.rst for detailed principles. Key: tests assert behavior not implementation, composition over inheritance, let errors propagate, don't weaken existing tests, compute don't store, tell don't ask.
Python: F-strings preferred. One import per line. DateTimeTzField for timestamps (not Django's DateTimeField). UUIDField from morango for syncable models. Descriptive migration names (no _auto_).
Vue: PascalCase filenames. Component name must match filename. Use computed() for derived values.
Git: Imperative commit messages, no conventional-commit prefixes. Logical commit ordering for review. Black/Prettier enforced by pre-commit.
Don't guess — look at existing code for patterns: docs/backend_architecture/api_patterns.rst, docs/frontend_architecture/, existing test files in __tests__/ or test/.
pytest kolibri/path/to/test/ # Python (directory)
pytest kolibri/core/auth/test/ -k test_login # Python (filter by name)
pnpm run test-jest -- path/to/file.spec.js # Frontend (single file)
pnpm run test-jest -- --testPathPattern learn # Frontend (filter by pattern)
pre-commit run --all-files # Lint (all files)
pre-commit run --files path/to/File.vue # Lint (specific file)Always use pre-commit as the single entry point for linting — do not invoke ESLint or other linters directly.
Testing: docs/testing.rst, docs/frontend_architecture/unit_testing.rst, docs/backend_architecture/testing.rst | Frontend arch: docs/frontend_architecture/ | Backend arch: docs/backend_architecture/ | i18n: docs/i18n.rst | Code quality: docs/code_quality.rst | How-tos: docs/howtos/ | Workflow: docs/development_workflow.rst | Multi-agent setup: docs/howtos/multi_agent_setup.md | User docs: https://kolibri.readthedocs.io/