Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
635f16b
refactor(manage): migrate rich text editor from Slate to Tiptap
rschlaefli Jul 7, 2026
e0734c9
feat(editor): add support for GFM tables and code syntax highlighting
rschlaefli Jul 7, 2026
0f99fe7
test(editor): add Playwright E2E spec for Tiptap rich features
rschlaefli Jul 7, 2026
7dc19c1
chore: merge remote-tracking branch 'origin/v3' into migrate-editor-t…
rschlaefli Jul 7, 2026
9830d1f
style: format get-shard-files script
rschlaefli Jul 7, 2026
c6bbe2c
fix(playwright): stabilize flaky answer options accordion and auto-sa…
rschlaefli Jul 7, 2026
a259f06
Merge remote-tracking branch 'origin/v3' into migrate-editor-to-tiptap
rschlaefli Jul 7, 2026
a2e9ba2
docs(wiki): update tech stack overview and playwright testing tips fo…
rschlaefli Jul 7, 2026
dd1cb04
docs(wiki): remove legacy host based stack and simplify agents.md
rschlaefli Jul 8, 2026
1b103dd
chore: merge v3 and address PR review feedback on Tiptap editor and s…
rschlaefli Jul 8, 2026
da9da9b
refactor: use standard data-cy attributes instead of data-testid
rschlaefli Jul 8, 2026
b40fc33
fix(a11y/styles/tests): address Droid review maintainability and acce…
rschlaefli Jul 8, 2026
a6ebc55
docs(project): add PR 5148 finalization plan
rschlaefli Jul 10, 2026
6c1b6eb
chore: merge v3 into Tiptap finalization
rschlaefli Jul 10, 2026
f336930
docs(project): record PR 5148 baseline
rschlaefli Jul 10, 2026
a417373
fix(editor): preserve form state during content sync
rschlaefli Jul 10, 2026
91d680e
test(playwright): stabilize Tiptap editor workflows
rschlaefli Jul 10, 2026
d4b2247
fix(editor): enforce Markdown-safe rich content
rschlaefli Jul 10, 2026
8975d20
fix(editor): synchronize focused external updates
rschlaefli Jul 10, 2026
f4a2736
fix(markdown): align shared preview styles
rschlaefli Jul 10, 2026
ebdb9e6
docs(project): record PR 5148 verification
rschlaefli Jul 10, 2026
fdd2c79
docs(project): finalize PR 5148 evidence
rschlaefli Jul 10, 2026
f2a6c79
test(playwright): focus Tiptap table cell before typing
rschlaefli Jul 10, 2026
9a4c805
test(playwright): align submission switch expectation
rschlaefli Jul 10, 2026
59453d5
refactor(editor): simplify toolbar interactions
rschlaefli Jul 10, 2026
c2849f5
fix(editor): normalize legacy empty content
rschlaefli Jul 11, 2026
9e04380
fix(editor): refresh localized placeholder
rschlaefli Jul 11, 2026
837b057
refactor(editor): simplify final migration diff
rschlaefli Jul 11, 2026
9b18a04
fix(editor): react to Tiptap selection state
rschlaefli Jul 12, 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
1 change: 1 addition & 0 deletions .agents/skills/klicker-frontend-ui/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Conventions (design system, Tailwind v4, Apollo, i18n, CSP): [docs/frontend-conv
- Every new user-visible string in BOTH `packages/i18n/messages/de.ts` and `en.ts`, matching namespace; access via full-path keys (`t('manage.…')`).
- Every new interactive element gets `data-cy` (design-system prop form: `data={{ cy: '…' }}`); pick names consistent with the sibling elements.
- Forms: Formik + Yup. Conditional classes: `twMerge`. Feature flags gate alone — never `flag && count > 0`.
- Rich editors: prove a feature survives the persisted Markdown round-trip before exposing it in the UI; do not infer storage support from the editor document model alone. Represent empty editor content as `''` or `undefined`, never the legacy `'<br>'` sentinel.
- No Next.js middleware for CSP/headers — that belongs at the proxy layer.
3. **Verify in the browser — mandatory, not optional.** Depending on your environment path:
- **Inside Devcontainer:** Dev servers auto-start in the background. No need to start/stop them. View logs via `tail -f /tmp/dev.log`.
Expand Down
7 changes: 6 additions & 1 deletion .agents/skills/klicker-playwright-e2e/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ Action menus:

Editors and selects:

- Use `fillEditorField` and `verifyEditorField` for Slate/rich-text fields.
- Use `.ProseMirror` (with `.first()`/`.last()`) to target the Tiptap rich-text editor DOM.
- For Slate/legacy rich-text fields, `fillEditorField` and `verifyEditorField` helpers remain.
- Use direct `toHaveValue` for ordinary inputs; do not wrap simple fields just for symmetry.
- Use `switchElementType` and `setElementStatus` for element modal dropdowns when applicable.
- Prefer `selectOption` from repo helpers for design-system selects.
Expand Down Expand Up @@ -129,6 +130,10 @@ Cleanup dialogs:
- **react-select**: target the inner `<input>` via `#container-id input` for `.fill()`/`.press()`/visibility assertions — Cypress `.type()` works on the wrapper, Playwright does not. (`playwright/tests/K-elements-selection.spec.ts`)
- **localforage parity**: Playwright creates a fresh context per test (Cypress keeps IndexedDB across `it` blocks). Serial workflows depending on previous PWA answers must snapshot/restore localforage — and direct QR links may need restoration on the `https://pwa.klicker.com` origin, not `127.0.0.1`. (`playwright/util/workflow.ts`)
- **PIN-cookie bridges**: clear test-side PIN cookie bridges whenever the Cypress source clears cookies, or later direct-link checks bypass the expected PIN form via a stale `live-quiz-pin-*` cookie. (`playwright/tests/O-live-quiz.spec.ts`)
- **Tiptap Editor Layout Shifts & Accordion Clicks**: The Tiptap editor mounts asynchronously and shifts the surrounding layout. Before opening an accordion near it, wait for `.ProseMirror`, return when the stable child control is already visible, inspect `aria-expanded`, and click once only when closed. Never retry by blindly toggling the trigger.
- **Tiptap Auto-Save / False Dirty States**: Programmatic synchronization must not emit form updates: use `setContent(..., { emitUpdate: false })` for external content and `setEditable(editable, false)` for disabled-state changes. Normalization is only a comparison aid for deciding whether content differs; it is not a substitute for suppressing programmatic update events.
- **Tiptap Empty State**: Form bindings pass `''` or `undefined`, never `'<br>'`. Verify empty editors through the decorated `p.is-editor-empty[data-placeholder]`; a hard-break node suppresses that placeholder while looking visually blank.
- **Tiptap Code Block Language Selection**: To assert correct syntax highlighting (Highlight.js in the editor, Prism.js in the preview `/questions/[id]`), do not use toolbar button terminal clicks (which omit the language tag). Type fenced Markdown input rules directly into the editor, exit a block with three Enter presses, and cover both JavaScript/TypeScript and a non-JS language shared by the registries (R is the regression choice) in editor and preview assertions.

## CI Notes

Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/klicker-wiki-maintenance/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ New non-obvious patterns discovered during a task go to the matching page above
3. Validate + format:

```bash
bash ~/.claude/skills/llm-wiki-okf/scripts/validate.sh docs # if the validator is available; no trailing slash
bash ~/.agents/skills/rs-llm-wiki-okf/scripts/validate.sh docs # if the validator is available; no trailing slash
pnpm exec prettier --write docs/
```

Expand Down
70 changes: 8 additions & 62 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,35 +89,16 @@ packages/
cypress/ # E2E tests
```

## Tech Stack
## Workflows & References

| Layer | Technology |
| ---------------------- | ----------------------------------------------------- |
| Frontend framework | Next.js 15, React, TypeScript |
| Styling | TailwindCSS, @uzh-bf/design-system |
| GraphQL server | GraphQL Yoga + Pothos schema builder |
| GraphQL client | Apollo Client |
| ORM | Prisma 6 (PostgreSQL) |
| Caching | Redis (ioredis) |
| Workflow orchestration | Hatchet (workers for async processing) |
| Auth | Edu-ID (OIDC), magic links, LTI, delegated login |
| Build | Turborepo + Rollup |
| Test | Vitest (unit), Cypress (E2E) |
| Formatting | Prettier (no semi, single quotes, trailing comma es5) |
Detailed technical concepts and workflows live in the agent-facing engineering wiki at [docs/index.md](docs/index.md):

## GraphQL Workflow

Code-first with **Pothos** in `packages/graphql/src/`. After changing types/resolvers (`src/graphql/`) or `.graphql` ops (`src/graphql/ops/`), regenerate with `pnpm --filter @klicker-uzh/graphql generate` (codegen is required — ops are stale otherwise). Op-name prefixes: `Q` query, `M` mutation, `S` subscription, `F` fragment. The public schema definition is generated at [packages/graphql/src/public/schema.graphql](packages/graphql/src/public/schema.graphql).

## Database Workflow

Prisma split-schema under `packages/prisma/src/prisma/schema/`. After editing a `.prisma` file: `pnpm run prisma:migrate`, then `pnpm run prisma:sync` (mirrors the schema into `apps/analytics`, excluding `js.prisma`) and regenerate the client. Update GraphQL types/resolvers if the change affects the API.

## Auth Model

- **Lecturers**: Edu-ID (OIDC) or delegated login via `apps/auth`
- **Participants**: magic link, LTI, username/password, temporary (anonymous)
- JWT tokens; GraphQL resolvers enforce three-layer auth: authenticate -> authorize -> execute
- **Tech Stack & Architecture**: see [Architecture Overview](docs/architecture-overview.md) and [Getting Started](docs/getting-started.md).
- **GraphQL APIs & Resolvers**: see [GraphQL API Layer](docs/graphql-api-layer.md).
- **Database & Prisma Schemas**: see [Data & Migrations](docs/data-and-migrations.md).
- **Authentication & Authorization**: see [Auth Model](docs/auth-model.md).
- **Coding Style & Conventions**: see [Frontend Conventions](docs/frontend-conventions.md).
- **Testing & Verification Matrix**: see [Testing](docs/testing.md).

## Local Dev Setup

Expand All @@ -143,47 +124,12 @@ Apps at `https://{api,auth,pwa,manage,control,olat-api,response-api}.klicker.loc

**Media uploads and Blob CORS:** the manage media library uploads directly from the browser to Azure Blob Storage with a SAS URL. The storage account's Blob service CORS must allow the actual local origin (`https://manage.klicker.localhost` or `https://manage.klicker.<workspace>.localhost`), not only production origins such as `https://manage.klicker.com`. For a dedicated dev storage account, use a dev-only rule like `https://*.localhost`; keep production storage accounts exact.

### Legacy host-based stack

Traefik reverse proxy serves the apps on `*.klicker.com` domains (needs `/etc/hosts` entries + mkcert certs; Docker Compose runs Postgres, Redis, Traefik, Hatchet-lite). Without Traefik, hit `http://localhost:<port>` directly — per-app ports are in [Repo Layout](#repo-layout). The `*.klicker.com` domains better mirror production cookie/domain behavior.

| URL | App | Port |
| ------------------------------------------- | ---------------------------- | ---: |
| https://pwa.klicker.com | Student PWA | 3001 |
| https://manage.klicker.com | Lecturer UI | 3002 |
| https://control.klicker.com | Controller | 3003 |
| https://chat.klicker.com | Chat | 3004 |
| https://auth.klicker.com | Auth | 3010 |
| https://api.klicker.com | Backend/GraphQL | 3000 |
| https://assessment.klicker.com | Assessment PWA (same as PWA) | 3001 |
| https://assessment-api.klicker.com | Assessment API (same as API) | 3000 |
| https://response-api.klicker.com | Response API | 7078 |
| https://response-api-assessment.klicker.com | Response API (assessment) | 7078 |

### Test credentials (local seeded DB only)

- Lecturer: username `lecturer`, password `abcd` (delegated login)
- Students: `testuser1`-`testuser50`, password `abcdabcd` (enrolled in "Testkurs")
- Additional: `testuser51`-`testuser52` exist but are not enrolled in any course by default

## Code Conventions

- **TypeScript strict mode** everywhere
- **Functional components** with hooks only (no class components)
- **Component naming**: PascalCase files, `function` keyword for component declarations
- **Prettier**: no semicolons, single quotes, trailing comma es5, 2-space indent
- Plugins: `prettier-plugin-organize-imports` + `prettier-plugin-tailwindcss`
- **Imports**: use `@` and `~` path aliases
- **GraphQL ops**: import from `@klicker-uzh/graphql`
- **State**: Apollo Client for server state, React hooks for local state
- **Styling**: TailwindCSS utilities only, `twMerge` for conditional classes

## Pre-commit / Pre-push

- **pre-commit** (husky): runs `pnpm run check:all` (typecheck + format:check via lint-staged + lint + syncpack)
- **pre-push**: runs `pnpm run build`
- lint-staged checks: `prettier --check` on all staged files

## Important Notes

- **Task tracking**: ClickUp is the source of truth; GitHub Issues are not actively used.
Expand Down
13 changes: 9 additions & 4 deletions apps/frontend-manage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
"@klicker-uzh/types": "workspace:*",
"@socialgouv/matomo-next": "1.9.1",
"@tanstack/react-table": "8.20.5",
"@tiptap/core": "3.27.1",
"@tiptap/extension-code-block-lowlight": "3.27.1",
"@tiptap/extension-image": "3.27.1",
"@tiptap/extension-placeholder": "3.27.1",
"@tiptap/extension-table": "3.27.1",
"@tiptap/markdown": "3.27.1",
"@tiptap/react": "3.27.1",
"@tiptap/starter-kit": "3.27.1",
"@uidotdev/usehooks": "2.4.1",
"@uzh-bf/design-system": "4.1.6",
"dayjs": "1.11.20",
Expand All @@ -40,6 +48,7 @@
"js-search": "2.0.1",
"localforage": "1.10.0",
"lodash": "4.17.21",
"lowlight": "3.3.0",
"nanoid": "5.0.8",
"next": "15.5.18",
"next-intl": "4.3.4",
Expand All @@ -54,12 +63,8 @@
"react-select": "5.10.2",
"react-tagcloud": "2.3.3",
"recharts": "3.0.2",
"remark-slate": "1.8.6",
"remeda": "2.15.0",
"sharp": "0.33.5",
"slate": "0.110.2",
"slate-history": "0.109.0",
"slate-react": "0.110.2",
"tailwind-merge": "3.3.1",
"yup": "1.6.1"
},
Expand Down
Loading
Loading