Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dc0d6d8
docs(project): add KB management UX implementation plan
rschlaefli Aug 24, 2026
4451642
enhance(kb): prioritize resource workspace
rschlaefli Aug 24, 2026
07452f7
enhance(kb): present resources as metadata table
rschlaefli Aug 24, 2026
12dcd4c
enhance(kb): unify resource creation
rschlaefli Aug 24, 2026
edc4c90
enhance(kb): streamline detail workspace
rschlaefli Aug 24, 2026
1133e0f
enhance(kb): collapse secondary configuration
rschlaefli Aug 24, 2026
dfa33dd
fix(kb): protect resource creation lifecycle
rschlaefli Aug 24, 2026
b900805
docs(project): update KB UX verification progress
rschlaefli Aug 24, 2026
2b3d8cb
fix(kb): align UX package static checks
rschlaefli Aug 24, 2026
ac821a5
fix(kb): lock resource mode during uploads
rschlaefli Aug 24, 2026
f817a28
fix(kb): preserve creation close and narrow layout proof
rschlaefli Aug 24, 2026
248d58c
docs(project): record KB UX final review
rschlaefli Aug 24, 2026
574ca21
docs(project): record full KB verification
rschlaefli Aug 24, 2026
c58032b
enhance(playwright): run local E2E from host
rschlaefli Aug 25, 2026
25a32cb
docs(project): record KB UX PR metadata
rschlaefli Aug 25, 2026
42773ea
fix(dev): harden local runtime for KB verification
rschlaefli Aug 25, 2026
da6149e
docs(dev): clarify local cache policy
rschlaefli Aug 25, 2026
0f552de
docs(project): record runtime review
rschlaefli Aug 25, 2026
601d2a6
docs(project): record PR publication
rschlaefli Aug 25, 2026
dbc554b
merge: reconcile KB management UX with lifecycle head
rschlaefli Aug 25, 2026
6638b21
fix(kb): address management UX review feedback
rschlaefli Aug 25, 2026
02cef1b
Merge branch 'rs/kb-v3-ai-finalization' into rs/kb-management-ux
rschlaefli Aug 25, 2026
d5bac25
test(kb): focus management UX checks on desktop
rschlaefli Aug 25, 2026
95842ae
Merge commit 'f85a3dda9466633c66dae9b48f158b373aa1aa75' into rs/kb-ma…
rschlaefli Aug 25, 2026
89fb7d7
test(kb): stabilize management UX E2E setup
rschlaefli Aug 25, 2026
a035b1b
fix(kb): keep resource creation modal consistent
rschlaefli Aug 25, 2026
9a0792a
merge: refresh KB lifecycle parent
rschlaefli Aug 25, 2026
d9747b3
docs(kb): close desktop UX verification plan
rschlaefli Aug 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
2 changes: 1 addition & 1 deletion .agents/skills/klicker-frontend-ui/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Conventions (design system, Tailwind v4, Apollo, i18n, CSP): [docs/frontend-conv
## App boundaries

- `frontend-manage` (lecturer), `frontend-pwa` (student; also has a localforage offline side-channel for live-quiz answers — don't bypass `storageHelpers.ts`), `frontend-control` (mobile controller), `auth` (login flows — auth changes also need [docs/auth-model.md](../../../docs/auth-model.md)).
- Knowledge-base management is a reusable package mounted by `frontend-manage`: edit `packages/kb-management`, not duplicate app-local components. Verify `/resources/knowledgeBases` plus the detail route at desktop and mobile widths, both locales, and every changed empty/active/success/failure state.
- Knowledge-base management is a reusable package mounted by `frontend-manage`: edit `packages/kb-management`, not duplicate app-local components. The detail resource workspace uses a semantic metadata table, one `+ Add resource` chooser for Website/Document with Video disabled until supported, and an overflow menu for destructive row actions. Preserve the explicit `ADDED`-before-Ingest lifecycle and keep the chooser non-dismissible after a file upload ticket is requested until confirmation or terminal failure. Verify `/resources/knowledgeBases` plus the detail route at desktop and mobile widths, both locales, and every changed empty/active/success/failure state.
- The KB navigation item is an interim `user.privatePreview` discovery gate. Direct catalog/detail URLs must render the localized `KB_PREVIEW_ACCESS_REQUIRED` service error for a non-preview lecturer; never rely on hidden navigation as authorization.
- The knowledge-resource Ingest action accepts only the resource identifier. Do not expose transport tuning in the UI unless the GraphQL and ingestion-platform contracts add a real user-controlled setting.
- Keep full KB attempt history out of the two-second detail poll. Load the bounded, owner-checked history query only when a lecturer expands a resource, while the parent query carries only the latest run needed for operation status.
Expand Down
37 changes: 37 additions & 0 deletions .agents/skills/klicker-playwright-e2e/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,43 @@ rg -n "test\\(" playwright/tests

## Local Test Setup

### Host-run against a running devrouter workspace (preferred)

Playwright is a black-box driver: run it from the host against the devrouter
routes. Browser binaries and node_modules come from shared host caches, so
never download browsers into a DevPod.

```bash
# auto-detects routed worktrees / plain devcontainer / host-run apps
bash util/run-host-e2e.sh --project=chromium tests/Y-kb-management-ux.spec.ts

# inspect the resolved URL + database mapping without running anything
bash util/run-host-e2e.sh --print

# linked-workspace token override (long branch names can get truncated)
E2E_WORKSPACE=<token> bash util/run-host-e2e.sh --project=chromium <spec>
```

The runner installs only the Playwright workspace dependencies on the host,
builds `@klicker-uzh/prisma` and `@klicker-uzh/types` for global setup, maps
the application URLs and seed database to the reachable runtime, and reuses
the host browser cache. Headless runs install only the smaller Chromium shell;
a headed run needs one full Chromium installation on the host.

The seed database uses the workspace Postgres container's OrbStack host name.
Node Postgres cannot negotiate libpq direct TLS through the Traefik database
route; that route remains correct for psql and other libpq tooling.
On another Docker runtime, pass `E2E_DATABASE_URL` for a disposable database
that is reachable from the host.
Container-local dependencies stay behind the routed applications. If a future
browser journey needs direct access to another service, expose a host route for
that service instead of running Playwright inside the DevPod.

The existing global setup resets and reseeds the mapped database. Run the host
runner only against a disposable local test runtime.

### Legacy host-based stack

Run from repo root. Use Volta when Node/pnpm versions are confusing.

```bash
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Prisma split-schema under `packages/prisma/src/prisma/schema/`. After editing a

### Self-contained devcontainer (recommended)

Clone-and-run via a self-contained devcontainer — no Infisical/Doppler, no EduID, no `/etc/hosts` edits. The container owns the whole stack (Node 24 + pnpm toolchain, Postgres, 3× Redis, MailHog, Hatchet) and runs **all core apps in ONE container** via `turbo dev`. Run pnpm/prisma/tests **inside the container**, never on the host.
Clone-and-run via a self-contained devcontainer — no Infisical/Doppler, no EduID, no `/etc/hosts` edits. The container owns the whole stack (Node 24 + pnpm toolchain, Postgres, 3× Redis, MailHog, Hatchet) and runs **all core apps in ONE container** via `turbo dev`. Run pnpm/prisma/unit tests **inside the container**, never on the host. **Exception — Playwright E2E runs on the host**: run `bash util/run-host-e2e.sh --project=chromium tests/<spec>.spec.ts` (or `pnpm --filter @klicker-uzh/playwright test:host -- --project=chromium <spec>`) — it auto-maps the app URLs and seed database for routed devrouter workspaces, a plain primary devcontainer, or host-run apps, and shares host browser/node caches; never download browsers into a DevPod. Services without direct routes stay inside the container and are exercised through the routed apps; do not move Playwright into the container to reach them.

```bash
devrouter ensure .
Expand Down
2 changes: 2 additions & 0 deletions docs/frontend-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ The lecturer routes `apps/frontend-manage/src/pages/resources/knowledgeBases.tsx

The catalog uses server search and cursor-driven “load more” rather than loading all owned KBs. The detail page keeps metadata/metrics separate from `packages/kb-management/src/components/KnowledgeBaseResourceList.tsx:KnowledgeBaseResourceList`, which owns server search plus design-system type/status filters, selection, confirmed bulk deletion, the source inspector, and contextual Ingest/Retry/Re-ingest/Delete actions. While any loaded row is `QUEUED`/`PROCESSING`, the two-second interval fetches page zero plus pages known to contain active rows and runs a full loaded-window walk every tenth tick. Cursor or page-length drift triggers an immediate full walk. Promise-only polls use `ApolloClient.query` with `no-cache`; generation fencing, the latest loaded-count ref, and shared cache merge preserve the loaded window and remove rows from selection when they become active. Show indeterminate real-operation progress and safe-to-leave messaging rather than invented percentages.

The detail resource workspace presents that loaded window as a semantic design-system table with source, operation, serving, update, selection, and contextual action metadata. Use one `+ Add resource` action that opens the chooser for Website or Document; keep Video visibly unavailable until its ingestion contract exists. Keep destructive resource deletion in the row overflow menu, and preserve the explicit inspector Ingest action for resources that are only `ADDED`. When a file upload has requested its ticket, keep the chooser open and non-dismissible until upload confirmation or a terminal failure.

The inspector loads the owner-checked five-attempt history lazily. Full attempt history must stay outside the two-second list poll. Lecturer-facing failure detail is localized from stable status/error codes; raw platform messages are not rendered. Transport tuning is not user-controlled. Changes must preserve EN/DE messages, `data-cy` hooks, keyboard/focus behavior, and browser evidence for desktop plus 390 px mobile states, including search/filter, selection/confirmation, empty, active, ready, failed, and replacement-cutover feedback where affected.

KB and resource deletion dialogs explain the two observable phases: the item disappears immediately, while stored files and the external index are removed in the background. Success toasts confirm removal from the lecturer view without claiming that external cleanup has already completed.
Expand Down
1 change: 1 addition & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Clone-and-run via a self-contained devcontainer — no Infisical, no external Ed
Use `devrouter workspace up <branch-name>` from the main repository to create a new worktree. Do not use bare `devpod up` or manual route-token loops; `ensure` owns the persisted identity, Git mount, overlay, aliases, runtime proof, and routes together.
3. Those namespaced hosts only work because `allowedDevOrigins` in `packages/next-config/index.js` is `['**.localhost']` in development (and `undefined` in production) — Next's implicit `*.localhost` matches a single label only. If that glob ever stops covering a worktree host, the symptom is an app that serves HTML but never hydrates, with no obvious error.
3. **Logs:** The dev servers auto-start inside the container. View logs via `devrouter exec . -- tail -f /tmp/dev.log`.
4. **Browser E2E:** Run Playwright from the host with `bash util/run-host-e2e.sh --project=chromium <spec>`. The runner detects routed linked and primary checkouts, a plain primary devcontainer, and host-run apps; it maps the app URLs and seed database while reusing the host browser cache. Never install Playwright browser binaries in a DevPod. The existing global setup resets and reseeds the mapped database, so use only a disposable local test runtime.

For OpenRouter-backed Chat, inject the shared prototyping key when starting the
workspace; never write it into the repository:
Expand Down
18 changes: 18 additions & 0 deletions packages/i18n/messages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,7 @@ Da die KlickerUZH-App noch nicht im iOS-App-Store verfügbar ist, folgen Sie die
'{resources, plural, one {# Ressource} other {# Ressourcen}} · {chatbots, plural, one {# verknüpfter Chatbot} other {# verknüpfte Chatbots}}',
loadMore: 'Weitere Wissensdatenbanken laden',
notFound: 'Die Wissensdatenbank konnte nicht gefunden werden.',
detailFallbackTitle: 'Wissensdatenbank',
backToList: 'Zurück zu den Wissensdatenbanken',
metricsTitle: 'Nutzung und Verknüpfungen',
metricVisibleResources: 'Sichtbare Ressourcen',
Expand Down Expand Up @@ -1568,6 +1569,8 @@ Da die KlickerUZH-App noch nicht im iOS-App-Store verfügbar ist, folgen Sie die
linkSuccess: 'Link wurde zur Wissensdatenbank hinzugefügt.',
linkError: 'Der Link konnte nicht hinzugefügt werden.',
resourcesTitle: 'Ressourcen',
resourceColumn: 'Ressource',
resourceActions: 'Aktionen',
resourcesLoadError: 'Die Ressourcen konnten nicht geladen werden.',
searchResources: 'Ressourcen suchen',
searchResourcesPlaceholder: 'Titel, Dateiname oder URL suchen',
Expand All @@ -1576,13 +1579,28 @@ Da die KlickerUZH-App noch nicht im iOS-App-Store verfügbar ist, folgen Sie die
filterAll: 'Alle',
typeFile: 'Datei',
typeUrl: 'Link',
addResource: 'Ressource hinzufügen',
addResourceTitle: 'Ressource hinzufügen',
addResourceDescription:
'Wählen Sie, wie Sie diese Ressource hinzufügen möchten.',
addWebsite: 'Website',
addWebsiteDescription: 'Eine Website-URL zur Verarbeitung registrieren.',
addDocument: 'Dokument',
addDocumentDescription: 'Eine PDF-, TXT- oder Markdown-Datei hochladen.',
addVideo: 'Video',
comingSoon: 'Demnächst verfügbar',
configure: 'Konfigurieren',
backToResourceTypes: 'Zurück',
noResourceResults: 'Keine Ressourcen entsprechen diesen Filtern.',
resourceResultCount:
'{count, plural, =0 {Keine Ressourcen} one {# Ressource} other {# Ressourcen}}',
selectAllPage: 'Bis zu 50 verfügbare Ressourcen auswählen',
selectResource: '„{title}“ auswählen',
loadMoreResources: 'Weitere Ressourcen laden',
noResources: 'Es wurden noch keine Ressourcen hinzugefügt.',
emptyResourceHint:
'Verwenden Sie oben «Ressource hinzufügen», um eine Website oder ein Dokument hinzuzufügen.',
updatedAtLabel: 'Aktualisiert',
updatedAt: 'Aktualisiert {date}',
statusAdded: 'Hinzugefügt',
statusQueued: 'In Warteschlange',
Expand Down
16 changes: 16 additions & 0 deletions packages/i18n/messages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,7 @@ Since the KlickerUZH app is not yet available in the iOS App Store, follow these
'{resources, plural, one {# resource} other {# resources}} · {chatbots, plural, one {# connected chatbot} other {# connected chatbots}}',
loadMore: 'Load more knowledge bases',
notFound: 'The knowledge base could not be found.',
detailFallbackTitle: 'Knowledge base',
backToList: 'Back to knowledge bases',
metricsTitle: 'Usage and connections',
metricVisibleResources: 'Visible resources',
Expand Down Expand Up @@ -1558,6 +1559,8 @@ Since the KlickerUZH app is not yet available in the iOS App Store, follow these
linkSuccess: 'Link added to the knowledge base.',
linkError: 'The link could not be added.',
resourcesTitle: 'Resources',
resourceColumn: 'Resource',
resourceActions: 'Actions',
resourcesLoadError: 'The resources could not be loaded.',
searchResources: 'Search resources',
searchResourcesPlaceholder: 'Search title, filename or URL',
Expand All @@ -1566,13 +1569,26 @@ Since the KlickerUZH app is not yet available in the iOS App Store, follow these
filterAll: 'All',
typeFile: 'File',
typeUrl: 'Link',
addResource: 'Add resource',
addResourceTitle: 'Add a resource',
addResourceDescription: 'Choose how you want to add this resource.',
addWebsite: 'Website',
addWebsiteDescription: 'Register a website URL for ingestion.',
addDocument: 'Document',
addDocumentDescription: 'Upload a PDF, TXT or Markdown file.',
addVideo: 'Video',
comingSoon: 'Coming soon',
configure: 'Configure',
backToResourceTypes: 'Back',
noResourceResults: 'No resources match these filters.',
resourceResultCount:
'{count, plural, =0 {No resources} one {# resource} other {# resources}}',
selectAllPage: 'Select up to 50 available resources',
selectResource: 'Select “{title}”',
loadMoreResources: 'Load more resources',
noResources: 'No resources have been added yet.',
emptyResourceHint: 'Use Add resource above to add a website or document.',
updatedAtLabel: 'Updated',
updatedAt: 'Updated {date}',
statusAdded: 'Added',
statusQueued: 'Queued',
Expand Down
Loading
Loading