Skip to content
Open
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
f00326f
feat(events): add stable identity to event categories
dapolach Jul 24, 2026
d9c550d
feat(events): reference event category by stable id from registration
dapolach Jul 26, 2026
feb9c56
feat(events): pair ORIS categories by orisId on import and sync
dapolach Jul 26, 2026
75b4495
feat(events): carry structured categories in domain events
dapolach Jul 26, 2026
fa86ce9
refactor(events): address code review findings for category identity
dapolach Jul 26, 2026
9caeb61
fix(events): render category collection field as repeatable rows
dapolach Jul 26, 2026
6b2e823
docs(events): mark event-category-identity tasks complete
dapolach Jul 26, 2026
43fd43a
docs(events): sync specs and archive event-category-identity
dapolach Jul 26, 2026
6c3a2e5
openspec(halforms): unify custom field factory composition and collec…
dapolach Jul 27, 2026
e9f265b
refactor(halforms): add optional customFactory parameter to halFormsF…
dapolach Jul 27, 2026
af3d6ae
refactor(halforms): extract fieldFactory prop from HalFormsInputProps…
dapolach Jul 27, 2026
9e54c02
refactor(halforms): migrate field factories to composed custom field …
dapolach Jul 27, 2026
de3a792
docs(halforms): document field factory signature, composition helpers…
dapolach Jul 27, 2026
eb47cc5
docs(halforms): note fieldFactory is now an explicit HalFormsCollecti…
dapolach Jul 27, 2026
3734d92
docs(openspec): mark verification tasks complete for refactor-halform…
dapolach Jul 27, 2026
2a2b7a0
refactor(halforms): post-implementation cleanup for field factory uni…
dapolach Jul 27, 2026
7000bc3
chore(openspec): archive completed change refactor-halforms-field-fac…
dapolach Jul 27, 2026
94c68c9
refactor(halforms): reorder imports for consistency and add TODOs for…
dapolach Jul 27, 2026
4af64be
fix(halforms): unify multi-member field rendering; remove dead list t…
dapolach Jul 27, 2026
94672f4
fix(traininggroup): render trainers field as member picker
dapolach Jul 27, 2026
41c5df7
chore(openspec): archive completed change unify-multi-member-field-re…
dapolach Jul 27, 2026
84620c2
feat(traininggroup): introduce AgeRangeResponse for min/max age handling
dapolach Jul 27, 2026
70ad782
refactor(members): remove MemberResource interface, use MemberDetails…
dapolach Jul 27, 2026
88a6da9
feat(openspec): configure OpenAPI schemas to map domain IDs to UUIDs
dapolach Jul 27, 2026
3e470c3
build(openapi): add spec bundler and drift check for spec-first migra…
dapolach Jul 27, 2026
2c38465
feat(openapi): migrate members module to hand-written spec
dapolach Jul 28, 2026
41b2633
feat(openapi): generate frontend HAL types from x-hal-* extensions
dapolach Jul 28, 2026
1db60a1
feat(openapi): generate members response DTOs from the spec
dapolach Jul 28, 2026
e50beeb
fix(gradle): fixed gradle builds for local and github CI
dapolach Jul 28, 2026
fc03746
feat(hateoas): wrap plain payload DTOs into HAL models via ResponseBo…
dapolach Jul 28, 2026
a834fc4
feat(openapi): generate members and event-types APIs from the spec
dapolach Jul 29, 2026
77e5001
feat(openapi): migrate finance and calendar modules to spec-first
dapolach Jul 29, 2026
2a7c7db
feat(openapi): migrate remaining modules to spec-first (#304)
dapolach Jul 30, 2026
e902430
refactor: route HAL affordances at generated *Api interfaces (#305)
dapolach Jul 30, 2026
a5b2e18
refactor(common): replace PatchField with JsonNullable (#306)
dapolach Jul 31, 2026
cf943e7
feat(openapi): make the hand-written spec the source of klabis-full.j…
dapolach Jul 31, 2026
25b4e91
docs(openapi): move suspendMember 409 contract into the spec (#309)
dapolach Aug 1, 2026
4f87404
refactor(groups): remove files generated to wrong path
dapolach Aug 1, 2026
7ef28a0
chore(hookify): disable block-direct-tests hook locally
dapolach Aug 1, 2026
ce4ad32
chore(frontend): updated frontend bundle
dapolach Aug 1, 2026
980f46e
refactor(openapi): read x-klabis-* authorization keys in the template…
dapolach Aug 1, 2026
cee248e
updated project structure for IDE
dapolach Aug 1, 2026
91a4fb8
feat(openapi): make each module spec a standalone OpenAPI document (#…
dapolach Aug 1, 2026
9c67773
Merge remote-tracking branch 'origin/feature/api-spec-first' into fea…
dapolach Aug 1, 2026
851e2e8
chore(openapi): remove klabis-full.json from git, generate on demand …
dapolach Aug 1, 2026
11ca1f0
docs(openapi): propose eliminating remaining manual schemaMappings
dapolach Aug 2, 2026
3421c2f
refactor(openapi): application/json sibling + eliminate schemaMapping…
dapolach Aug 2, 2026
2f19c3e
Merge remote-tracking branch 'origin/feature/api-spec-first' into fea…
dapolach Aug 2, 2026
4292843
fix(openapi): remove PATCH tri-state nullable spelling from response-…
dapolach Aug 2, 2026
4711c63
fix(review): address PR #300 CodeQL and code-quality bot findings
dapolach Aug 2, 2026
bc687f5
fix(openapi): add missing top-level enum branch to model.mustache
dapolach Aug 2, 2026
90e2baf
refactor(openapi): stop redirecting inline enum schemas onto domain e…
dapolach Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .claude/agent-memory/frontend-developer/MEMORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Frontend-developer memory

## Project

- [event-category-identity frontend iteration 2](project_event_category_identity_frontend.md) — no separate "Moje přihláška" component, orphaned category display pattern, hideEmptyColumns gotcha, openapi regen against live 8443 backend without touching docs/
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
name: event-category-identity-frontend
description: Frontend iteration 2 of event-category-identity — registration category is now an id-backed object, single shared registrations table doubles as admin view and member's own view
metadata:
type: project
---

OpenSpec change `event-category-identity` iteration 2 (backend done as of 2026-07-26): registration
requests use `categoryId` (UUID via `EventCategoryId {value}`), responses carry `category: {id, name} | undefined`
(absent/null = orphaned registration, category was removed from the event after the member registered).

**Key discovery: there is no dedicated "Moje přihláška" component.** The admin-facing
`RegistrationsTable` inside `frontend/src/pages/events/EventDetailPage.tsx` is the single shared
table both admins and the registered member see (member sees their own row via the same
`_embedded.registrationDtoList`, other rows/columns hidden per-field by backend authorization).
Don't build a separate "own registration" component — extend this one.

**HAL-FORMS `register`/`editRegistration` needed zero custom field code** — the affordance's
`categoryId` property comes with `options.inline` (value=id, prompt=name), and the existing generic
`HalFormsSelect` (`frontend/src/components/HalNavigator2/halforms/fields/HalFormsSelect.tsx`) already
renders inline options generically. Only the table's category *display* cell needed a change.

**Fix pattern for orphaned category display**: `TableCell column="category"` needs a `dataRender`
reading `(value as EventCategory | null | undefined)?.name ?? labels.ui.notProvided` — reused the
existing `notProvided: 'neuvedeno'` label rather than adding a new one for "unset category".

**`KlabisTable`'s `hideEmptyColumns` gotcha**: it hides a column if *every* row's raw value is
`null`/`undefined`/`''`. A test with only an orphaned row (`category: null`) and no other row with a
real category will hide the whole category column — the "neuvedeno" cell never renders in that case.
Tests (and real orphaned-registration scenarios) need at least one row with a real category present
so the column stays visible. See `frontend/src/pages/events/EventDetailPage.test.tsx` describe block
`'category cell display (event-category-identity)'`.

## Regenerating klabisApi.d.ts against a running backend

`docs/openapi/klabis-full.json` (source for `npm run openapi`) is generated by a Gradle task that
expects the backend on port **8080**, but local dev backend runs on **8443** (self-signed TLS). When
you need fresh types and only 8443 is up: `curl -sk https://localhost:8443/v3/api-docs -o <tmp>.json`
then `npx openapi-typescript <tmp>.json -o ./src/api/klabisApi.d.ts` directly — skip writing into
`docs/openapi/klabis-full.json` since that file lives outside `frontend/` and frontend-developer must
not touch files outside `./frontend`. Verify with `python3 -c "import json; json.load(open(...))"` that
the curl didn't truncate before trusting the regenerated types.
2 changes: 1 addition & 1 deletion .claude/hookify.block-direct-tests.local.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: block-direct-tests
enabled: true
enabled: false
event: bash
pattern: (gradlew?\s+.*test|vitest|npm\s+(run\s+)?test)
action: warn
Expand Down
129 changes: 92 additions & 37 deletions .claude/skills/backend-patterns/SKILL.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ Use this checklist when adding a new Spring Modulith module or new Aggregate to
- [ ] `@SecurityRequirement(name = "KlabisAuth", scopes = {...})`
- [ ] Convert UUID path variables to typed IDs at controller boundary: `new <Aggregate>Id(uuid)`
- [ ] Implement role-based command routing (admin vs. self-update)
- [ ] Add state-driven HATEOAS affordances using `klabisLinkTo()` / `klabisAfford()`
- [ ] Add state-driven HATEOAS affordances using `klabisLinkTo()` / `klabisAfford()`, passing the
generated `*Api` interface to `methodOn(...)` — never the controller class, or the template
loses its input metadata (see SKILL.md, "HATEOAS Rules")
- [ ] Leave `@RequestBody` and Bean Validation constraints on the interface; the override declares
only `@Parameter` / `@Operation` for springdoc
- [ ] Create MapStruct `@Mapper` for simple DTO mapping
- [ ] Create manual mapper utility class for complex PATCH operations with `PatchField`
- [ ] Create manual mapper utility class for complex PATCH operations with `JsonNullable`

## 4. Infrastructure — JDBC

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Onboarding pro vývojáře bez Spring/DDD znalostí. Žádný projektový kód
- `@HalForms` annotation + `Access` enum
- `RootController` a navigace
- `EntityModelWithDomain` + `ModelWithDomainPostprocessor`
- `PatchField<T>` framework
- `JsonNullable<T>` tri-state PATCH wrapper
- `@ValidOptionalSize`
- `MvcExceptionHandler` mapování výjimek na HTTP statusy
- `HateoasConfiguration`, `MvcConfiguration`, `SpaFallbackController`, `@MvcComponent`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Vše v `backend/src/main/java/com/klabis/common/` — celý package:
- `common.mvc` — MvcExceptionHandler, MvcComponent, MvcConfiguration
- `common.observability` — Custom metriky pro Modulith
- `common.pagination` — TranslatedPageable
- `common.patch` — PatchField, PatchFieldDeserializer
- `common.patch` — JsonNullable wiring (JsonNullableConfiguration)
- `common.ratelimit` — PerKeyRateLimiter
- `common.security` (vč. `fieldsecurity`) — SecurityConfiguration, Authority, OwnerVisible, OwnerId, KlabisJwtAuthenticationToken, OwnershipResolver
- `common.templating` — TemplateRenderer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,40 @@ Maps HAL-FORMS property types to React components:
| boolean | HalFormsBoolean |
| datetime | HalFormsDateTime |

Extend with `expandHalFormsFieldFactory(customFactory)` to add custom field types.
**Signature:** `halFormsFieldsFactory(fieldType: string, conf: HalFormsInputProps, customFactory?: CustomFieldFactory): ReactElement | null`

Dispatch order for every call:
1. **`multi` always wins first** — if `conf.prop` is multi-valued (and has no `options`/`suggest`), the property routes to `HalFormsCollectionField` unconditionally, before `customFactory` ever runs. `HalFormsCollectionField` then recurses per row (`multiple: false`) back through the same custom-aware factory.
2. **`customFactory`** is consulted next — a custom type wins over any built-in default.
3. **Built-in `switch`** on `fieldType` (the table above) as the final fallback.

Because of step 1, a `CustomFieldFactory` only ever receives a **single-row** call for a multi-valued property — it never sees the array itself and does not need to special-case `isMultipleProperty` to avoid being called at the array level.

**Important:** a custom type must never try to render its own multi-value UI by checking `isMultipleProperty` inside the `CustomFieldFactory` — step 1 already claimed the array before the factory runs, so that check will only ever see single-row calls and is dead code for the array case. If a custom type needs different behavior for the multi-valued case (e.g. `MemberId`/`UUID`, which used to render one combined checkbox-group for a multi-select), re-express it as a plain single-field component and let the standard `multi` → `HalFormsCollectionField` mechanism iterate it instead: the base `multi` branch routes the array to `HalFormsCollectionField`, which recurses per row (`multiple: false`) back into the same custom factory, landing on the same single-field component once per item — with add/remove handled generically. This trades one combined widget for N per-row rows, but requires no special exemption in the framework.

**`fieldFactory` is not part of `HalFormsInputProps`.** It used to be threaded through `conf.fieldFactory` solely so `HalFormsCollectionField` could recurse into it; it is now an explicit component prop on `HalFormsCollectionField` (`<HalFormsCollectionField {...conf} fieldFactory={boundFactory(customFactory)} />`, an internal helper in `HalFormsFieldFactory.tsx`), set only in the `multi` branch above. Don't read or set `fieldFactory` off `HalFormsInputProps`/`conf` anywhere else — `HalFormsForm`'s `subElementProps` no longer carries it either.

**Extending with custom field types:** use `expandHalFormsFieldFactory(customFactory: CustomFieldFactory): HalFormFieldFactory` — it binds your custom logic into `halFormsFieldsFactory`'s 3rd parameter and returns a normal `HalFormFieldFactory` (the shape `HalFormButton`/`HalFormDisplay`/`HalFormsForm` consumers expect, so call-sites don't change).

```ts
type CustomFieldFactory = (fieldType: string, conf: HalFormsInputProps) => ReactElement | null;

const myCustomFactory: CustomFieldFactory = (fieldType, conf) => {
if (fieldType === 'MyCustomType') return <MyCustomField {...conf} />;
return null; // falls through to the next factory / built-ins
};

export const myFieldsFactory = expandHalFormsFieldFactory(myCustomFactory);
```

To compose several custom factories (e.g. a base factory plus feature-specific extras), chain them by falling through to the previous one instead of nesting `expandHalFormsFieldFactory` calls:

```ts
const combinedCustomFactory: CustomFieldFactory = (fieldType, conf) =>
myExtraCaseOrNull(fieldType, conf) ?? baseCustomFactory(fieldType, conf);

export const combinedFieldsFactory = expandHalFormsFieldFactory(combinedCustomFactory);
```

## HATEOAS Utility Functions

Expand Down
Loading
Loading