@micha.bigler/ui-core-micha 3.14.3 → 3.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.github/workflows/ci.yml +35 -0
  2. package/.github/workflows/publish.yml +4 -4
  3. package/CHANGELOG.md +6 -0
  4. package/WORK_ORDERS.md +2 -0
  5. package/dist/auth/UserMenu.js +1 -1
  6. package/dist/auth/apiClient.js +7 -6
  7. package/dist/chat/ChatList.js +1 -1
  8. package/dist/chat/ChatThread.js +2 -1
  9. package/dist/chat/ChatWorkspace.js +1 -1
  10. package/dist/chat/ModelPicker.js +2 -5
  11. package/dist/chat/useChatSocket.js +1 -1
  12. package/dist/components/LoginForm.js +1 -1
  13. package/dist/components/SecurityComponent.js +1 -1
  14. package/dist/components/UserListComponent.js +2 -8
  15. package/dist/components/charts/ChartFrame.js +1 -1
  16. package/dist/components/charts/ScatterChart.js +2 -1
  17. package/dist/layout/SectionNav.js +1 -1
  18. package/dist/messaging/AttachmentList.js +2 -2
  19. package/dist/messaging/Composer.js +2 -1
  20. package/dist/messaging/ConversationLaunchers.js +1 -4
  21. package/dist/messaging/ConversationList.js +1 -1
  22. package/dist/messaging/DirectMessageLauncher.js +1 -1
  23. package/dist/messaging/MessagingProvider.js +0 -0
  24. package/dist/messaging/PollCard.js +7 -5
  25. package/dist/messaging/ReadTicks.js +2 -2
  26. package/dist/messaging/Thread.js +5 -3
  27. package/dist/notifications/NotificationBell.js +4 -7
  28. package/dist/notifications/NotificationSettings.js +1 -1
  29. package/dist/onboarding/OnboardingProvider.js +3 -6
  30. package/dist/pages/LoginPage.js +1 -1
  31. package/dist/pages/PasswordChangePage.js +1 -1
  32. package/dist/pages/PasswordInvitePage.js +1 -1
  33. package/dist/pages/PasswordResetRequestPage.js +1 -1
  34. package/dist/pages/SignUpPage.js +1 -1
  35. package/dist/pages/SignupConfirmPage.js +1 -1
  36. package/dist/theme/createAppTheme.js +5 -4
  37. package/dist/theme/themeCompleteness.js +3 -12
  38. package/dist/utils/auth-errors.js +4 -3
  39. package/package.json +11 -11
  40. package/tsconfig.build.json +1 -1
  41. package/work-orders/UCM-CI-1.md +65 -0
@@ -0,0 +1,35 @@
1
+ name: CI
2
+
3
+ on: workflow_dispatch
4
+
5
+ permissions:
6
+ contents: read
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Check out code
14
+ uses: actions/checkout@v7
15
+ with:
16
+ fetch-depth: 2
17
+
18
+ - name: Set up Node
19
+ uses: actions/setup-node@v7
20
+ with:
21
+ node-version: '24.21.0'
22
+
23
+ - name: Set up pnpm
24
+ uses: pnpm/action-setup@v6
25
+ with:
26
+ version: 12.10.1
27
+
28
+ - name: Install dependencies (pnpm)
29
+ run: pnpm install --frozen-lockfile
30
+
31
+ - name: Build
32
+ run: pnpm run build
33
+
34
+ - name: Run tests
35
+ run: pnpm run test
@@ -20,14 +20,14 @@ jobs:
20
20
 
21
21
  steps:
22
22
  - name: Check out code
23
- uses: actions/checkout@v6
23
+ uses: actions/checkout@v7
24
24
  with:
25
25
  fetch-depth: 2
26
26
 
27
27
  - name: Set up Node
28
- uses: actions/setup-node@v6
28
+ uses: actions/setup-node@v7
29
29
  with:
30
- node-version: '24.15.0'
30
+ node-version: '24.21.0'
31
31
  registry-url: 'https://registry.npmjs.org'
32
32
 
33
33
  - name: Decide whether to publish
@@ -99,7 +99,7 @@ jobs:
99
99
  if: steps.version_check.outputs.should_publish == 'true'
100
100
  uses: pnpm/action-setup@v6
101
101
  with:
102
- version: 11.1.2
102
+ version: 12.10.1
103
103
 
104
104
  - name: Show version from package.json
105
105
  if: steps.version_check.outputs.should_publish == 'true'
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.14.4 - UCM-DEP-1
4
+
5
+ Dependency updates. The runtime dependency `@fontsource/dm-sans` moves to ^5.3.0; the peer dependency ranges are
6
+ unchanged. Tooling: TypeScript 7 (`moduleResolution: Bundler` in `tsconfig.build.json`), vitest 5, jsdom 30, pnpm 12
7
+ and updated GitHub Actions in both workflows. No source or API change.
8
+
3
9
  ## 3.14.3 - UCM-CHAT-1 publish fix
4
10
 
5
11
  3.14.0 to 3.14.2 were never published: `themeCompleteness` rejected the chat components' inline `sx` on baseline-styled
package/WORK_ORDERS.md CHANGED
@@ -21,6 +21,7 @@ rules are defined centrally in `webapps/AGENTS.md` → "Work-Order Register".
21
21
  | `PRIM-*` | Shared dashboard/data primitives — small presentational components that are neither chart (`CHART-*`) nor app chrome (`SHELL-*`) |
22
22
  | `SHELL-*` | Shared app chrome and navigation shells (`UserMenu`, mobile bottom nav, secondary-navigation shell) — used since `SHELL-1`, documented here 2026-08-11 |
23
23
  | `TEST-*` | Consumer integration checks the kit ships for apps to run in their own suites |
24
+ | `CI-*` | This repository's own CI workflows (dispatch-only verification runs; `publish.yml` stays the publish path) |
24
25
 
25
26
  Introduce a new prefix when none fits and add it here. New WOs always get a
26
27
  prefixed ID; never reuse a bare flat number across workstreams.
@@ -37,6 +38,7 @@ prefixed ID; never reuse a bare flat number across workstreams.
37
38
  | UCM-THEME-17 | Decide a less compressed baseline type scale | Design round (design-language process): inventory, instrument with 2-3 candidate scales next to today's baseline and MUI on real screens (hram, a survey site, jg-ferien, kerzenziehen), value rounds, frozen sheet. Measured 2026-10-04: page/card/section titles differ by 1-3 px, hierarchy rests on weight alone. Full order [`work-orders/UCM-THEME-17.md`](work-orders/UCM-THEME-17.md) | 2026-10-04 | done | `68dfc6a`, `998e36c`, `cb9dd89`, `c452224` | Tier 2 (document only) · operator decision 2026-10-04: baseline finding, not per-app overrides · implementation order follows the freeze · survey_app SVA-THEME-1 typography revert held until then · **frozen 2026-10-04** after two value rounds: candidate B, mobile h4 23, h1-h3 36/32/28; value source [`docs/TYPE-SCALE.md`](docs/TYPE-SCALE.md) · review: none, document order, the operator freeze is the gate · implementation UCM-THEME-18 |
38
39
  | UCM-THEME-18 | Ship the frozen baseline type scale | `tokens.js` typography per `docs/TYPE-SCALE.md`: h1-h3 36/32/28, h4 24 (23 below `sm`), h5 20, subtitle1 13; light fixture typography entries; minor release 3.12.0. Full order [`work-orders/UCM-THEME-18.md`](work-orders/UCM-THEME-18.md) | 2026-10-04 | 5be83d5 | | Tier 3 (shared-core) · reviewer + ui_reviewer · downstream per-app orders (h5 page titles -> h4, per-level weight overrides) written after the release - 2026-10-04: Codex implemented (gpt-6-luna; probe of stale 2026-10-03 record succeeded, available) - orchestrator fixed review findings - review: codex/gpt-6-luna - lenses: envelope,regression,duplication,tests + ui_review - 3 raised - 2 accepted - worst accepted: defect (h4 mobile key now follows an app-overridden sm, test added; completeness test strengthened; rejected: breakpointDownQuery vs theme.breakpoints.down, no theme exists at token time) - light fixture diff limited to typography entries - no rendered side-by-side (no prototype; values asserted from the resolved theme in light and dark) - release 3.12.0 |
39
40
  | UCM-DEP-1 | All open Renovate updates in one change on main, one CI run | PRs #17 to #24 (npm non-major, actions, jsdom 30, pnpm 12, TypeScript 7, vitest 5) in one change; release only if runtime dependencies changed. Full order [work-orders/UCM-DEP-1.md](work-orders/UCM-DEP-1.md) | 2026-10-15 | planned | | Tier 3 (shared core, dependencies). Part of the PR close-out round of 2026-10-15 (operator decision 2026-10-15). Envelope by the Infra Expertenchat. Gate #1 approved by the operator 2026-10-15 (click in session local_1e5f212a-77db-4423-a507-983adf826bc4). |
41
+ | UCM-CI-1 | A dispatch-only CI workflow for ui-core-micha | `.github/workflows/ci.yml`: install, build, test on Linux, `workflow_dispatch` only, no publish, no secrets, so a candidate ref can be proven without releasing. Full order [work-orders/UCM-CI-1.md](work-orders/UCM-CI-1.md) | 2026-10-15 | done | 69b8920 | Tier 3 (CI). Requested by the operator in the UCM-CHAT Orchestrator session 2026-10-15 as the verification path for UCM-DEP-1; that instruction is the Gate #1 approval. Codex gpt-6-luna dispatched once, RESULT: DONE; file matches the Envelope. review: codex/gpt-6-luna · lenses: envelope+security (combined, one run; regression, duplication and tests not applicable to a 30-line dispatch-only file) · 0 raised · 0 accepted · worst accepted: none. Evidence: dispatched run 38739752689 on ci-test/UCM-CI-1 green (install, build, full test suite incl. packageTreeShaking on Linux); the ref was deleted. |
40
42
  | UCM-I18N-5 | [User-list pagination range text is hard-coded English](work-orders/UCM-I18N-5.md) | `UserListComponent`'s `TablePagination` sets `labelRowsPerPage` but no `labelDisplayedRows`, so German shows "1–4 of 4". | 2026-10-01 | done | 6b9d557 | Found in jg-ferien's rendered check 2026-10-01. Follows `UCM-I18N-4`. Tier 3. Done = released AND jg-ferien pin bumped. Envelope written 2026-10-01; operator: hand to the jg Paket-4 session. Implementation map filled 2026-10-02 (`a732025`): the translation bundle carries FOUR locales (de/fr/en/sw), not three — confirmed by reading `authTranslations.ts` directly; proposed two keys (exact-count + "more than" form) with a best-effort, not native-verified, Swahili wording. Implemented by codex/gpt-5.6-luna exactly per the map: `labelDisplayedRows` resolves `UserList.DISPLAYED_ROWS`/`UserList.DISPLAYED_ROWS_MORE_THAN` through `t()`, both keys added in all four locales. review: codex/gpt-5.6-luna · lenses: envelope,regression,duplication,tests,ui_reviewer (one concurrent batch, diff inline) · 4 raised · 3 accepted (deduplicated) · worst accepted: defect. Accepted: the rendered English test would still pass against a reverted implementation, since the EN wording was deliberately made identical to MUI's own hardcoded default — added a second test swapping in a distinctive marker string for just that key, proving the render genuinely resolves through i18next, not MUI's fallback; `authTranslations.test.js`'s new key-parity check only asserted non-empty strings, not that each locale actually carries the `{{from}}`/`{{to}}`/`{{count}}` placeholders the others do — strengthened with an explicit per-locale placeholder check; self-identified (corroborated by the `tests` lens's own closing remark) that the `count === -1` ("more than") branch can never be reached through a render of `UserListComponent` itself (its own `count` prop is always a real array length) — extracted the inline callback into an exported, directly unit-tested `formatDisplayedRowsLabel(t)` rather than leaving it permanently untested dead code. Rejected: `regression`'s claim that hard-coding `labelDisplayedRows` "overrides any consuming app's MUI theme customisation" — speculative, no actual consumer override exists (same explicit-prop design as `labelRowsPerPage` since `UCM-I18N-4`), and the reviewer's own repo search found zero consumer snapshots/assertions relying on the old English text. `envelope`'s "release + pin bump missing from the diff" was correct but not a code defect — addressed by the remaining steps below, not a fix to the reviewed diff. `duplication`: 0 findings. `ui_reviewer`: 0 blocking findings; confirmed placeholder consistency across all 8 strings; Swahili wording flagged as advisory-only ("structurally plausible to a non-native reviewer, native-speaker verification advisable" — not re-asked of a native speaker here, recorded as open). Tests: 37/37 green (`userScreenTranslations.test.jsx`, `authTranslations.test.js`, `UserListComponent.test.jsx`). Released as `3.11.1` (patch — continues `UCM-I18N-4`'s i18n-gap category, not a new capability area), commit `6b9d557`; npm Trusted Publishing confirmed via the Actions job log (not a registry re-query — provenance publishes can lag), tarball `@micha.bigler/ui-core-micha@3.11.1`. jg-ferien's pin bumped 3.7.0 -> 3.11.1 same day, closed as jg's own `JG-DEPS-3` (`2c1d6e3`) — see that repo's `WORK_ORDERS.md` for the empirical safety checks (status-colour palette diff, `UserListComponent`-unused confirmation, full-suite run) this multi-version jump required. |
41
43
  | UCM-TEST-1 | A kit integration check every app runs in its own suite | Exported `checkKitIntegration({ i18n, wrapper, pages })` -> `{ findings }` (shape of `assertThemeComplete`): kit keys present in the app's i18n, one `i18next` instance, kit pages render in the app's providers without raw keys, the `invalid`-email case shows the field text. Replaces per-app hand-written wiring tests and manual auth walks. Full order [`work-orders/UCM-TEST-1.md`](work-orders/UCM-TEST-1.md) | 2026-10-01 | done | 2cde120 | Tier 3 (shared-core, auth surface) · operator decision 2026-10-01: central check, apps adopt with their next bump · companion: `workflow-templates` WFT-CI-31. Codex implemented (available today, no fallback). Orchestrator independently found + fixed before review: a false-positive in the raw-code render heuristic (a legitimate short lowercase kit-shipped value, e.g. German `"oder"` for `Auth.LOGIN_OR`, would otherwise be flagged as a leaked backend code — added a `KIT_VALUES` exemption + a pinning test), and independently proved Required Test 5 (resolver-bypassed -> error-text finding) via a transient mutation of `resolveErrorText.js`, restored clean. Independent `reviewer` (all 4 lenses) + `ui_reviewer` (Codex gpt-5.6-luna, concurrent background batch, full diff): **review: codex/gpt-5.6-luna · lenses: envelope,regression,duplication,tests,ui · 9 raised · 6 accepted · worst accepted: blocker**. Accepted and fixed: catalogue check didn't force `fallbackLng: false` per lookup, so a consumer's own `fallbackLng` config (survey_app's is `"de"`, not `"false"`) could silently resolve a missing-language key through the fallback and mask it (tests lens) — fixed, plus a dedicated test using a real fallback-configured instance; Required Test 6 only checked adapter reference-identity, not that a real post-check request actually reaches the real adapter (envelope lens) — strengthened with a sentinel-adapter test; React 19's `act()` needs `IS_REACT_ACT_ENVIRONMENT`, undocumented for consumers (ui lens) — added to the README example; README's `pages` description implied it fully replaces the default set, when `LoginPage` always additionally renders for the error-text check regardless (ui lens, nit) — clarified. Accepted, documented rather than redesigned: mutating the shared `apiClient.defaults.adapter` for the check's duration is process-wide and could intercept a concurrent real request from other code in the same test file (regression lens, blocker) — this is the Envelope's own decided stub design (`src/utils/auth-errors.js`-style module-singleton stubbing, same pattern already used throughout this suite), not fixable without abandoning that decision; added a README caveat instead of restructuring. Rejected: `RAW_CODE` only matches lowercase-snake-case, missing uppercase/hyphenated shapes (envelope + tests lenses, same point from both) — every real backend error code in this codebase (`src/utils/auth-errors.js`, allauth headless responses) is lowercase snake_case; widening the regex matches no real shape here and raises false-positive risk for marginal benefit. `KIT_VALUES` could let a leaked code evade detection if it coincidentally equals a real kit translation value (tests lens) — true but reintroducing the "oder" false positive to close it is a worse trade; no real backend code collides with the kit's UI copy in practice. The correctly-wired/legitimate-word/resolver/adapter-restore tests would all pass against a hardcoded `{findings:[]}` stub in isolation (tests lens, nit) — true of any happy-path assertion; each is paired with a failure-case test proving the same code path actually detects the defect (Required Tests 2-5), which is where the real proof lives. `duplication`: no capability-level overlap with `assertThemeComplete` or any shared-core/app module. **Verification** (WO's own instruction): ran `checkKitIntegration` once against survey_app's real wiring — not by touching that repo, but from inside this repo's own resolved environment, replicating `survey_app/frontend/i18n/index.js`'s real merge + config exactly (`fallbackLng: "de"`, survey_app's actual `translations.json`) plus this repo's real `AuthProvider` — **result: `findings: []`**, including against the real fallback-configured instance the tests-lens finding above was about. Orchestrator's own scoped run: `tests/checkKitIntegration.test.jsx` + `tests/resolveErrorText.test.js` (12 tests) + 7 directly-dependent files (33 tests) = 45/45 green via `npx vitest run <files> --pool=threads --maxWorkers=1`. No rendered gate — library-only change, no running app to preview. **Released as `3.10.0`** (minor — new exported capability, per the WO's own instruction). Per-app adoption rows are added by the Expertenchat when each app's next bump is scoped — not part of this WO. |
42
44
  | UCM-AUTH-11 | [`UserListComponent` offers an admin the delete action on their own row](work-orders/UCM-AUTH-11.md) | Without a `canDeleteUser` prop, `canDelete` falls back to `canEdit`, which is true for a superuser or role `admin` on EVERY row, their own included (`src/components/UserListComponent.jsx` `defaultCanEdit`/`canDelete`, ~l.128-156); the backend (dcm) still allows an admin's generic self-delete. Default should never offer deleting one's own row. | 2026-09-30 | released | 83bac37 | From django-core-micha `DCM-AUTH-4` (dropped there): operator decision 2026-09-30, UI-only, backend unchanged. Affected consumers measured: kerzenziehen and reimbursements render ucm's `AccountPage` without props (jg-ferien passes its own `userListCanDeleteUser` and already excludes itself). Both apps pin ucm **2.41.3**, so reaching them needs a major bump (3.0.0 removed chart layout props) — tracked as its own follow-up, not this WO. Gate #1 approved 2026-09-30 (operator handover). Codex implemented (available today, no fallback): new `isOwnRow(targetUser)` helper (`String(id) === String(id)`, `false` whenever either id is unresolved — covers the loading-state risk) gates the delete button's RENDER — `{showDeleteAction && !isOwnRow(row) && (...)}` — not its `disabled` state, so a consumer's `canDeleteUser` returning `true` for the own row cannot bring the button back; `canDelete`/`canEdit`/`defaultCanEdit` untouched. Independent `reviewer` (all 4 lenses) + `ui_reviewer` + `sec_reviewer` (Codex gpt-5.6-luna, concurrent background batch, full diff): **review: codex/gpt-5.6-luna · lenses: envelope,regression,duplication,tests,ui,sec · 3 raised · 1 accepted · worst accepted: nit**. Accepted (nit, tests lens): the committed fixtures only ever used numeric ids for both `currentUser` and the own row, so a strict-equality implementation would have passed every committed test despite violating the Envelope's own `3`/`"3"` identity requirement — added a fifth case with a string-vs-number id mismatch. Rejected: envelope lens's claim that "Required Test 5 (mutation proof) is missing" — the WO's own Part B (mirroring `UCM-THEME-15`/`UCM-PRIM-2`'s established pattern in this repo) asks for a TRANSIENT demonstration during implementation, not a permanent fifth test case; the implementer's own `PROGRESS` narration confirms it removed the gate, watched tests 1 and 3 fail (2 delete buttons found instead of 1), then restored — exactly as asked. Envelope lens's claim that "package.json remains 3.9.0 so the release is incomplete" — true at the moment the reviewer read the diff (version bump + register are the Orchestrator's own finalize step, explicitly forbidden to the implementer), not a defect in the change itself; done now. `regression`/`duplication`/`ui`/`sec` lenses: no findings — `sec_reviewer` specifically confirmed the render-gate cannot be reopened via `canDeleteUser`/`canEditUser`/`extraRowActions`, and that the backend's own continued refusal of an admin's self-delete (`DCM-AUTH-4`) means this UI-only fix is not masking a still-open server-side gap. Orchestrator's own scoped run: `tests/UserListComponent.test.jsx` 7/7 green + `tests/userScreenTranslations.test.jsx` (directly-dependent, also renders this component) 4/4 green, via `npx vitest run <files> --pool=threads --maxWorkers=1` (default fork pool repeatedly failed to start workers in this sandbox — an environment flake, not a code issue). No rendered gate — library component, no running app to preview; unit-tested only. **Released as `3.9.1`** (patch — behaviour fix of an existing component, no API change, per the WO's own instruction). `npm view @micha.bigler/ui-core-micha version` will read `3.9.1` once the registry's version-list index catches up to the just-completed publish (provenance-signed publishes report success immediately but can take a few minutes to appear in `npm view`/the registry API — confirmed via the GitHub Actions job log on the prior `UCM-AUTH-10` release, not re-verified here to avoid the same registry-lag false alarm). **Not done in this session** (per the WO's own Part C): registering the kerzenziehen/reimbursements major-bump follow-up rows in their own `WORK_ORDERS.md` registers — flagged for whoever next works in those repos. |
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useContext, useState } from 'react';
3
3
  import { Avatar, Box, Divider, IconButton, Menu, MenuItem, Typography, } from '@mui/material';
4
4
  import { useTheme } from '@mui/material/styles';
@@ -149,20 +149,21 @@ function extractAuthSignal(data) {
149
149
  return { code: null, i18nKey: null };
150
150
  }
151
151
  apiClient.interceptors.response.use((response) => response, async (error) => {
152
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
153
- const status = (_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : null;
154
- const data = (_d = (_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.data) !== null && _d !== void 0 ? _d : {};
152
+ var _a, _b, _c, _d, _e;
153
+ var _f, _g, _h, _j;
154
+ const status = (_f = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _f !== void 0 ? _f : null;
155
+ const data = (_g = (_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.data) !== null && _g !== void 0 ? _g : {};
155
156
  const { code, i18nKey } = extractAuthSignal(data);
156
157
  const isAuthStatus = status === 401 || status === 403;
157
158
  const isNotAuthenticated = code === "not_authenticated" || i18nKey === "auth.not_authenticated";
158
159
  // Reauthentication gate: allauth returns 401 + flows[{id:"reauthenticate"}]
159
160
  // when the session is stale before a sensitive operation (e.g. adding a passkey).
160
161
  // Show the password-confirm modal, wait for it to complete, then retry once.
161
- const flows = (_g = (_f = (_e = data === null || data === void 0 ? void 0 : data.data) === null || _e === void 0 ? void 0 : _e.flows) !== null && _f !== void 0 ? _f : data === null || data === void 0 ? void 0 : data.flows) !== null && _g !== void 0 ? _g : [];
162
+ const flows = (_j = (_h = (_c = data === null || data === void 0 ? void 0 : data.data) === null || _c === void 0 ? void 0 : _c.flows) !== null && _h !== void 0 ? _h : data === null || data === void 0 ? void 0 : data.flows) !== null && _j !== void 0 ? _j : [];
162
163
  const needsReauth = status === 401 &&
163
164
  Array.isArray(flows) &&
164
165
  flows.some((f) => f.id === "reauthenticate");
165
- if (needsReauth && !((_h = error.config) === null || _h === void 0 ? void 0 : _h._reauthRetried)) {
166
+ if (needsReauth && !((_d = error.config) === null || _d === void 0 ? void 0 : _d._reauthRetried)) {
166
167
  try {
167
168
  await requestReauth();
168
169
  return apiClient(Object.assign(Object.assign({}, error.config), { _reauthRetried: true }));
@@ -175,7 +176,7 @@ apiClient.interceptors.response.use((response) => response, async (error) => {
175
176
  // expect to handle 401 silently and must not trigger a redirect-on-mount.
176
177
  // Carried as an axios config property, so it never travels to the backend
177
178
  // (would otherwise trigger a CORS preflight on cross-origin requests).
178
- const skipRedirect = ((_j = error === null || error === void 0 ? void 0 : error.config) === null || _j === void 0 ? void 0 : _j.skipAuthRedirect) === true;
179
+ const skipRedirect = ((_e = error === null || error === void 0 ? void 0 : error.config) === null || _e === void 0 ? void 0 : _e.skipAuthRedirect) === true;
179
180
  if (isAuthStatus && isNotAuthenticated && !skipRedirect) {
180
181
  redirectToLoginOnce();
181
182
  }
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useState } from 'react';
3
3
  import { Badge, Box, Button, Collapse, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, IconButton, List, ListItem, ListItemButton, ListItemText, Menu, MenuItem, Stack, TextField } from '@mui/material';
4
4
  import { MoreVert } from '@mui/icons-material';
@@ -43,7 +43,8 @@ export function ChatThread({ turns = [], tool = null, notice = null, hasOlder =
43
43
  }, [(_a = turns[0]) === null || _a === void 0 ? void 0 : _a.id]);
44
44
  const loadingOlder = useRef(false); // one request at a time, also across rapid scroll events
45
45
  const loadOlder = async () => {
46
- var _a, _b;
46
+ var _a;
47
+ var _b;
47
48
  if (loadingOlder.current)
48
49
  return;
49
50
  loadingOlder.current = true;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import { Alert, Box, Button, Chip, Drawer, Stack, Typography, useMediaQuery, useTheme } from '@mui/material';
4
4
  import { useTranslation } from 'react-i18next';
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useMemo, useRef, useState } from 'react';
3
3
  import { Box, Button, Chip, Drawer, List, ListItemButton, ListItemText, Popover, Stack, ToggleButton, ToggleButtonGroup, Typography, useMediaQuery, useTheme } from '@mui/material';
4
4
  import { useTranslation } from 'react-i18next';
@@ -28,10 +28,7 @@ export function ModelPicker({ entries = [], selection, onChange, disabled = fals
28
28
  onChange === null || onChange === void 0 ? void 0 : onChange({ entry: entry.id, effort });
29
29
  };
30
30
  const content = (_jsx(Box, { sx: { p: 2, minWidth: narrow ? 0 : 320, maxWidth: '100%' }, children: _jsxs(Stack, { spacing: 1.5, children: [Object.entries(groups).map(([driver, items]) => (_jsxs(Box, { role: "group", "aria-label": driverName(t, driver), children: [_jsx(Typography, { variant: "overline", color: "text.secondary", children: driverName(t, driver) }), _jsx(List, { dense: true, disablePadding: true, children: items.map((entry) => (_jsxs(ListItemButton, { selected: entry.id === (selection === null || selection === void 0 ? void 0 : selection.entry), onClick: () => pickEntry(entry), sx: { minHeight: 40, borderRadius: 1 }, children: [_jsx(ListItemText, { primary: entry.label || entry.model }), entry.billing === 'pay_per_use' ? _jsx(Chip, { size: "small", variant: "outlined", label: t('Chat.payPerUse') }) : null] }, entry.id))) })] }, driver))), _jsxs(Box, { children: [_jsx(Typography, { variant: "overline", color: "text.secondary", children: t('Chat.effortLabel') }), _jsx(ToggleButtonGroup, { exclusive: true, fullWidth: true, size: "small", "aria-label": t('Chat.effortLabel'), value: (selection === null || selection === void 0 ? void 0 : selection.effort) || null, onChange: (_event, effort) => { if (effort && active)
31
- onChange === null || onChange === void 0 ? void 0 : onChange({ entry: active.id, effort }); }, children: EFFORT_LEVELS.map((effort) => {
32
- var _a;
33
- return (_jsx(ToggleButton, { value: effort, disabled: !((_a = active === null || active === void 0 ? void 0 : active.efforts) === null || _a === void 0 ? void 0 : _a.includes(effort)), sx: { minHeight: 40 }, children: t(`Chat.effort.${effort}`) }, effort));
34
- }) })] })] }) }));
31
+ onChange === null || onChange === void 0 ? void 0 : onChange({ entry: active.id, effort }); }, children: EFFORT_LEVELS.map((effort) => { var _a; return (_jsx(ToggleButton, { value: effort, disabled: !((_a = active === null || active === void 0 ? void 0 : active.efforts) === null || _a === void 0 ? void 0 : _a.includes(effort)), sx: { minHeight: 40 }, children: t(`Chat.effort.${effort}`) }, effort)); }) })] })] }) }));
35
32
  const label = active ? [active.label || active.model, (selection === null || selection === void 0 ? void 0 : selection.effort) && t(`Chat.effort.${selection.effort}`)].filter(Boolean).join(' · ') : t('Chat.chooseModel');
36
33
  return (_jsxs(_Fragment, { children: [_jsx(Button, { ref: anchor, size: "small", color: "inherit", disabled: disabled || entries.length === 0, onClick: () => setOpen(true), "aria-haspopup": "dialog", children: label }), narrow ? (_jsx(Drawer, { anchor: "bottom", open: isOpen, onClose: () => setOpen(false), children: content })) : (_jsx(Popover, { open: isOpen, anchorEl: anchor.current, onClose: () => setOpen(false), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, children: content }))] }));
37
34
  }
@@ -32,7 +32,7 @@ export function useChatSocket(chatId, { wsUrl = defaultChatWsUrl, onFrame, onOpe
32
32
  if (frame && typeof frame === 'object')
33
33
  (_b = (_a = callbacks.current).onFrame) === null || _b === void 0 ? void 0 : _b.call(_a, frame, chatId);
34
34
  }
35
- catch ( /* Ignore malformed frames. */_c) { /* Ignore malformed frames. */ } };
35
+ catch (_c) { /* Ignore malformed frames. */ } };
36
36
  ws.onclose = (event) => {
37
37
  var _a, _b, _c, _d, _e, _f;
38
38
  if (!alive || socket.current !== ws)
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useState, useEffect } from 'react';
3
3
  import { Box, TextField, Button, Typography, Divider, } from '@mui/material';
4
4
  import { useTranslation } from 'react-i18next';
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // src/auth/components/SecurityComponent.jsx
3
3
  import React, { useContext, useMemo, useState } from 'react';
4
4
  import { Box, Typography, Divider, Alert, } from '@mui/material';
@@ -265,10 +265,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
265
265
  align: 'center',
266
266
  headerAlign: 'center',
267
267
  valueGetter: (row) => { var _a; return Boolean(((_a = row === null || row === void 0 ? void 0 : row.profile) === null || _a === void 0 ? void 0 : _a.is_new) || (row === null || row === void 0 ? void 0 : row.is_new)); },
268
- renderCell: (row) => {
269
- var _a;
270
- return renderBooleanStatusIcon(Boolean(((_a = row === null || row === void 0 ? void 0 : row.profile) === null || _a === void 0 ? void 0 : _a.is_new) || (row === null || row === void 0 ? void 0 : row.is_new)), t('Common.YES', 'Yes'), t('Common.NO', 'No'));
271
- },
268
+ renderCell: (row) => { var _a; return renderBooleanStatusIcon(Boolean(((_a = row === null || row === void 0 ? void 0 : row.profile) === null || _a === void 0 ? void 0 : _a.is_new) || (row === null || row === void 0 ? void 0 : row.is_new)), t('Common.YES', 'Yes'), t('Common.NO', 'No')); },
272
269
  });
273
270
  }
274
271
  if (showSuccessfulLoginColumn) {
@@ -281,10 +278,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
281
278
  align: 'center',
282
279
  headerAlign: 'center',
283
280
  valueGetter: (row) => { var _a; return Boolean((_a = row === null || row === void 0 ? void 0 : row.successful_login) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.last_login); },
284
- renderCell: (row) => {
285
- var _a;
286
- return renderBooleanStatusIcon(Boolean((_a = row === null || row === void 0 ? void 0 : row.successful_login) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.last_login), t('Common.YES', 'Yes'), t('Common.NO', 'No'));
287
- },
281
+ renderCell: (row) => { var _a; return renderBooleanStatusIcon(Boolean((_a = row === null || row === void 0 ? void 0 : row.successful_login) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.last_login), t('Common.YES', 'Yes'), t('Common.NO', 'No')); },
288
282
  });
289
283
  }
290
284
  if (showRoleColumn) {
@@ -9,7 +9,7 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
12
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import React, { useId, useRef, useState } from 'react';
14
14
  import { Alert, Box, Button, CircularProgress, Paper, Stack, Tooltip, Typography, } from '@mui/material';
15
15
  import { useTranslation } from 'react-i18next';
@@ -40,7 +40,8 @@ export function scaleBubbleRadius(value, maxValue, { minRadius = BUBBLE_MIN_RADI
40
40
  // continuous-scale colour) can only be expressed by replacing the marker itself.
41
41
  function makeMarkerSlot(seriesById, { sizeAccessor, chartMax, getPointStyle }) {
42
42
  return function PresetScatterMarker(_a) {
43
- var _b, _c, _d, _e, _f, _g;
43
+ var _b, _c;
44
+ var _d, _e, _f, _g;
44
45
  var { seriesId, dataIndex, color, x, y, isHighlighted, isFaded, size, onClick } = _a, rest = __rest(_a, ["seriesId", "dataIndex", "color", "x", "y", "isHighlighted", "isFaded", "size", "onClick"]);
45
46
  const point = (_c = (_b = seriesById[seriesId]) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c[dataIndex];
46
47
  const style = point && getPointStyle ? (getPointStyle(point, { seriesId, dataIndex }) || {}) : {};
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useId, useState } from 'react';
3
3
  import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
4
4
  import { Box, ButtonBase, Drawer, List, ListItem, ListItemButton, ListItemText, Paper, Stack, Typography, } from '@mui/material';
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import CloseIcon from '@mui/icons-material/Close';
3
3
  import DownloadIcon from '@mui/icons-material/DownloadOutlined';
4
4
  import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined';
@@ -49,7 +49,7 @@ export function AttachmentList({ attachments = [] }) {
49
49
  setPreviews((current) => (Object.assign(Object.assign({}, current), { [attachment.id]: url })));
50
50
  }
51
51
  }
52
- catch ( /* The image remains available through the download action. */_a) { /* The image remains available through the download action. */ }
52
+ catch (_a) { /* The image remains available through the download action. */ }
53
53
  });
54
54
  return () => { live = false; urls.forEach((url) => URL.revokeObjectURL(url)); };
55
55
  }, [attachments, getAttachmentThumbnail]);
@@ -110,7 +110,8 @@ export function Composer({ conversationId, conversation, replyTarget = null, onR
110
110
  };
111
111
  const removeFile = (index) => setFiles((current) => current.filter((_, fileIndex) => fileIndex !== index));
112
112
  const insertEmoji = (emoji) => {
113
- var _a, _b, _c, _d;
113
+ var _a, _b;
114
+ var _c, _d;
114
115
  const input = ((_b = (_a = messageInputRef.current) === null || _a === void 0 ? void 0 : _a.querySelector) === null || _b === void 0 ? void 0 : _b.call(_a, 'textarea')) || messageInputRef.current;
115
116
  const start = (_c = input === null || input === void 0 ? void 0 : input.selectionStart) !== null && _c !== void 0 ? _c : body.length;
116
117
  const end = (_d = input === null || input === void 0 ? void 0 : input.selectionEnd) !== null && _d !== void 0 ? _d : start;
@@ -32,8 +32,5 @@ export function ConversationLaunchers({ groupLaunchers = [], broadcastLauncher,
32
32
  autoOpened.current = true;
33
33
  open('broadcast', (_a = broadcastLauncher.payload) !== null && _a !== void 0 ? _a : broadcastLauncher);
34
34
  }, [autoOpenBroadcast, broadcastLauncher]); // one intentional auto-open per mounted launcher
35
- return (_jsxs(Stack, { spacing: 1, children: [broadcastLauncher && !autoOpenBroadcast && (_jsx(Button, { variant: "outlined", onClick: () => { var _a; return open('broadcast', (_a = broadcastLauncher.payload) !== null && _a !== void 0 ? _a : broadcastLauncher); }, children: broadcastLauncher.label || t('MessagingLaunchers.OPEN_BROADCAST') })), groupLaunchers.map((launcher) => {
36
- var _a;
37
- return (_jsx(Button, { variant: "outlined", onClick: () => { var _a; return open('group', (_a = launcher.payload) !== null && _a !== void 0 ? _a : launcher); }, children: launcher.label || t('MessagingLaunchers.OPEN_GROUP') }, (_a = launcher.id) !== null && _a !== void 0 ? _a : launcher.label));
38
- }), error && _jsx(Alert, { severity: "error", children: error })] }));
35
+ return (_jsxs(Stack, { spacing: 1, children: [broadcastLauncher && !autoOpenBroadcast && (_jsx(Button, { variant: "outlined", onClick: () => { var _a; return open('broadcast', (_a = broadcastLauncher.payload) !== null && _a !== void 0 ? _a : broadcastLauncher); }, children: broadcastLauncher.label || t('MessagingLaunchers.OPEN_BROADCAST') })), groupLaunchers.map((launcher) => { var _a; return (_jsx(Button, { variant: "outlined", onClick: () => { var _a; return open('group', (_a = launcher.payload) !== null && _a !== void 0 ? _a : launcher); }, children: launcher.label || t('MessagingLaunchers.OPEN_GROUP') }, (_a = launcher.id) !== null && _a !== void 0 ? _a : launcher.label)); }), error && _jsx(Alert, { severity: "error", children: error })] }));
39
36
  }
@@ -64,7 +64,7 @@ export function ConversationList({ onOpen, groupLaunchers, broadcastLauncher, au
64
64
  const [loadingMore, setLoadingMore] = useState(false);
65
65
  const [error, setError] = useState(null);
66
66
  const conversations = useMemo(() => ordered(Object.values(cache.conversations).filter((item) => includeArchived || !item.archived_at && !item.archived)), [cache.conversations, includeArchived]);
67
- const unreadFor = (conversation) => { var _a, _b, _c; return (_c = (_a = conversation.unread_count) !== null && _a !== void 0 ? _a : (_b = cache.unread.by_conversation) === null || _b === void 0 ? void 0 : _b[conversation.id]) !== null && _c !== void 0 ? _c : 0; };
67
+ const unreadFor = (conversation) => { var _a; var _b, _c; return (_c = (_b = conversation.unread_count) !== null && _b !== void 0 ? _b : (_a = cache.unread.by_conversation) === null || _a === void 0 ? void 0 : _a[conversation.id]) !== null && _c !== void 0 ? _c : 0; };
68
68
  // Once opened, the broadcast conversation becomes a normal, recency-sorted
69
69
  // row with real unread/preview state — hide the static launcher so it isn't
70
70
  // shown twice (button + list row) with the button's stale, unread-less copy.
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useId, useMemo, useState } from 'react';
3
3
  import { Alert, Autocomplete, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, TextField, Typography, useMediaQuery, } from '@mui/material';
4
4
  import { useTheme } from '@mui/material/styles';
Binary file
@@ -24,7 +24,8 @@ export const POLL_CARD_CHECKBOX_SX = { pt: 0.25 };
24
24
  * rejected vote.
25
25
  */
26
26
  export function PollCard({ message, canClose: canCloseProp }) {
27
- var _a, _b, _c, _d, _e, _f, _g, _h;
27
+ var _a, _b;
28
+ var _c, _d, _e, _f, _g, _h;
28
29
  const { t } = useTranslation();
29
30
  const { castPollVote, closeConversationPoll, currentUser } = useMessaging();
30
31
  const poll = message === null || message === void 0 ? void 0 : message.poll;
@@ -47,8 +48,8 @@ export function PollCard({ message, canClose: canCloseProp }) {
47
48
  if (!poll)
48
49
  return null;
49
50
  const closed = Boolean(poll.closed_at);
50
- const creatorId = (_c = (_a = poll.created_by_id) !== null && _a !== void 0 ? _a : (_b = poll.created_by) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : poll.created_by;
51
- const canClose = (_g = (_e = (_d = canCloseProp !== null && canCloseProp !== void 0 ? canCloseProp : poll.can_close) !== null && _d !== void 0 ? _d : message === null || message === void 0 ? void 0 : message.can_close) !== null && _e !== void 0 ? _e : (_f = message === null || message === void 0 ? void 0 : message.permissions) === null || _f === void 0 ? void 0 : _f.can_close) !== null && _g !== void 0 ? _g : (creatorId != null && creatorId === (currentUser === null || currentUser === void 0 ? void 0 : currentUser.id));
51
+ const creatorId = (_d = (_c = poll.created_by_id) !== null && _c !== void 0 ? _c : (_a = poll.created_by) === null || _a === void 0 ? void 0 : _a.id) !== null && _d !== void 0 ? _d : poll.created_by;
52
+ const canClose = (_g = (_f = (_e = canCloseProp !== null && canCloseProp !== void 0 ? canCloseProp : poll.can_close) !== null && _e !== void 0 ? _e : message === null || message === void 0 ? void 0 : message.can_close) !== null && _f !== void 0 ? _f : (_b = message === null || message === void 0 ? void 0 : message.permissions) === null || _b === void 0 ? void 0 : _b.can_close) !== null && _g !== void 0 ? _g : (creatorId != null && creatorId === (currentUser === null || currentUser === void 0 ? void 0 : currentUser.id));
52
53
  // Percentage denominator (MSG-6i): the number of distinct people who
53
54
  // answered this poll, not sum(vote_count) across options. For a
54
55
  // multi-select poll one person can vote for several options, so summing
@@ -109,11 +110,12 @@ export function PollCard({ message, canClose: canCloseProp }) {
109
110
  }
110
111
  };
111
112
  const optionLabel = (option) => {
112
- var _a, _b, _c;
113
+ var _a;
114
+ var _b, _c;
113
115
  // dcm's poll projection gives voter user ids only, never names — there
114
116
  // is no user-directory endpoint (same structural limit already
115
117
  // established for the DM candidate list).
116
- const voteCount = (_c = (_a = option.vote_count) !== null && _a !== void 0 ? _a : (_b = option.voters) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0;
118
+ const voteCount = (_c = (_b = option.vote_count) !== null && _b !== void 0 ? _b : (_a = option.voters) === null || _a === void 0 ? void 0 : _a.length) !== null && _c !== void 0 ? _c : 0;
117
119
  const percent = respondentCount > 0 ? Math.round((voteCount / respondentCount) * 100) : 0;
118
120
  const votedByMe = selected.includes(option.id);
119
121
  const resultText = t('MessagingPoll.OPTION_RESULT', { count: voteCount, percent });
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import DoneAllOutlinedIcon from '@mui/icons-material/DoneAllOutlined';
3
3
  import DoneOutlinedIcon from '@mui/icons-material/DoneOutlined';
4
4
  import { ButtonBase, List, ListItem, Popover, Tooltip, Typography } from '@mui/material';
@@ -41,7 +41,7 @@ export function ReadTicks({ messageId, conversation }) {
41
41
  }, [getMessageReadStatus, messageId]);
42
42
  if (!status)
43
43
  return null;
44
- const recipientDetail = ((_a = status.recipient_detail) === null || _a === void 0 ? void 0 : _a.map((recipient) => { var _a, _b; return (Object.assign(Object.assign({}, recipient), { last_read_at: (_b = (_a = cache.receipts[`${conversation === null || conversation === void 0 ? void 0 : conversation.id}:${recipient.user_id}`]) === null || _a === void 0 ? void 0 : _a.last_read_at) !== null && _b !== void 0 ? _b : recipient.last_read_at })); })) || status.recipient_detail;
44
+ const recipientDetail = ((_a = status.recipient_detail) === null || _a === void 0 ? void 0 : _a.map((recipient) => { var _a; var _b; return (Object.assign(Object.assign({}, recipient), { last_read_at: (_b = (_a = cache.receipts[`${conversation === null || conversation === void 0 ? void 0 : conversation.id}:${recipient.user_id}`]) === null || _a === void 0 ? void 0 : _a.last_read_at) !== null && _b !== void 0 ? _b : recipient.last_read_at })); })) || status.recipient_detail;
45
45
  const directReceipt = (conversation === null || conversation === void 0 ? void 0 : conversation.kind) === 'direct' && (conversation === null || conversation === void 0 ? void 0 : conversation.other_user_id) != null
46
46
  ? cache.receipts[`${conversation.id}:${conversation.other_user_id}`]
47
47
  : null;
@@ -15,7 +15,8 @@ function replyToId(message) { var _a; return (_a = message.reply_to_id) !== null
15
15
  // MessagingProvider.jsx's applyFrame merge-trap handling). A null receipt with
16
16
  // at least one reply counts as unread; no replies means no marker at all.
17
17
  function hasUnreadReplies(message, receipts, userId) {
18
- var _a, _b;
18
+ var _a;
19
+ var _b;
19
20
  if (!message.reply_count)
20
21
  return false;
21
22
  const lastReadAt = (_b = (_a = receipts[`thread:${message.id}:${userId}`]) === null || _a === void 0 ? void 0 : _a.last_read_at) !== null && _b !== void 0 ? _b : message.thread_last_read_at;
@@ -77,10 +78,11 @@ export function Thread({ conversationId, onReplyTargetChange, canModerateMessage
77
78
  if (conversationId == null)
78
79
  return undefined;
79
80
  return subscribe(MESSAGING_ENVELOPE, (frame) => {
80
- var _a, _b;
81
+ var _a;
82
+ var _b;
81
83
  if (frame.type !== 'message')
82
84
  return;
83
- const frameConversationId = (_a = frame.conversation_id) !== null && _a !== void 0 ? _a : (_b = frame.message) === null || _b === void 0 ? void 0 : _b.conversation_id;
85
+ const frameConversationId = (_b = frame.conversation_id) !== null && _b !== void 0 ? _b : (_a = frame.message) === null || _a === void 0 ? void 0 : _a.conversation_id;
84
86
  if (String(frameConversationId) !== String(conversationId))
85
87
  return;
86
88
  if (document.visibilityState === 'hidden')
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import NotificationsIcon from '@mui/icons-material/Notifications';
4
4
  import { Badge, Box, Button, IconButton, List, ListItemButton, ListItemText, Menu, Typography, } from '@mui/material';
@@ -20,10 +20,7 @@ export function NotificationBell({ resolveLink }) {
20
20
  if (resolveLink && ((_a = notification.content) === null || _a === void 0 ? void 0 : _a.link))
21
21
  resolveLink(notification.content.link);
22
22
  };
23
- return (_jsxs(_Fragment, { children: [_jsx(IconButton, { "aria-label": t('NotificationBell.TITLE'), color: "inherit", onClick: (event) => setAnchorEl(event.currentTarget), children: _jsx(Badge, { badgeContent: unreadCount, color: "error", max: 99, children: _jsx(NotificationsIcon, {}) }) }), _jsx(Menu, { anchorEl: anchorEl, anchorOrigin: { horizontal: 'right', vertical: 'bottom' }, onClose: () => setAnchorEl(null), open: open, transformOrigin: { horizontal: 'right', vertical: 'top' }, children: _jsxs(Box, { sx: { minWidth: 320 }, children: [_jsxs(Box, { sx: { alignItems: 'center', display: 'flex', justifyContent: 'space-between', px: 2, py: 1 }, children: [_jsx(Typography, { variant: "subtitle1", children: t('NotificationBell.TITLE') }), unreadIds.length > 0 && (_jsx(Button, { onClick: () => markSeen(unreadIds), size: "small", children: t('NotificationBell.MARK_ALL') }))] }), notifications.length === 0 ? (_jsx(Typography, { sx: { px: 2, py: 2 }, variant: "body2", children: t('NotificationBell.EMPTY') })) : (_jsx(List, { dense: true, disablePadding: true, children: notifications.map((notification) => {
24
- var _a, _b, _c, _d;
25
- return (_jsx(ListItemButton, { onClick: () => handleItemClick(notification), children: _jsx(ListItemText, { primary: t((_a = notification.content) === null || _a === void 0 ? void 0 : _a.title_key, ((_b = notification.content) === null || _b === void 0 ? void 0 : _b.params) || {}), secondary: ((_c = notification.content) === null || _c === void 0 ? void 0 : _c.body_key)
26
- ? t(notification.content.body_key, notification.content.params || {})
27
- : undefined, slotProps: { primary: { fontWeight: notification.seen || notification.dismissed ? 'regular' : 'bold' } } }) }, (_d = notification.id) !== null && _d !== void 0 ? _d : notification.notification_id));
28
- }) }))] }) })] }));
23
+ return (_jsxs(_Fragment, { children: [_jsx(IconButton, { "aria-label": t('NotificationBell.TITLE'), color: "inherit", onClick: (event) => setAnchorEl(event.currentTarget), children: _jsx(Badge, { badgeContent: unreadCount, color: "error", max: 99, children: _jsx(NotificationsIcon, {}) }) }), _jsx(Menu, { anchorEl: anchorEl, anchorOrigin: { horizontal: 'right', vertical: 'bottom' }, onClose: () => setAnchorEl(null), open: open, transformOrigin: { horizontal: 'right', vertical: 'top' }, children: _jsxs(Box, { sx: { minWidth: 320 }, children: [_jsxs(Box, { sx: { alignItems: 'center', display: 'flex', justifyContent: 'space-between', px: 2, py: 1 }, children: [_jsx(Typography, { variant: "subtitle1", children: t('NotificationBell.TITLE') }), unreadIds.length > 0 && (_jsx(Button, { onClick: () => markSeen(unreadIds), size: "small", children: t('NotificationBell.MARK_ALL') }))] }), notifications.length === 0 ? (_jsx(Typography, { sx: { px: 2, py: 2 }, variant: "body2", children: t('NotificationBell.EMPTY') })) : (_jsx(List, { dense: true, disablePadding: true, children: notifications.map((notification) => { var _a, _b, _c; var _d; return (_jsx(ListItemButton, { onClick: () => handleItemClick(notification), children: _jsx(ListItemText, { primary: t((_a = notification.content) === null || _a === void 0 ? void 0 : _a.title_key, ((_b = notification.content) === null || _b === void 0 ? void 0 : _b.params) || {}), secondary: ((_c = notification.content) === null || _c === void 0 ? void 0 : _c.body_key)
24
+ ? t(notification.content.body_key, notification.content.params || {})
25
+ : undefined, slotProps: { primary: { fontWeight: notification.seen || notification.dismissed ? 'regular' : 'bold' } } }) }, (_d = notification.id) !== null && _d !== void 0 ? _d : notification.notification_id)); }) }))] }) })] }));
29
26
  }
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useEffect, useState } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import Alert from '@mui/material/Alert';
@@ -57,12 +57,9 @@ export const UNIVERSAL_STEP_DESCRIPTORS = [
57
57
  // verified their PWA manifest/icons are actually installable must not
58
58
  // pass pwaInstallEnabled, or this would show an iOS "Add to Home Screen"
59
59
  // hint for an app that isn't really installable.
60
- condition: (ctx) => {
61
- var _a, _b, _c;
62
- return Boolean(ctx.pwaInstallEnabled
63
- && !((_a = ctx.pwaInstall) === null || _a === void 0 ? void 0 : _a.isStandalone)
64
- && (Boolean((_b = ctx.pwaInstall) === null || _b === void 0 ? void 0 : _b.deferredPrompt) || Boolean((_c = ctx.pwaInstall) === null || _c === void 0 ? void 0 : _c.isIos)));
65
- },
60
+ condition: (ctx) => { var _a, _b, _c; return Boolean(ctx.pwaInstallEnabled
61
+ && !((_a = ctx.pwaInstall) === null || _a === void 0 ? void 0 : _a.isStandalone)
62
+ && (Boolean((_b = ctx.pwaInstall) === null || _b === void 0 ? void 0 : _b.deferredPrompt) || Boolean((_c = ctx.pwaInstall) === null || _c === void 0 ? void 0 : _c.isIos))); },
66
63
  blocking: false,
67
64
  skipable: true,
68
65
  persistDismissed: true,
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useState, useContext, useEffect } from 'react';
3
3
  import { useNavigate, useLocation } from 'react-router-dom';
4
4
  import { Helmet } from 'react-helmet';
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // src/auth/pages/PasswordChangePage.jsx
3
3
  import React, { useState } from 'react';
4
4
  import { Helmet } from 'react-helmet';
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // src/auth/pages/PasswordInvitePage.jsx
3
3
  import React, { useState, useEffect } from 'react';
4
4
  import { useNavigate, useParams, useLocation } from 'react-router-dom';
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // src/auth/pages/PasswordResetRequestPage.jsx
3
3
  import React, { useState } from 'react';
4
4
  import { Helmet } from 'react-helmet';
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useContext, useEffect, useMemo, useRef, useState, } from 'react';
3
3
  import { useNavigate, useLocation } from 'react-router-dom';
4
4
  import { Alert, Box, Button, Stack, TextField, Typography, } from '@mui/material';
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // src/pages/SignupConfirmPage.jsx
3
3
  //
4
4
  // S13: Completes a pending registration. Reads the signed pending-token from
@@ -266,7 +266,8 @@ function createPaletteAwareComponents(palette, isDark) {
266
266
  * objects are created, so runtime app/site overrides cannot leave stale values.
267
267
  */
268
268
  export function createAppTheme(appConfig = {}) {
269
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
269
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
270
+ var _r, _s;
270
271
  if (!((_a = appConfig === null || appConfig === void 0 ? void 0 : appConfig.palette) === null || _a === void 0 ? void 0 : _a.primary)) {
271
272
  throw new TypeError('createAppTheme: appConfig.palette.primary is required.');
272
273
  }
@@ -327,16 +328,16 @@ export function createAppTheme(appConfig = {}) {
327
328
  ? finalPalette.dataSeries.categorical.map((colour) => (deriveLightenedPageColour(colour, [finalPalette.background.paper], 3)))
328
329
  : finalPalette.dataSeries.categorical;
329
330
  const computedPalette = Object.assign(Object.assign({}, derivedStatusEntries), { controlBorder: Object.assign(Object.assign(Object.assign({}, finalPalette.controlBorder), derivedControlBorderEntries), { focus: isDark
330
- ? ((_o = (_m = (_l = appConfig.palette) === null || _l === void 0 ? void 0 : _l.controlBorder) === null || _m === void 0 ? void 0 : _m.focus) !== null && _o !== void 0 ? _o : deriveLightenedColour(primaryMain, contrastSurfaces))
331
+ ? ((_r = (_m = (_l = appConfig.palette) === null || _l === void 0 ? void 0 : _l.controlBorder) === null || _m === void 0 ? void 0 : _m.focus) !== null && _r !== void 0 ? _r : deriveLightenedColour(primaryMain, contrastSurfaces))
331
332
  : deriveFocusColour(finalPalette.primary.main, contrastSurfaces) }), primary: Object.assign(Object.assign(Object.assign({}, finalPalette.primary), (isDark && primaryMain !== finalPalette.primary.main
332
333
  ? withMainShades(primaryMain, primaryContrastText)
333
334
  : { main: primaryMain })), { contrastText: primaryContrastText }), dataSeries: { categorical: derivedSeries } });
334
335
  const resolvedPalette = Object.assign(Object.assign({}, finalPalette), computedPalette);
335
- const fontFamily = ((_p = appConfig.typography) === null || _p === void 0 ? void 0 : _p.fontFamily) || BASELINE_STATIC.typography.fontFamily;
336
+ const fontFamily = ((_o = appConfig.typography) === null || _o === void 0 ? void 0 : _o.fontFamily) || BASELINE_STATIC.typography.fontFamily;
336
337
  // The h4 step below `sm` follows the app's own `sm` if it overrides the breakpoint,
337
338
  // so the step always ends where theme.breakpoints.down('sm') ends.
338
339
  const _t = BASELINE_STATIC.typography.h4, _u = breakpointDownQuery(BREAKPOINT_SM), h4Mobile = _t[_u], h4Base = __rest(_t, [typeof _u === "symbol" ? _u : _u + ""]);
339
- const smValue = (_s = (_r = (_q = appConfig.breakpoints) === null || _q === void 0 ? void 0 : _q.values) === null || _r === void 0 ? void 0 : _r.sm) !== null && _s !== void 0 ? _s : BREAKPOINT_SM;
340
+ const smValue = (_s = (_q = (_p = appConfig.breakpoints) === null || _p === void 0 ? void 0 : _p.values) === null || _q === void 0 ? void 0 : _q.sm) !== null && _s !== void 0 ? _s : BREAKPOINT_SM;
340
341
  return createTheme(Object.assign(Object.assign({}, BASELINE_STATIC), { typography: Object.assign(Object.assign({}, BASELINE_STATIC.typography), { fontFamily, h4: Object.assign(Object.assign({}, h4Base), { [breakpointDownQuery(smValue)]: h4Mobile }) }), fontLoading: Object.assign(Object.assign({}, BASELINE_STATIC.fontLoading), { weights: [...BASELINE_STATIC.fontLoading.weights] }), shadow: isDark ? Object.assign(Object.assign({}, BASELINE_STATIC.shadow), { overlay: DARK_OVERLAY_SHADOW }) : BASELINE_STATIC.shadow, palette: resolvedPalette, themeCompleteness: {
341
342
  baseline: true,
342
343
  exemptions: (isDark
@@ -22,10 +22,7 @@ const TYPOGRAPHY_SURFACES = TYPOGRAPHY_VARIANTS.flatMap((variant) => ['fontSize'
22
22
  const componentPath = (component, path) => pathSurface(`components.${component}.${path}`);
23
23
  const componentKeyLeaf = (component, path, key, leaf) => keyedSurface(`components.${component}.${path}.${key}.${leaf}`, (theme) => { var _a, _b; return (_b = (_a = getPath(theme, `components.${component}.${path}`)) === null || _a === void 0 ? void 0 : _a[key]) === null || _b === void 0 ? void 0 : _b[leaf]; });
24
24
  const COMPONENT_SURFACES = [
25
- keyedSurface('components.MuiCssBaseline.reduced-motion.transitionDuration', (theme) => {
26
- var _a, _b, _c, _d, _e;
27
- return (_e = (_d = (_c = (_b = (_a = theme.components) === null || _a === void 0 ? void 0 : _a.MuiCssBaseline) === null || _b === void 0 ? void 0 : _b.styleOverrides) === null || _c === void 0 ? void 0 : _c['@media (prefers-reduced-motion: reduce)']) === null || _d === void 0 ? void 0 : _d['*, *::before, *::after']) === null || _e === void 0 ? void 0 : _e.transitionDuration;
28
- }),
25
+ keyedSurface('components.MuiCssBaseline.reduced-motion.transitionDuration', (theme) => { var _a, _b, _c, _d, _e; return (_e = (_d = (_c = (_b = (_a = theme.components) === null || _a === void 0 ? void 0 : _a.MuiCssBaseline) === null || _b === void 0 ? void 0 : _b.styleOverrides) === null || _c === void 0 ? void 0 : _c['@media (prefers-reduced-motion: reduce)']) === null || _d === void 0 ? void 0 : _d['*, *::before, *::after']) === null || _e === void 0 ? void 0 : _e.transitionDuration; }),
29
26
  componentPath('MuiButton', 'defaultProps.disableElevation'),
30
27
  componentPath('MuiButton', 'styleOverrides.root.minHeight'),
31
28
  componentPath('MuiButton', 'styleOverrides.root.borderRadius'),
@@ -69,10 +66,7 @@ const COMPONENT_SURFACES = [
69
66
  componentPath('MuiChip', 'styleOverrides.root.height'),
70
67
  componentPath('MuiChip', 'styleOverrides.root.borderRadius'),
71
68
  componentPath('MuiChip', 'styleOverrides.root.fontSize'),
72
- keyedSurface('components.MuiChip.styleOverrides.root.coarse-hit-area.inset', (theme) => {
73
- var _a, _b, _c, _d, _e, _f;
74
- return (_f = (_e = (_d = (_c = (_b = (_a = theme.components) === null || _a === void 0 ? void 0 : _a.MuiChip) === null || _b === void 0 ? void 0 : _b.styleOverrides) === null || _c === void 0 ? void 0 : _c.root) === null || _d === void 0 ? void 0 : _d['@media (any-pointer: coarse)']) === null || _e === void 0 ? void 0 : _e['&::after']) === null || _f === void 0 ? void 0 : _f.inset;
75
- }),
69
+ keyedSurface('components.MuiChip.styleOverrides.root.coarse-hit-area.inset', (theme) => { var _a, _b, _c, _d, _e, _f; return (_f = (_e = (_d = (_c = (_b = (_a = theme.components) === null || _a === void 0 ? void 0 : _a.MuiChip) === null || _b === void 0 ? void 0 : _b.styleOverrides) === null || _c === void 0 ? void 0 : _c.root) === null || _d === void 0 ? void 0 : _d['@media (any-pointer: coarse)']) === null || _e === void 0 ? void 0 : _e['&::after']) === null || _f === void 0 ? void 0 : _f.inset; }),
76
70
  componentPath('MuiBottomNavigation', 'styleOverrides.root.boxShadow'),
77
71
  componentPath('MuiBottomNavigation', 'styleOverrides.root.backgroundColor'),
78
72
  componentPath('MuiBottomNavigationAction', 'styleOverrides.root.padding'),
@@ -95,10 +89,7 @@ const COMPONENT_SURFACES = [
95
89
  componentKeyLeaf('MuiTextField', 'styleOverrides.root', '@media (any-pointer: coarse)', 'minWidth'),
96
90
  componentPath('MuiOutlinedInput', 'styleOverrides.root.minHeight'),
97
91
  componentPath('MuiOutlinedInput', 'styleOverrides.root.borderRadius'),
98
- keyedSurface('components.MuiOutlinedInput.variants.non-multiline-height', (theme) => {
99
- var _a, _b, _c, _d, _e;
100
- return (_e = (_d = (_c = (_b = (_a = theme.components) === null || _a === void 0 ? void 0 : _a.MuiOutlinedInput) === null || _b === void 0 ? void 0 : _b.variants) === null || _c === void 0 ? void 0 : _c.find(({ props }) => (props === null || props === void 0 ? void 0 : props.multiline) === false)) === null || _d === void 0 ? void 0 : _d.style) === null || _e === void 0 ? void 0 : _e.height;
101
- }),
92
+ keyedSurface('components.MuiOutlinedInput.variants.non-multiline-height', (theme) => { var _a, _b, _c, _d, _e; return (_e = (_d = (_c = (_b = (_a = theme.components) === null || _a === void 0 ? void 0 : _a.MuiOutlinedInput) === null || _b === void 0 ? void 0 : _b.variants) === null || _c === void 0 ? void 0 : _c.find(({ props }) => (props === null || props === void 0 ? void 0 : props.multiline) === false)) === null || _d === void 0 ? void 0 : _d.style) === null || _e === void 0 ? void 0 : _e.height; }),
102
93
  componentKeyLeaf('MuiOutlinedInput', 'styleOverrides.root', '@media (any-pointer: coarse)', 'minHeight'),
103
94
  componentKeyLeaf('MuiOutlinedInput', 'styleOverrides.root', '@media (any-pointer: coarse)', 'minWidth'),
104
95
  componentKeyLeaf('MuiOutlinedInput', 'styleOverrides.root', '& .MuiOutlinedInput-notchedOutline', 'borderColor'),
@@ -1,8 +1,9 @@
1
1
  // src/utils/auth-errors.js
2
2
  export function extractErrorInfo(error) {
3
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
4
- const status = (_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : null;
5
- const data = (_d = (_c = error.response) === null || _c === void 0 ? void 0 : _c.data) !== null && _d !== void 0 ? _d : null;
3
+ var _a, _b;
4
+ var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
5
+ const status = (_c = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _c !== void 0 ? _c : null;
6
+ const data = (_d = (_b = error.response) === null || _b === void 0 ? void 0 : _b.data) !== null && _d !== void 0 ? _d : null;
6
7
  if (!data) {
7
8
  return {
8
9
  status,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "3.14.3",
3
+ "version": "3.14.4",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "sideEffects": [
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "private": false,
23
23
  "dependencies": {
24
- "@fontsource/dm-sans": "^5.2.8",
24
+ "@fontsource/dm-sans": "^5.3.0",
25
25
  "react-markdown": "^10.1.0",
26
26
  "remark-gfm": "^4.0.1"
27
27
  },
@@ -46,14 +46,14 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@mui/icons-material": "^7.3.11",
49
- "@mui/x-charts": "^8.0.0",
50
- "@testing-library/react": "^16.3.2",
51
- "@vitejs/plugin-react": "^6.0.5",
52
- "i18next": "^26.2.0",
53
- "jsdom": "^29.1.1",
54
- "react-i18next": "^17.0.8",
55
- "typescript": "^6.0.3",
56
- "vite": "^8.2.0",
57
- "vitest": "^4.1.10"
49
+ "@mui/x-charts": "^8.29.3",
50
+ "@testing-library/react": "^16.3.3",
51
+ "@vitejs/plugin-react": "^6.1.2",
52
+ "i18next": "^26.4.2",
53
+ "jsdom": "^30.1.2",
54
+ "react-i18next": "^17.0.16",
55
+ "typescript": "^7.0.2",
56
+ "vite": "^8.3.4",
57
+ "vitest": "^5.0.3"
58
58
  }
59
59
  }
@@ -7,7 +7,7 @@
7
7
  "jsx": "react-jsx",
8
8
  "module": "ESNext",
9
9
  "target": "ES2017",
10
- "moduleResolution": "Node",
10
+ "moduleResolution": "Bundler",
11
11
  "ignoreDeprecations": "6.0",
12
12
  "esModuleInterop": true,
13
13
  "skipLibCheck": true,
@@ -0,0 +1,65 @@
1
+ # UCM-CI-1 — A dispatch-only CI workflow for ui-core-micha
2
+
3
+ Prepared: 2026-10-15. Target repository: `ui-core-micha` (`main`).
4
+
5
+ # A. Envelope — addressed to everyone
6
+
7
+ ## Goal and expected outcome
8
+
9
+ ui-core-micha has one workflow, `publish.yml`, which tests and then publishes on a push to `main`. A change cannot be proven on a candidate ref without publishing, so work such as `UCM-DEP-1` has no safe CI run. This order adds `.github/workflows/ci.yml`: install, build and test on Linux, started only by hand (`workflow_dispatch`), with no publish step and no secrets.
10
+
11
+ Expected outcome: `gh workflow run ci.yml -R <owner>/ui-core-micha --ref ci-test/<ID>` runs the same install, build and test steps as `publish.yml` against that ref, and nothing else.
12
+
13
+ ## Operator decision this Envelope records (2026-10-15)
14
+
15
+ The operator asked for this workflow in session, as the way to verify `UCM-DEP-1`: "Lege für ui-core-micha einen kleinen ci.yml (nur install, build, test, workflow_dispatch) als eigenes Register-Item an und nutze ihn für UCM-DEP-1."
16
+
17
+ ## Scope
18
+
19
+ - One new file `.github/workflows/ci.yml`:
20
+ - trigger: `workflow_dispatch` only;
21
+ - `permissions: contents: read`;
22
+ - one job on `ubuntu-latest` with the steps of `publish.yml` that precede publishing, using the same action versions and the same Node and pnpm versions as `publish.yml`: checkout, setup-node, setup-pnpm, `pnpm install --frozen-lockfile`, `pnpm run build`, `pnpm run test`;
23
+ - no `npm publish`, no `id-token` permission, no secrets, no `registry-url`.
24
+ - A register row and the `CI-*` prefix in `WORK_ORDERS.md` (Orchestrator).
25
+
26
+ ## Non-goals
27
+
28
+ - No change to `publish.yml`, to any test, or to the package.
29
+ - No push or pull-request trigger: the workflow runs only when dispatched.
30
+ - No matrix, cache tuning or extra jobs.
31
+
32
+ ## Tier
33
+
34
+ Tier 3: CI. Small in effect.
35
+
36
+ ## Risks
37
+
38
+ - A `workflow_dispatch` workflow must exist on the default branch before it can be dispatched at a ref; it lands on `main` first and is then used from `ci-test/<ID>` refs.
39
+ - The Node and pnpm pins duplicate those of `publish.yml`; `UCM-DEP-1` bumps both.
40
+
41
+ ## Required tests to write
42
+
43
+ None: the evidence is one dispatched run of the new workflow on a `ci-test/UCM-CI-1` ref.
44
+
45
+ # B. Implementation map — addressed to the implementer
46
+
47
+ ## Context package
48
+
49
+ Work from this package; do not explore broadly. Target working directory: `C:/Users/Micha Bigler/Documents/webapps/ui-core-micha`.
50
+
51
+ - Read `.github/workflows/publish.yml` once. Copy, verbatim, the `uses:` pins and versions of its checkout, setup-node (`node-version`) and pnpm/action-setup (`version`) steps. Do not copy the version-check script, the `if:` conditions, the npm update or the publish step.
52
+ - Create `.github/workflows/ci.yml`: `name: CI`; `on: workflow_dispatch`; top-level `permissions: contents: read`; one job `test` on `ubuntu-latest` with steps: Check out code, Set up Node, Set up pnpm, Install dependencies (`pnpm install --frozen-lockfile`), Build (`pnpm run build`), Run tests (`pnpm run test`). Same indentation and step-name style as `publish.yml`.
53
+ - Do not edit anything else.
54
+
55
+ ## Preamble
56
+
57
+ > The text above is the COMPLETE spec — the committed WO file's content, not a plan to refine; there is no separate plan file. Read the nearest `AGENTS.md`, the relevant `.codex/skills/<role>/SKILL.md`, and the app `MEMORY.md` ONLY for conventions. Stay in scope; do not touch auth/permissions/deps/schema/CI unless the spec says so; do not update `MEMORY.md`. **Do NOT edit `WORK_ORDERS.md` — the register row and the review verdicts are the orchestrator's alone.** **Your tools are for editing source and test files and for running the tests you wrote — nothing else.** Do NOT install dependencies, touch a lockfile, run a package manager, or tidy up stray files; if something in the repo state blocks you, stop and report it as `RESULT: BLOCKED <reason>` instead of fixing it. Do NOT `git add`/`commit`/`push` — leave every change uncommitted in the working tree for the orchestrator's independent review. This order needs no tests; run none. The orchestrator does the independent review and the dispatched run after you finish.
58
+ >
59
+ > Narrate continuously: a `PLAN: <step1> | <step2> | …` line up front, then a single-line `PROGRESS: [<n>/<total>] <present-tense action>` before every relevant action (and `… done` on completion), spaced so no gap exceeds ~2 min, stdout unbuffered, plus exactly one final `RESULT: DONE|BLOCKED <reason>`.
60
+
61
+ # C. Orchestrator only — not addressed to the implementer
62
+
63
+ > **If this WO was dispatched to you as an implementer: STOP here.**
64
+
65
+ Gate #1: the operator's instruction of 2026-10-15 in the UCM-CHAT-1/2 Orchestrator session (quoted in Part A). Review lenses per Tier 3 for a workflow file (envelope, regression, duplication, tests do not apply: no tests; sec_reviewer on the permissions block). Evidence: a dispatched run on `ci-test/UCM-CI-1` after the file is on `main`.