Design Doc β Event Categories, Phase 1 (Admin Categories)
The Phase-1 Design Doc β the engineering HOW for the single admin screen
Categories, end-to-end (backend β frontend β design-system), at a depth sufficient
to run the DR, break into tasks, and estimate. Phase 1 is taxonomy management only
β creating/organising the categories. Tagging events with categories is a later phase
(so Phase 1 touches no SQL β see Β§3). Design-system-anchored (the contract is
components + tokens, not pixels); carries the UI reference (Β§4) the PM (Gali) reviews
against. The WHAT/WHY lives in the PRD (event-categories-resident-interests, on its own
branch) and is not restated. Backend is outline-level (decisions + contracts + risks
for the DR). Assumptions are marked (assumption β confirm).
1. TL;DRβ
Phase 1 delivers the admin taxonomy screen in organization-dashboard (embedded via
iframe from the community gear-menu): create / edit / delete / deactivate categories,
assign them to communities, import in bulk, batch-act, filter, search, paginate. This is a
new, independent category system β a fresh Mongo-based foundation that will, in a
future phase, replace the legacy system-categories/communities-categories (the
legacy system is not a reference and is not touched here). Notifications and
subscriptions are settings + placeholder counts only; event tagging, consumer
filters, and dispatch are later phases. The screen reuses the dashboard's DataGrid
pattern; the design system needs a Chip status-badge variant + a communities
cell. Everything in Phase 1 lives in MongoDB β zero SQL schema change.
2. Approach & key decisionsβ
| Decision | Why | Alternative (rejected) |
|---|---|---|
New, independent category system (not reuse of system-categories) | Product decision: this is the future foundation; the legacy taxonomy will be replaced by it later. Legacy is not a reference, no coexistence wiring. | Reuse/extend legacy system-categories β explicitly declined by product |
| Store everything in MongoDB; zero SQL in Phase 1 | Strategic direction is to move off SQL; taxonomy management needs no SQL entity. Org/community are referenced by value (their MySQL int id stored as a Number), the established Mongo pattern β no new SQL coupling | A SQL table for categories β adds coupling to the store we're moving away from |
| Tagging events deferred to a later phase | Phase 1 is taxonomy only; events live in MySQL (groups) β deferring keeps Phase 1 SQL-free | Build eventβcategory tagging now β out of phase + couples to SQL |
Reuse organization-dashboard DataGrid + table-view-provider | Matches communities-table/resources-table; server pagination solved | Hand-roll a table β more work, divergent |
| Anchor UI to the Claude artifact | Product decision; simpler column set | Figma Make per-hub model β deferred (OQ-A) |
| Build order: design-system + backend API before FE wiring | FE depends on both | Parallel-blind β FE rework |
3. Architectureβ
Phase 1 is Mongo-only. The existing entities it references (organizations, communities,
events) live in Aurora/MySQL and are co-owned by the legacy PHP β Phase 1 references
them by value (their integer id stored as a Number), with no foreign key and no
SQL change.
- Embedding: the screen is a React app embedded via iframe inside the legacy PHP (the established mechanism until PHP is retired).
- Auth: the existing dashboard auth β Cognito JWT (bearer + refresh) β already works for the embedded screen; no new auth pattern needed (verified against the existing dashboard).
4. UI referenceβ
The canonical visual the build is checked against and Gali reviews at the end:
- Claude artifact (interactive):
https://claude.ai/public/artifacts/2c7d7677-1242-445c-ae16-89f455f3523cβ tab Admin Categories. Local:~/Downloads/categories-ui-preview (1).html; screenshot~/categories-admin-claude-artifact.jpeg. - Secondary (deferred): Figma Make
figma.com/make/Q7qP6JwTSt8cHgAefLrLyd(Level / Resident-Visibility / Icon / per-hub β OQ-A).
5. Scopeβ
| In scope (Phase 1 β admin taxonomy) | Out / later phases |
|---|---|
| Category CRUD; 2-level subcategories (in the form); colour, status | Event tagging (category β event) β later phase |
| Community assignment (which communities a category applies to) | Consumer filters & feed |
| List Β· filter (status/community/search) Β· server pagination | Resident subscription / opt-in UI |
| Batch activate/deactivate/delete; Import (CSV/XLSX) | Notification dispatch (Phase 3) |
| Stat cards (Total/Active/Subscribers/Tagged) β last two placeholder | AI auto-categorization |
| Notification settings modal (settings only) | Replacing the legacy system-categories (future migration) |
| Feature flag + RBAC; Mongo-only, zero SQL | β |
6. Frontend compositionβ
Single screen "Categories". Each region β an existing @bewith-dev/design-system
component or the dashboard table pattern. new = a design-system delta (Β§12).
| Region | Components | Notes |
|---|---|---|
| Header + actions | Typography; Button Γ3 (primary "New Category", secondary "Import", "Notification Settings") | Mirrors in RTL. |
| Stat cards (4) | SummaryCard Γ4 (+ SummaryCardSkeleton) | Subscribers/Tagged = placeholders. |
| Filters | Search Β· Select (Status) Β· Select (Community) | |
| Table + pagination | dashboard DataGrid + table-view-provider (reuse) | Server paging (page/pageSize=25); {records, pagination:{currentPage,pageSize,totalResults}}. |
| Cols | Category (dot + name + desc) Β· Status Chip status-badge new Β· Communities Chip cell + "+N" new Β· Events Β· Subscribers Β· Actions (IconButton edit/delete) | Subscribers = placeholder. |
| Create/Edit modal | Modal, TextField, ColorPicker, Select/Toggle, MultiSelectSearchInput, subcategories editor, Button | Validation: duplicate name, required. |
| Manage Communities | Modal, MultiSelectSearchInput, Button | Title Manage Communities β <name>. |
| Delete/Deactivate | ConfirmationModal | Deactivate (kept, hidden) vs Delete (impact summary). |
| Import | Modal, DropZone (β€5MB), template Button, preview table | Per-row validation. |
| Batch | toolbar + BatchWarningModal | Activate/Deactivate/Delete. |
| Notification Settings | Modal, per-category settings | Settings only β no dispatch. |
Import flow:
7. Statesβ
Drawn in the artifact: populated, empty (CTA), batch toolbar, all modals, create toast. To build (undrawn β OQ-9): loading/skeleton, error / access-error, pagination loading / last-page. Per-state a11y in Β§13.
8. Backend β data model, API, integrity (outline)β
A new, independent system in backend-services (NestJS) on MongoDB. Names
proposed β OQ-1. No SQL schema change in Phase 1; org/community referenced by their
MySQL integer id stored as a Number.
Collections (Mongo)
| Collection | Purpose | Key fields |
|---|---|---|
event-categories | category / subcategory (the new taxonomy) | key (stable, immutable), name, description, color, status, parentId? (2-level), scope (system/org/community), organizationId (Number), audit, i18n |
event-category-communities | which communities a category applies to | categoryId, communityId (Number) |
The default category list (SYSTEM scope). A future PM-provided default set (Gali) will
seed scope: system rows into event-categories via migrations-tool, read-only to
customer admins. Phase 1 fixes the location + shape for it; the list content arrives
later (OQ-E). It is a new, independent list β not derived from the legacy
system-categories.
Integrity rules
- Unique name per
scope+ community (FR-16) β index + validation. key: a stable, immutable identifier per category β every later phase (tagging, subscriptions, AI) will reference it; getting it right now is cheap, retrofitting is not.- 2-level max: a
parentIdmust reference a top-level category; reject depth > 2. - Delete vs Deactivate: deactivate keeps the row + community links (hidden); delete
removes the category + its
*-communitiesrows. - Scope:
system(default set, read-only to customer admins),org,community; every query scoped byorganizationId/communityId.
API (resource categories, /api/v2 via external-gateway):
| Method Β· path | Purpose | FR |
|---|---|---|
GET /categories | list β filters (status, community, search) + pagination | FR-02/03 |
POST /categories Β· PUT /:id Β· DELETE /:id | CRUD | FR-01/04/05 |
POST /categories/batch | batch activate/deactivate/delete | FR-06 |
POST /categories/import | import (validate, per-row errors) | FR-07 |
PUT /categories/:id/communities | assign communities | FR-08 |
GET /categories/stats | stat-card counts | FR-09 |
Event tagging β deferred, recommended shape (for the later phase, reserved now). When
events get tagged, do it in a new Mongo collection (e.g. event-category-tags), one
doc per eventβcategory link, holding categoryId + groupId (the MySQL event int id, by
value) + organizationId + communityId + source, with a unique index on groupId +
categoryId. Rationale: zero SQL schema,
zero new SQL coupling β aligned with moving off SQL (mirrors the existing
resource-waitlist-member pattern). Integrity is service-layer (no cross-store FK); orphan
tags are cleaned on event-delete. Rejected: a MySQL groups_categories table β it
deepens SQL coupling.
9. Security & multi-tenancyβ
| Actor | Create/Edit/Delete | Assign communities | SYSTEM-scope rows |
|---|---|---|---|
| Org admin | all org/community categories | all org communities | read-only |
| Community admin | only within their communities | their communities only | read-only |
| System | platform-managed (seed) | β | manage |
- SYSTEM read-only enforced server-side (not just hidden) β FR-09.
- Guard: the existing category CRUD is community-admin (
@CommunityGuard()); a SYSTEM/ORG/COMMUNITY model also needs an org-admin guard for org-scope actions (OQ-F). - Tenant isolation: every read/write scoped by
organizationId/communityId; reject cross-tenant ids. - Import safety: size cap (β€5MB), format allow-list (CSV/XLSX), row cap (assumption), CSV-formula-injection sanitisation, server-side validation.
- Auth/embedding: existing Cognito JWT; the iframe-embedded screen uses the established dashboard auth β no new pattern (confirmed).
10. Non-functionalβ
- Scale (assumption β confirm): tensβlow-hundreds of categories per org; pageSize 25.
- List query indexed on
organizationId+status+parentId; name search. - Batch cap per op (assumption β β€100); complete < 5s, progress if slower.
- Import sync for small files (assumption β async if large).
11. Migration & existing dataβ
- Phase 1 is purely additive and Mongo-only β new collections, no SQL migration, reversible (flag off).
- The legacy
system-categories/communities-categoriesare not touched and not referenced. This new system will replace them in a future migration (separate effort, out of Phase-1 scope) β flag as an ADR when that work is scoped. - No eventβcategory data in Phase 1 (tagging deferred).
12. Design-system deltasβ
| Target | Delta | DS task |
|---|---|---|
Chip β status badge | variant (Active/Inactive, semantic tokens) | /add-variant Chip status-badge |
| Communities cell | chips + "+N" overflow (composes Chip) | /add-composite CommunitiesCell (or reuse) |
Reused: table + pagination ride the dashboard DataGrid + table-view-provider;
Modal, ConfirmationModal, BatchWarningModal, ColorPicker, MultiSelectSearchInput,
DropZone, SummaryCard(+Skeleton), Search, Select, Button, Typography,
IconButton β all in COMPONENTS.md. Tokens only, no raw hex.
13. i18n / RTL & Accessibilityβ
- i18n/RTL: English + Hebrew; copy via
t()(Hebrew strings in PRD Β§7.4 / PM spec); logical CSS + themedirection; DataGrid verified both directions. - a11y (WCAG 2.1 AA, validated by
/design-review): table semantics + per-category checkbox names + status-not-colour-only + announced pagination; modal-formaria-invalid/aria-describedbyerrors stating problem+correction + focus-first-invalid + labelled Selects/ColorPicker; modal focus trap/restore +alertdialogfor destructive; empty/access-error headings + focus; toastsaria-live; translated icon-button names; AA token contrast.
14. Test plan & acceptance criteriaβ
Levels: unit (services/validation), integration (API + Mongo), e2e (screen flows incl RTL). Key scenarios:
| Scenario | FR |
|---|---|
| Create valid; reject duplicate name in scope | FR-01/16 |
| Edit; delete vs deactivate (impact preserved) | FR-04/05 |
| Batch activate/deactivate/delete | FR-06 |
| Import: valid rows + per-row errors | FR-07 |
| Assign communities | FR-08 |
| Filter (status/community) + search + pagination | FR-02/03 |
| RBAC: community-admin cross-community denied; SYSTEM read-only | FR-09 |
| RTL render + a11y AA | β |
Acceptance criteria: the PRD Β§9 / PM-spec criteria for Phase-1 admin scope (not restated) β each maps to a scenario above.
15. Open questionsβ
The agenda to close in the DR with Aviad. None blocks authoring this doc.
| # | Question | Owner | Blocks build? |
|---|---|---|---|
| OQ-1 | Final names (flag, collection) + copy ("Categories" vs "Category Settings") | Aviad | yes (contract) |
| OQ-A | Scope/Level (System/Org/Community) β the artifact has no Level column, but the model is scope-based. Surface scope in the UI (column/badge), or keep it data-only in P1? | Aviad / PM | scope |
| OQ-B | Subscribers card+column β placeholder now, or remove until later-phase data? | PM | no |
| OQ-C | Hierarchy: 2 levels (category β subcategory), managed in the form, table flat (assumed) β confirm; or show subcategories in the table (expand)? | PM / design | no |
| OQ-E | The PM-provided default category list (Gali) β timing, format, breadth. Blocks the SYSTEM seed, not the P1 screen. | Gali / PM | seed only |
| OQ-F | org-admin guard for org-scope actions (existing CRUD guard is community-admin) | auth / backend | yes |
| OQ-9 | Undrawn states (loading/error/access-error) β frames or accept Β§7 as contract | design | no |
Resolved this round: tagging deferred (taxonomy-only); new independent system replacing the legacy (no coexistence/reference); event-tag junction β Mongo (minimal SQL coupling); iframe-auth β existing Cognito JWT (OQ-D closed); machine-readable source (OQ-8 closed).
16. Risks & dependenciesβ
| Risk / dependency | Impact | Mitigation |
|---|---|---|
| OQ-1 naming unresolved | blocks model/API contract | resolve in DR before T3/T4 |
| OQ-F org-admin guard | org-scope actions can't be authorised | decide guard in DR; route to auth-security |
| OQ-E default-list timing | SYSTEM seed can't ship | P1 screen works without it; seed is a separate deliverable |
| design-system deltas land late | blocks FE T9/T11 | sequence T1/T2 first |
| Future SQL decoupling | the eventual event-tagging keeps off SQL | reserved Mongo junction shape (Β§8) |
17. Task breakdown & effortβ
Effort is relative (S/M/L), to confirm in planning β not hours. Feeds
/breakdown. All backend work is Mongo-only.
| # | Area | Task | Effort | Depends on |
|---|---|---|---|---|
| T1 | design-system | Chip status-badge variant + tokens | S | β |
| T2 | design-system | Communities cell (chips + "+N") | S | β |
| T3 | backend | event-categories Mongo model + indexes + key | S | OQ-1 |
| T4 | backend | CRUD API + list filters + pagination | M | T3 |
| T5 | backend | Batch endpoint | S | T4 |
| T6 | backend | Import (parse/validate/per-row errors) | M | T4 |
| T7 | backend | Community assignment + stats | S | T4 |
| T8 | backend | RBAC (org + community guard) + SYSTEM read-only + feature flag | M | T4, OQ-F |
| T9 | frontend | Screen on DataGrid + provider (columns/cells/pagination) | M | T1,T2,T4 |
| T10 | frontend | Stat cards + filters | S | T4,T7 |
| T11 | frontend | Create/Edit modal | M | T1,T4 |
| T12 | frontend | Manage-Communities / Import / Batch / Delete-Deactivate / Notification-Settings modals | L | T4,T5,T6 |
| T13 | frontend | States + toasts | S | T9 |
| T14 | frontend | i18n (he/en) + RTL pass | S | T9βT13 |
| T15 | qa | Unit + integration + e2e (Β§14 scenarios) | M | most |
18. Definition of Done (Phase 1)β
- All P0 FRs implemented; OQ-1 / OQ-A / OQ-C / OQ-F resolved.
-
/design-reviewpass (design-system anchoring + a11y AA + RTL verified). - Β§14 scenarios green; lint / typecheck / tests / build green; no secrets; CU-86c1p963g linked.
- Behind the feature flag; RBAC + SYSTEM read-only enforced server-side; no SQL change.
- Reviewed with Aviad (DR) before build; Β§17 broken out via
/breakdownand estimated.
19. Downstreamβ
/plan-feature / /breakdown consume Β§17; design-system ships Β§12;
frontend-developer + backend-developer build on the existing patterns; later phases
(event tagging, consumer filters, subscriptions, dispatch, AI, and the legacy-replacement
migration) get sibling docs in event-categories/.