Skip to content

Commit 4bfa034

Browse files
authored
Merge pull request #14207 from rtibbles/0.19intodevelop
0.19 into develop
2 parents d691cfb + 2269a2c commit 4bfa034

127 files changed

Lines changed: 12269 additions & 588 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/add_contributor.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ jobs:
5050
5151
- name: GitHub App token
5252
if: env.new_author == 'true'
53-
uses: tibdex/github-app-token@v2
53+
uses: actions/create-github-app-token@v2
5454
id: generate-token
5555
with:
56-
app_id: ${{ secrets.LE_BOT_APP_ID }}
57-
private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
56+
app-id: ${{ secrets.LE_BOT_APP_ID }}
57+
private-key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
5858

5959
- name: Push changes to develop
6060
if: env.new_author == 'true'

.github/workflows/i18n-download.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ jobs:
5757

5858
- name: Generate App Token
5959
id: generate-token
60-
uses: tibdex/github-app-token@v2
60+
uses: actions/create-github-app-token@v2
6161
with:
62-
app_id: ${{ secrets.LE_BOT_APP_ID }}
63-
private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
62+
app-id: ${{ secrets.LE_BOT_APP_ID }}
63+
private-key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
6464

6565
- name: Create Pull Request
6666
uses: peter-evans/create-pull-request@v8

.github/workflows/update_h5p.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ jobs:
9090
- name: Generate App Token
9191
if: steps.check-commit.outputs.changed == 'true' && steps.check-build-changes.outputs.has_build_changes == 'true'
9292
id: generate-token
93-
uses: tibdex/github-app-token@v2
93+
uses: actions/create-github-app-token@v2
9494
with:
95-
app_id: ${{ secrets.LE_BOT_APP_ID }}
96-
private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
95+
app-id: ${{ secrets.LE_BOT_APP_ID }}
96+
private-key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
9797

9898
- name: Create Pull Request
9999
if: steps.check-commit.outputs.changed == 'true' && steps.check-build-changes.outputs.has_build_changes == 'true'

.gitignore

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@ kolibri/locale/.crowdin-download-marker
5656
.project
5757
# vscode files
5858
.vscode
59-
# pycharm project file
60-
kolibri.iml
59+
# pycharm / jetbrains project files
60+
/*.iml
6161
.pydevproject
62-
.venv
63-
.idea
62+
.idea/
63+
.junie/
64+
.aiassistant/
65+
.aiignore
6466

6567
# Complexity
6668
output/*.html
@@ -105,6 +107,7 @@ kolibri/core/content/contentschema/migrations/*
105107

106108
# virtual environment
107109
venv/
110+
.venv/
108111
.python-version
109112
.envrc
110113
.env
@@ -126,6 +129,7 @@ build_tools/crowdin-cli.jar
126129
# Ignore pytest cache directory
127130
.pytest_cache/
128131
.pytest_kolibri_home
132+
.kolibri_home
129133

130134
# ignore source font files
131135
*.ttf

AGENTS.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<!-- Generic guidance for all coding agents (Claude Code, Zed, Cursor, etc.) -->
2+
3+
# Kolibri Development Guide for AI Coding Agents
4+
5+
**Project:** Kolibri - Offline learning platform for low-resource communities
6+
**Stack:** Python/Django backend, Vue.js 2.7 frontend, pytest/Jest testing
7+
**Platforms:** Linux, Windows, Mac, Android (via python-for-android)
8+
9+
## Quick Start
10+
11+
```bash
12+
pip install -r requirements/dev.txt # Python deps
13+
pnpm install # Node deps
14+
pre-commit install # Required — commits fail without this
15+
export KOLIBRI_RUN_MODE=dev
16+
kolibri manage migrate # Database migrations
17+
```
18+
19+
Dev servers (run in separate terminals):
20+
```bash
21+
pnpm run python-devserver # Django on port 8000
22+
pnpm run watch # Webpack watcher
23+
```
24+
25+
→ Full setup: `docs/getting_started.rst` | Architecture: `docs/stack.rst`
26+
27+
## Critical Gotchas
28+
29+
### ⚠️ BEFORE Writing Any Vue Component, Search for Existing Ones
30+
Do not create a new component without first searching for an existing solution:
31+
1. **Kolibri Design System** ([docs](https://design-system.learningequality.org/)) — `KButton`, `KCircularLoader`, `KTextbox`, `KSelect`, `KModal`, `KCheckbox`, `KIcon`, etc.
32+
2. **`packages/kolibri/components/`**`CoreTable`, `AuthMessage`, `BottomAppBar`, `AppBar`, etc.
33+
3. **`packages/kolibri-common/components/`**`AccordionContainer`, `BaseToolbar`, etc.
34+
35+
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.
36+
37+
### ⚠️ Use Theme Tokens, Not Hard-Coded Colors
38+
Never use raw color values. Access theme colors via `$themeTokens` and `$themePalette`:
39+
```vue
40+
<template>
41+
<div :style="{ color: $themeTokens.text, backgroundColor: $themeTokens.surface }">
42+
<span :style="{ color: $themeTokens.annotation }">secondary text</span>
43+
</div>
44+
</template>
45+
```
46+
For computed dynamic styles, use `$computedClass`. See `docs/frontend_architecture/core.rst`.
47+
48+
### ⚠️ Style Blocks, Not Inline — RTL Depends On It
49+
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`
50+
51+
### ⚠️ Composition API, Not Options API
52+
New components must use `setup()`. Do not use Options API (`data()`, `computed:`, `methods:`).
53+
54+
### ⚠️ No New Vuex — Use Composables
55+
Vuex is deprecated. Use Vue composables for state. → `docs/frontend_architecture/composables.rst`, `docs/frontend_architecture/vuex.rst`
56+
57+
### ⚠️ Use `responsive-window` / `responsive-element`, Not Media Queries
58+
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.
59+
60+
### ⚠️ Internationalize All User-Visible Text
61+
Use `createTranslator` — never hard-code strings in templates:
62+
```javascript
63+
const strings = createTranslator('QuizStrings', {
64+
title: { message: 'Quiz Results', context: 'Page heading' },
65+
});
66+
// In setup(), destructure with $ suffix:
67+
const { title$ } = strings; // title$() returns translated string
68+
```
69+
70+
### ⚠️ API Calls via Resource Classes Only
71+
Use `Resource` from `kolibri/apiResource`. Define in `apiResources.js`. Never use raw `fetch` or `axios`.
72+
73+
### ⚠️ Backend APIs: Use ValuesViewset
74+
Use `ValuesViewset` (or `ReadOnlyValuesViewset`) from `kolibri.core.api` for new API endpoints — not `ModelViewSet`, `ViewSet`, or `GenericViewSet`:
75+
```python
76+
from kolibri.core.api import ReadOnlyValuesViewset
77+
78+
class MyViewSet(ReadOnlyValuesViewset):
79+
values = ("id", "title", "description")
80+
# Define values tuple and annotate_queryset for computed fields
81+
```
82+
Viewset permissions use `KolibriAuthPermissions` from `kolibri.core.auth.api`. See `docs/backend_architecture/api_patterns.rst`.
83+
84+
### ⚠️ Testing is Required
85+
- **Python:** pytest is the test runner. Django API tests extend `APITestCase` from `rest_framework.test`. Other Django tests extend `django.test.TestCase`. Only use bare pytest-style function tests for non-Django code.
86+
- **Frontend:** Jest runner + Vue Testing Library. Do NOT import from `vitest` or `@vue/test-utils`. `describe`/`it`/`expect` are Jest globals (no import needed). Use `jest.fn()` and `jest.mock()`:
87+
```javascript
88+
import { render, screen } from '@testing-library/vue';
89+
// describe, it, expect are Jest globals — do NOT import them
90+
describe('MyComponent', () => {
91+
it('renders', () => {
92+
render(MyComponent, { props: { title: 'Hello' } });
93+
expect(screen.getByText('Hello')).toBeTruthy();
94+
});
95+
});
96+
```
97+
- **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.
98+
99+
### ⚠️ Pre-commit Auto-fixes Files
100+
When a commit fails: pre-commit auto-fixes files → **`git add` the fixed files** → re-commit.
101+
102+
## Project Structure
103+
104+
```
105+
kolibri/
106+
├── kolibri/core/ # Core modules: auth/, content/, device/, lessons/, exams/, logger/, tasks/
107+
├── kolibri/plugins/ # Frontend plugins: learn/, coach/, facility/, ...
108+
│ └── <plugin>/ # api_urls.py, viewsets.py, kolibri_plugin.py, test/
109+
│ └── frontend/ # app.js, views/, composables/, routes/, __tests__/
110+
├── packages/ # JS packages: kolibri/, kolibri-common/, kolibri-tools/
111+
├── docs/ # Developer docs (architecture, testing, i18n, etc.)
112+
├── requirements/ # Python deps
113+
└── test/ # Test utilities and fixtures
114+
```
115+
116+
→ See `docs/backend_architecture/plugins.rst` for plugin layout and core-vs-plugins decision guide
117+
118+
## Code Quality
119+
120+
→ 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.
121+
122+
## Key Conventions
123+
124+
**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_`).
125+
126+
**Vue:** PascalCase filenames. Component `name` must match filename. Use `computed()` for derived values.
127+
128+
**Git:** Imperative commit messages, no conventional-commit prefixes. Logical commit ordering for review. Black/Prettier enforced by pre-commit.
129+
130+
**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/`.
131+
132+
## Running Tests
133+
134+
```bash
135+
pytest kolibri/path/to/test/ # Python (directory)
136+
pytest kolibri/core/auth/test/ -k test_login # Python (filter by name)
137+
pnpm run test-jest -- path/to/file.spec.js # Frontend (single file)
138+
pnpm run test-jest -- --testPathPattern learn # Frontend (filter by pattern)
139+
pre-commit run --all-files # Lint
140+
```
141+
142+
## Docs Reference
143+
144+
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/

CLAUDE.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
@AGENTS.md
2+
3+
## Extended Conventions
4+
5+
These supplement the gotchas in AGENTS.md. With Claude's large context window, the additional detail has negligible cost.
6+
7+
### Code Quality Principles
8+
9+
- **Compute, don't store**: Don't add DB fields derivable from other fields. Use `computed()` in Vue, `annotate_queryset` in ValuesViewset.
10+
- **Let errors propagate**: Don't wrap calls in try/catch that just log and rethrow. DRF's exception handling catches unhandled exceptions.
11+
- **Composition over inheritance**: Prefer composables over mixins, delegation over subclassing. Reserve inheritance for true is-a relationships.
12+
- **Tell, don't ask**: Don't inspect state → decide → update. Tell the object what to do.
13+
- **Tests assert behavior, not implementation**: Mock only at hard boundaries (network, filesystem, external services).
14+
- **Follow project vocabulary**: Use `Collection`, `ContentNode`, `Facility`, `FacilityUser`. Don't introduce synonyms.
15+
- **Escalate unclear decisions**: If an architectural choice isn't covered by docs or existing patterns, ask rather than deciding independently.
16+
- **Don't weaken existing tests**: Only modify tests when the tested behavior has intentionally changed.
17+
- **Small interfaces**: If something can be private, it must be.
18+
- **Externalize configuration**: Use Django settings or `kolibri.utils.conf.OPTIONS`, not hardcoded values.
19+
- **Accessibility**: `aria-*` attributes on interactive elements. Keyboard navigation must work.
20+
- **Identical code is not always duplication**: Only deduplicate when the knowledge is genuinely the same, not just when code looks similar.
21+
- **Keep code simple**: Prefer the simplest solution that achieves the goal. Code should be readable without extensive comments.
22+
- **DRY, but avoid premature abstraction**: Don't abstract too early — wait until a pattern appears at least three times (Rule of Three).
23+
- **Complete your refactors**: When changing a function signature, API, or pattern, update all usages — not just the one you're working on.
24+
- **Security**: API endpoints must have appropriate authentication and permissions. Validate submitted data. Don't bypass security practices (e.g., raw SQL instead of ORM queries).
25+
- **One concern, one layer**: Don't reimplement validation, error handling, or permission logic that already exists at another layer.
26+
- **Preserve existing comments**: Don't strip comments to "clean up." Only remove when the described code is deleted or the comment is provably incorrect.
27+
- **Don't rely on undocumented behavior**: If a behavior isn't in the API contract or language spec, don't depend on it.
28+
- **Whoever allocates a resource releases it**: Use context managers in Python (`with`), `onUnmounted` cleanup in Vue composables.
29+
30+
→ See `docs/code_quality.rst` for detailed Kolibri-specific examples
31+
32+
### Python Conventions (Extended)
33+
34+
- **Logging**: `logger = logging.getLogger(__name__)` at module level.
35+
- **Constants**: Uppercase strings in dedicated modules with `choices` tuples for model fields (see `kolibri/core/auth/constants/`).
36+
- **Model permissions**: Syncable models use declarative `RoleBasedPermissions`. Viewsets use `KolibriAuthPermissions` from `kolibri.core.auth.api`.
37+
- **Error constants**: API validation errors use codes from `kolibri/core/error_constants.py`, mirrored in frontend.
38+
- **Inline imports**: Only for circular import prevention. All other imports at file top.
39+
40+
### Multi-Agent / Multi-Worktree Isolation
41+
42+
→ See `docs/howtos/multi_agent_setup.md` for full setup including KOLIBRI_HOME isolation, unique ports, and provisioning commands.

MANIFEST.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ include README.md
66
include kolibri/VERSION
77
recursive-include kolibri/locale *.mo
88
recursive-include kolibri/locale *.json
9-
recursive-include kolibri/core *
9+
graft kolibri/core
1010
recursive-include kolibri/deployment *
1111
recursive-include kolibri/dist *
1212

@@ -25,7 +25,7 @@ recursive-exclude kolibri/dist/django/contrib/flatpages/locale *
2525
recursive-exclude kolibri/dist/django/contrib/sessions/locale *
2626
recursive-exclude kolibri/dist/django/contrib/admin/locale *
2727

28-
recursive-include kolibri/plugins *
28+
graft kolibri/plugins
2929
recursive-include kolibri/utils *
3030
recursive-include kolibri/*/static *.*
3131
recursive-include kolibri/*/build/ *.json

0 commit comments

Comments
 (0)