Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7ddd631
refactor: optimize mobx usage and performance
lebalz Oct 19, 2026
9243b69
Merge branch 'main' into refactor/mobx-usage
lebalz Oct 19, 2026
1487e3e
update agents
lebalz Oct 19, 2026
a0a726e
update packages
lebalz Oct 19, 2026
0a364a7
add note about keepAlive
lebalz Oct 19, 2026
0a3a2cb
refine agents.md
lebalz Oct 19, 2026
2b5816e
refactor: enhance QuillV2 editor functionality and image resizing cap…
lebalz Oct 19, 2026
f339dbd
refactor: implement custom ImageFormat class and configure Quill regi…
lebalz Oct 19, 2026
fe21c5f
refactor: modularize QuillV2 functionality with custom hooks for docu…
lebalz Oct 19, 2026
c31ad88
refactor: simplify image resizing logic and maintain aspect ratio dur…
lebalz Oct 19, 2026
3160d3e
refactor bubble positioning part1
lebalz Oct 19, 2026
92553e7
refactor: enhance type annotations in useQuillBubbleMenu hook for bet…
lebalz Oct 20, 2026
ab94769
refactor: improve image resize handle functionality and styles
lebalz Oct 20, 2026
0852ad4
refactor: update mobx to version 7.1.0 and better-auth dependencies
lebalz Oct 24, 2026
323f973
add skills section
lebalz Oct 24, 2026
4ff5858
refactor: enhance iDocument state management and cleanup logic
lebalz Oct 24, 2026
63d1a26
refactor: add guideline for positioning private methods in class stru…
lebalz Oct 24, 2026
57bfa35
refactor: streamline abort request handling and improve state managem…
lebalz Oct 24, 2026
758b27b
refactor: simplify apiStateFor computed function in iStore
lebalz Oct 24, 2026
3cc9f9f
refactor: restructure AddUser and ImportGroup components for improved…
lebalz Oct 24, 2026
8f50544
use latest mobx version
lebalz Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .agents/skills/code-style/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
name: code-style
description: How to structure code so that humans can review it. Use whenever you write or modify code (packages, scripts, tests, website), before writing it.
---

# Code style

Humans review all the code of this repository: write code that is easy to read and understand, even if it requires introducing intermediate abstractions.


## Structure code as a tree

Readers should see the high-level behavior first and follow implementation
details only when needed.

- Keep component bodies and public store/model methods focused on their
main responsibility. Delegate substantial implementation details to
well-named subcomponents, hooks, methods, or helper functions.
- Keep each function at a consistent level of abstraction. Leave short
checks and straightforward transformations inline; extract details when
naming them makes the overall flow easier to understand.
- Keep business logic and shared state transitions in MobX stores and
models. Components describe the UI and handle component-specific
interactions. Use computed getters for derived observable state.
- Use early returns for guards and completed branches so the main flow
stays easy to follow.
- Prefer placing an orchestration method before its supporting methods
when introducing a new flow. Preserve existing class structure, keep
related getters and actions together, and respect initialization order.
- Keep extracted code close to its owner and preserve MobX action
boundaries, including observable updates after asynchronous operations.
- Normally, only one component, model or store is defined per file. Multiple components per file are ok when they are small, closely related, not exported and don't have logic of their own (so no hooks, no state, no side effects). Otherwise split them into separate files and import them.
- When a component needs to import dedicated subcomponents, put them in a folder named after the component, with an `index.tsx` and a `styles.module.scss` file.
- When shared styles are needed, import them from the parent folder's `styles.module.scss` file. Avoid global CSS and inline styles.
- Use `clsx` for conditional class names. Avoid inline styles and global CSS.
- private methods should be located after public methods at the bottom of the class.

For example, this method inside `PermissionStore` expresses the decision
to update or create a permission. Persistence and collection updates stay
in the model and store methods it calls:

```ts
@action
createOrUpdateUserPermission(documentRootId: string, user: User, access: Access) {
const existingPermission = this.userPermissionsByDocumentRoot(documentRootId).find(
(permission) => permission.userId === user.id
);
if (existingPermission) {
return existingPermission.setAccess(access);
}
return this.createUserPermission(documentRootId, user, access);
}
```

## Make the intent explicit

- Use well-named intermediate variables and functions to name what a piece of code computes, instead of long expressions or comments.
- Group related lines together with line breaks, and use `{}` blocks when it helps.
- Prefer pure functions, that compute a result from their arguments, over functions with side effects.

## Comments

- Don't comment code that is self-explanatory: when the names, the signature or the code already say it, add no comment.
- Comment the code that is weird, unexpected or surprising (a workaround, a non-obvious constraint, an order that matters): that's where readers need one.
- Keep comments short, one line when possible. Point to the details instead of writing them out: link the issue, PR, documentation or upstream bug that explains them.

```ts
// Uniqueness is guaranteed by the api, let's not check it again here.
// See https://github.com/GBSL-Informatik/teaching-dev/pull/328
```

## Readability over micro-optimizations

Unless the code is on a hot path where performance matters, favor readability over micro-optimizations and clever one-liners. Iterating over a list several times with well-named steps is fine when the list is small.
54 changes: 52 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,25 @@ Teaching Dev is a Docusaurus teaching platform using React, TypeScript, MobX, an
- `src/` contains shared components, hooks, models, stores, API clients, themes, and remark plugins.
- `packages/` contains workspace packages, scoped to either the platform `packages/tdev/` or individual devs, e.g. `packages/hfr/`.
- `docs/`, `blog/`, and `static/` hold content and assets; `website/` contains site customizations and should never be touched in the tdev repository.
- `tdev-website/` contains the platform documentation site and its overrides.
- `tdev-website/` the teaching dev website, built with Docusaurus+Teaching-Dev itself, that serves as project documentation and a way to dogfood the framework.
- `docs/` - The documentation for the "current" version of Teaching-Dev
- `website` - framework-overrides for the Teaching-Dev website itself.
- `updateSync/` contains synchronization tools. Tests live in nearby `tests/` directories.

Refer to ARCHITECTURE.md for detailed architecture documentation.

## Instructions

You're an expert in the following areas:

- TypeScript
- React
- MobX and MobX-React
- Docusaurus
- HTML, SCSS
- Axios
- Socket.io

## Build, Test, and Development Commands

Use Node.js 24.13 or newer (`.nvmrc` pins 24.13.0) and Yarn. Run commands from the repository root.
Expand All @@ -22,7 +38,11 @@ Use Node.js 24.13 or newer (`.nvmrc` pins 24.13.0) and Yarn. Run commands from t
- `yarn test --run`: run Vitest once with V8 coverage; `yarn test` enables watch mode.
- `yarn format:check` / `yarn format`: check or apply Prettier formatting.

## Coding Style & Naming Conventions
## General Guidelines

- Use early returns for readability.
- Emphasize type safety and static analysis.
- Humans review all the code: always write code that is easy to read and review, following the [`code-style` skill](.agents/skills/code-style/SKILL.md).

Follow existing TypeScript/TSX patterns and `.prettierrc`: four-space indentation, single quotes, semicolons, no trailing commas, and a 110-character print width. Markdown and MDX are excluded from repository-wide Prettier checks; format them consistently by hand.

Expand All @@ -32,6 +52,36 @@ New components should normally be put in an `index.tsx` alongside a `styles.modu

Use mobx for state management and only use local reactish component state for component-specific state that does not need to be shared or persisted after unmounting. Use `useStore` to access stores.


## React Usage

- Use functional components with hooks.
- Event handlers should be prefixed with "handle", like "handleClick" for onClick.
- Avoid unnecessary re-renders by using React.memo, useMemo, and useCallback appropriately.
- Use descriptive prop types with TypeScript interfaces.
- Use Infima `--ifm-*` variables or available `--tdev-*` variables for component styling. Introduce new `--tdev-*` variables only when necessary.
- Give stable props to memoized and `observer` components, especially list rows. Do not create new arrays, objects, or JSX on each render; use shared constants for empty arrays.
- Do not read `ref.current` during render to pass it to props or context. Keep the element in state with a callback ref.
- If the initial value of `useState` reads observables, use a lazy initializer. Otherwise the expression runs, and subscribes, on every render.

## MobX State Management

- Use MobX stores for global state management.
- Use `observable`, `action`, and `computed` decorators appropriately.
- Prefer computed values over manual calculations in render.
- Keep business logic in stores, not components.
- Do not read an observable during render only for an effect or a per-item check. Use `useComputed` to derive a narrow value (e.g. `isActive`) so only affected components re-render.
- Use `observable.map` rather than a set when observers check membership by key. `ObservableSet.has()` tracks the whole set, so any change re-renders every observer.
- Store lookups called for each row of a list must not scan a computed list such as `orderedData`, as this subscribes the component to every item in the store.
- A context value provided near the app root must not change on navigation. Expose frequently changing store values as getters read on access, not values read during render.
- All stores which are initialized in the root store and have the lifecycle of the app.

## Skills

Task-specific instructions live in skills, following the [Agent Skills](https://agentskills.io) format. Read the matching `SKILL.md` before starting one of these tasks:

- [`code-style`](.agents/skills/code-style/SKILL.md) - Write or modify code that humans can easily review

## Testing Guidelines

Use tests only for remark plugins or packages which are run from the cli. No tests for components, hooks, or stores. Use Vitest with descriptive `describe` and `it` blocks. Name tests `*.test.ts` under the relevant module's `tests/` directory. Plugin tests commonly use inline snapshots; review snapshot changes for intended output differences. No coverage threshold is configured.
Expand Down
35 changes: 35 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Architecture

Teaching-Dev (tdev) is a static frontend codebase used together with the [teaching-api](https://github.com/GBSL-Informatik/teaching-api/) backend to load dynamic content. We utilize the latest ES6 language features, including `async`/`await`, and types.

## Frontend

Teaching-Dev's frontend is a React application on top of [Docusaurus](https://docusaurus.io/). It uses [MobX](https://mobx.js.org/) for state management and [Infima](https://infima.dev/) for styling components with scss-modules. Unless global, state logic and styles are always co-located with React components together with their subcomponents to make the component tree easier to manage.

Teaching-Dev is a framework for building educational platforms. It provides a set of React components, hooks, and stores to build a platform that can be used by multiple developers/teachers. The platform is built using TypeScript, React, MobX, and Yarn workspaces.

```
.
├── bin - reusable helper bash scripts, focused on creating educational content (e.g. to create an .mp4 from a video)
├── docs - educational content from implementations of the teaching-dev platform - never put content here.
├── packages - additional workspace packages, scoped to either the platform `packages/tdev/` or individual devs, e.g. `packages/hfr/`.
├── src
│ ├── api - axios api endpoints
│ │ └── OfflineApi - supports offline mode by caching API responses in IndexedDB or Memory
│ ├── components - React components provided by the platform
│ └── documents - all components which display `document` records from the teaching-api
│ ├── css - global css files, e.g. for Infima overrides
│ ├── helpers - helper functions, e.g. for date formatting
│ ├── hooks - Reusable React hooks
│ ├── models - State models using MobX observables
│ ├── pages - docusaurus pages
│ ├── plugins - remark- and rehype plugins for docusaurus, provided by the platform
│ ├── siteConfig - docusaurus site configuration for implementing the platform
│ ├── stores - MobX stores - collections of models and associated fetch logic
│ └── theme - docusaurus theme overrides
├── static - static assets
├── tdev-website - the teaching-dev platform documentation site and its overrides. Documentation for the platform should be put here, not in the `docs/` folder.
└── website - customizations and overrides from implementations of the teaching-dev platform. This folder should never be touched in the tdev repository.
```

`packages` are used to provide additional functionality to the platform. They are independent modules that can be added or removed without affecting the core functionality of the platform. Packages are configured in `siteConfig.ts` (or here in teaching-dev, in `tdev-website/siteConfig.ts`). Packages must be present before building the application and can not be dynamically loaded during runtime.
56 changes: 29 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"typecheck": "tsc",
"format": "prettier --write .",
"format:check": "prettier --check .",
"test": "vitest --coverage",
"test": "vitest --coverage --configLoader runner",
"updateTdev": "tsx updateSync/updateTdev.ts",
"sync": "yarn workspace @tdev/material-sync sync",
"remove": "yarn workspace @tdev/material-sync run remove",
Expand All @@ -41,51 +41,51 @@
"@mdi/js": "^7.4.47",
"@mdi/react": "^1.6.1",
"@mdx-js/react": "^3.1.1",
"@mermaid-js/layout-elk": "^0.2.1",
"@mermaid-js/layout-elk": "^1.0.1",
"@pierre/trees": "^1.0.0-beta.6",
"@sentry/react": "^10.64.0",
"@sentry/webpack-plugin": "^3.3.1",
"ace-builds": "^1.44.0",
"axios": "^1.18.1",
"better-auth": "^1.7.4",
"ace-builds": "^1.45.0",
"axios": "^1.20.0",
"better-auth": "^1.7.11",
"browser-image-compression": "^2.0.2",
"clsx": "^2.1.1",
"docusaurus-plugin-sass": "^0.2.6",
"dotenv": "^17.4.2",
"es-toolkit": "^1.49.0",
"dotenv": "^18.0.8",
"es-toolkit": "^1.52.0",
"exceljs": "^4.4.0",
"gray-matter": "^4.0.3",
"hashery": "^3.0.0",
"hashery": "^3.0.5",
"heic2any": "^0.0.4",
"idb": "^8.0.3",
"js-yaml": "^5.2.1",
"idb": "^8.0.4",
"js-yaml": "^5.5.0",
"known-css-properties": "^0.37.0",
"mdast-util-gfm-footnote": "^2.1.0",
"mdast-util-math": "^3.0.0",
"micromark-extension-math": "^3.1.0",
"micromatch": "^4.0.8",
"minimist": "^1.2.8",
"mobx": "^7.0.0",
"mobx-react-lite": "^5.0.0",
"mobx": "^7.1.1",
"mobx-react-lite": "^5.1.0",
"mobx-utils": "^6.1.1",
"qrcode.react": "^4.2.0",
"quill": "^2.0.3",
"raw-loader": "^4.0.2",
"rc-slider": "^11.1.9",
"react": "^19.2.7",
"react": "^19.3.1",
"react-ace": "^15.0.0",
"react-diff-viewer-continued": "^3.4.0",
"react-dom": "^19.2.7",
"react-draggable": "4.5.0",
"react-diff-viewer-continued": "^4.4.0",
"react-dom": "^19.3.1",
"react-draggable": "4.7.2",
"react-katex": "^3.1.0",
"react-quilljs": "^2.0.5",
"react-select": "^5.10.2",
"reactjs-popup": "^2.0.6",
"rehype-katex": "^7.0.1",
"remark-math": "^6.0.0",
"sass": "^1.101.0",
"socket.io-client": "^4.8.3",
"uuid": "^14.0.1"
"sass": "^1.105.2",
"socket.io-client": "^4.8.4",
"uuid": "^14.0.3"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.10.2",
Expand All @@ -97,22 +97,24 @@
"@types/js-yaml": "^4.0.9",
"@types/micromatch": "^4.0.10",
"@types/minimist": "^1.2.5",
"@types/node": "^26.2.0",
"@types/react-dom": "^19.2.3",
"@types/node": "^26.6.5",
"@types/react-dom": "^19.3.0",
"@types/react-katex": "^3.0.4",
"@types/uuid": "^11.0.0",
"@types/wicg-file-system-access": "^2023.10.7",
"@vitest/coverage-v8": "^4.1.10",
"concurrently": "^10.0.3",
"fs-extra": "^11.3.6",
"prettier": "^3.9.4",
"@vitest/coverage-v8": "^5.0.5",
"concurrently": "^10.0.6",
"esbuild": "^0.28.2",
"fs-extra": "^11.4.1",
"prettier": "^3.9.10",
"remark": "^15.0.1",
"remark-directive": "^4.0.0",
"remark-frontmatter": "^5.0.0",
"tsx": "^4.22.4",
"tsx": "^4.23.15",
"typescript": "^7.0.2",
"vfile": "^6.0.3",
"vitest": "^4.1.10",
"vite": "^8.3.0",
"vitest": "^5.0.5",
"yaml-loader": "^0.9.0"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/tdev/brython-code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"main": "index.ts",
"types": "index.ts",
"dependencies": {
"svg-parser": "^2.0.4"
"svg-parser": "^2.1.0"
},
"devDependencies": {
"@docusaurus/core": "*",
Expand Down
2 changes: 1 addition & 1 deletion packages/tdev/excalidoc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"main": "index.ts",
"types": "index.ts",
"dependencies": {
"@excalidraw/excalidraw": "^0.18.0"
"@excalidraw/excalidraw": "^0.18.1"
},
"devDependencies": {
"vitest": "*",
Expand Down
8 changes: 4 additions & 4 deletions packages/tdev/material-sync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/minimist": "^1.2.5",
"@types/node": "^22.0.0",
"@types/node": "^26.6.5",
"@types/rsync": "^0.4.36",
"execa": "^9.6.1",
"js-yaml": "^5.2.1",
"execa": "^10.2.0",
"js-yaml": "^5.5.0",
"minimist": "^1.2.8",
"rsync": "^0.6.1",
"tsx": "^4.20.3",
"tsx": "^4.23.15",
"typescript": "^7.0.2"
}
}
8 changes: 4 additions & 4 deletions packages/tdev/migrate-tdev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/minimist": "^1.2.5",
"@types/node": "^22.0.0",
"execa": "^9.6.1",
"js-yaml": "^5.2.1",
"@types/node": "^26.6.5",
"execa": "^10.2.0",
"js-yaml": "^5.5.0",
"minimist": "^1.2.8",
"tsx": "^4.20.3",
"tsx": "^4.23.15",
"typescript": "^7.0.2"
}
}
4 changes: 2 additions & 2 deletions packages/tdev/page-index/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"devDependencies": {
"@docusaurus/core": "*",
"@docusaurus/module-type-aliases": "*",
"@types/better-sqlite3": "^7.6.13",
"better-sqlite3": "^12.8.0"
"@types/better-sqlite3": "^9.6.0",
"better-sqlite3": "^13.0.3"
},
"peerDependencies": {
"@tdev/core": "1.0.0",
Expand Down
3 changes: 2 additions & 1 deletion src/components/Navbar/AccountSwitcher/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@mdi/js';
import LiveStatusIndicator from '@tdev-components/LiveStatusIndicator';
import Button from '@tdev-components/shared/Button';
import { useComputed } from '@tdev-hooks/useComputed';
import { useStore } from '@tdev-hooks/useStore';
import User from '@tdev-models/User';
import _ from 'es-toolkit/compat';
Expand All @@ -27,7 +28,7 @@ interface SwitchToUserButtonProps {
const SwitchToUserButton = observer(({ user, isInCurrentClass }: SwitchToUserButtonProps) => {
const userStore = useStore('userStore');

const active = userStore.viewedUserId === user.id;
const active = useComputed(() => userStore.viewedUserId === user.id, [userStore, user]);

const icon = user.hasElevatedAccess
? mdiShieldAccount
Expand Down
Loading
Loading