feat(web): shared UI wrappers, pipes, stores and store-slice pattern #60

Merged
patrick merged 1 commit from feature/web-shared-ui into main 2026-09-11 11:13:07 +00:00
Owner

PH1.5 (docs/12-implementation-plan.md) — establishes the layer every later frontend feature ticket builds on.

What's in here

  • shared/ui: thin, dumb (input()/output()-only) wrappers around Optimus UI — button, tag, input-text, textarea, select, checkbox, dialog, table, toast (+ AppToastService), confirm-dialog (+ AppConfirmService). Features never import @openng/optimus-ui directly; only shared/ui does.
  • shared/pipes: localTime (Europe/Amsterdam, date always visible, DST-aware) and confidence (% with n=), per CLAUDE.md / docs/06-ux.md.
  • shared/models: ScheduleRow/ScheduleItem and map GeoJSON view-model types (FeatureCollection for sites/routes/stops) — types only, API-independent; the Gantt/map components land later in track T-F.
  • shared/admin-crud: generic P15 admin CRUD (table + dynamic form) driven by a minimal, tolerant OpenAPI-schema-shaped config. backend/openapi.json doesn't exist yet (PH1.7), so it's built and tested against a representative fixture and degrades gracefully on an unrecognised property type/format. Fully input()/output()-driven — no HTTP, no store injection.
  • core/stores: current-user, config, language as simple SignalStore wrappers over existing core/ services, and notifications as the worked example of the signalStoreFeature slice-composition pattern (store/with-feed.ts + store/with-read-state.ts, composed in notifications.store.ts) every later feature store follows.
  • shared/architecture: two specs (no-optimus-outside-shared, dumb-components) statically enforce "no feature imports Optimus directly" and "shared/ components are dumb" via import.meta.glob reading source as text — a test rather than an ESLint rule because eslint.config.js/tools/eslint-rules/** sit outside this ticket's owned paths.
  • shared/i18n: the shared Transloco scope's nl/en strings.

Known gap (seam request filed)

core/i18n/transloco-loader.ts (owned by PH0.5, not this row) resolves any non-shell scope to features/<scope>/i18n/..., so provideTranslocoScope('shared') would currently 404 instead of resolving to shared/i18n/.... Doesn't block this PR (nothing mounts shared/ui/shared/admin-crud in a routed page yet), but will block the first feature that does. Filed as #59 with the one-line fix.

Acceptance criteria (issue #13)

  • Admin CRUD page (P15) lists/creates/edits/soft-deletes a table given only its OpenAPI schema
  • localTime shows Europe/Amsterdam with the date; confidence renders % with n=
  • shared/ components use only input()/output() — no store injection, no HTTP — verified by a test
  • Example feature store composes two slice files; shared i18n scope has nl/en parity
  • No feature imports Optimus UI directly (verified by a test)

Testing

npm run build / npm run lint / npm run test all green — 63 tests across 18 files. npm run i18n:parity OK.

Closes #13

🤖 Generated with Claude Code

https://claude.ai/code/session_01LoNrSy7Reyp7evkfcdHeLX

PH1.5 (docs/12-implementation-plan.md) — establishes the layer every later frontend feature ticket builds on. ## What's in here - **`shared/ui`**: thin, dumb (`input()`/`output()`-only) wrappers around Optimus UI — button, tag, input-text, textarea, select, checkbox, dialog, table, toast (+ `AppToastService`), confirm-dialog (+ `AppConfirmService`). Features never import `@openng/optimus-ui` directly; only `shared/ui` does. - **`shared/pipes`**: `localTime` (Europe/Amsterdam, date always visible, DST-aware) and `confidence` (`%` with `n=`), per CLAUDE.md / docs/06-ux.md. - **`shared/models`**: `ScheduleRow`/`ScheduleItem` and map GeoJSON view-model types (`FeatureCollection` for sites/routes/stops) — types only, API-independent; the Gantt/map components land later in track T-F. - **`shared/admin-crud`**: generic P15 admin CRUD (table + dynamic form) driven by a minimal, tolerant OpenAPI-schema-shaped config. `backend/openapi.json` doesn't exist yet (PH1.7), so it's built and tested against a representative fixture and degrades gracefully on an unrecognised property `type`/`format`. Fully `input()`/`output()`-driven — no HTTP, no store injection. - **`core/stores`**: `current-user`, `config`, `language` as simple `SignalStore` wrappers over existing `core/` services, and `notifications` as the worked example of the `signalStoreFeature` slice-composition pattern (`store/with-feed.ts` + `store/with-read-state.ts`, composed in `notifications.store.ts`) every later feature store follows. - **`shared/architecture`**: two specs (`no-optimus-outside-shared`, `dumb-components`) statically enforce "no feature imports Optimus directly" and "shared/ components are dumb" via `import.meta.glob` reading source as text — a test rather than an ESLint rule because `eslint.config.js`/`tools/eslint-rules/**` sit outside this ticket's owned paths. - **`shared/i18n`**: the `shared` Transloco scope's `nl`/`en` strings. ## Known gap (seam request filed) `core/i18n/transloco-loader.ts` (owned by PH0.5, not this row) resolves any non-`shell` scope to `features/<scope>/i18n/...`, so `provideTranslocoScope('shared')` would currently 404 instead of resolving to `shared/i18n/...`. Doesn't block this PR (nothing mounts `shared/ui`/`shared/admin-crud` in a routed page yet), but will block the first feature that does. Filed as #59 with the one-line fix. ## Acceptance criteria (issue #13) - [x] Admin CRUD page (P15) lists/creates/edits/soft-deletes a table given only its OpenAPI schema - [x] `localTime` shows Europe/Amsterdam with the date; `confidence` renders `%` with `n=` - [x] `shared/` components use only `input()`/`output()` — no store injection, no HTTP — verified by a test - [x] Example feature store composes two slice files; `shared` i18n scope has `nl`/`en` parity - [x] No feature imports Optimus UI directly (verified by a test) ## Testing `npm run build` / `npm run lint` / `npm run test` all green — 63 tests across 18 files. `npm run i18n:parity` OK. Closes #13 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LoNrSy7Reyp7evkfcdHeLX
PH1.5 — establishes the layer every later frontend feature ticket builds
on (docs/12-implementation-plan.md row PH1.5).

shared/ui: thin, dumb (input()/output()-only) wrappers around Optimus UI
components — button, tag, input-text, textarea, select, checkbox, dialog,
table, toast (+ AppToastService), confirm-dialog (+ AppConfirmService).
Features never import @openng/optimus-ui directly; only shared/ui does.

shared/pipes: localTime (Europe/Amsterdam, date always visible, DST-aware
via Intl + IANA zone) and confidence (% with n=), per CLAUDE.md and
docs/06-ux.md's cross-cutting UX rules.

shared/models: ScheduleRow/ScheduleItem and map GeoJSON view-model types
(FeatureCollection for sites/routes/stops) — types only, independent of
API DTOs, per the frontend decoupling rules; the Gantt/map components
themselves land later in track T-F.

shared/admin-crud: generic P15 admin CRUD (table + dynamic form) driven
by a minimal, tolerant OpenAPI-schema-shaped config. backend/openapi.json
doesn't exist yet (PH1.7), so this is built and tested against a
representative fixture (shared/admin-crud/fixtures) and degrades
gracefully on properties with an unrecognised type/format. Fully
input()/output()-driven — no HTTP, no store injection — so a future
feature wires it to real data without shared/ ever touching HttpClient.

core/stores: current-user, config and language as simple SignalStore
wrappers over existing core/ services, and notifications as the worked
example of the signalStoreFeature slice-composition pattern
(store/with-feed.ts + store/with-read-state.ts, composed in
notifications.store.ts) that every later feature store follows.

shared/architecture: two specs (no-optimus-outside-shared,
dumb-components) statically enforce "no feature imports Optimus
directly" and "shared/ components are dumb" via Vite's import.meta.glob
reading source as text, since eslint.config.js and tools/eslint-rules/**
sit outside this ticket's owned paths.

shared/i18n: the `shared` Transloco scope's nl/en strings. Filed a
seam-request (issue #59) for a one-line fix to
core/i18n/transloco-loader.ts (not owned by this row) needed before any
feature can actually load this scope at runtime.

npm run build/lint/test all green; 63 tests across 18 files.

Closes #13

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoNrSy7Reyp7evkfcdHeLX
patrick deleted branch feature/web-shared-ui 2026-09-11 11:13:07 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
patrick/Polaris!60
No description provided.