@owlmeans/web-panel 0.1.18-rc.60 → 0.1.18-rc.61

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.
package/README.md CHANGED
@@ -11,7 +11,7 @@ but never started.
11
11
  ## Installation
12
12
 
13
13
  ```sh
14
- bun add @owlmeans/web-panel@^0.1.18-rc.60
14
+ bun add @owlmeans/web-panel@^0.1.18-rc.61
15
15
  ```
16
16
 
17
17
  Peer requirements (the consuming app provides these): `react`, `react-dom`,
@@ -351,8 +351,8 @@ The dialog's strings are the lib-tier `socket` namespace (`reload.title`, `reloa
351
351
  | `SocketReloadDialog` | component | Blocking reload prompt for `'lost'` sockets |
352
352
  | `LoginScreen`, `LocalizedLoginScreen` | component | Identity-provider choice screen; the localized one binds `translate` to the app's resources |
353
353
  | `appendLoginScreen(ctx, setup?)`, `LoginScreenSetup` | function, type | Register the screen on the login service, with `Logo` and other `LoginScreenProps` |
354
- | `LoginTerms`, `LoginCredit`, `LoginMethodIcon` | component | The screen's terms sentence, credit line and method icons |
355
- | `LoginTermsProps`, `LoginCreditProps` | type | Their props |
354
+ | `LoginTerms`, `LoginPrivacyNotice`, `LoginCredit`, `LoginMethodIcon` | component | The screen's terms confirmation, its privacy-only disclosure (rendered alone once `termsDeferred` moves the checkbox to a post-login step — see `@owlmeans/client-auth/login`), the credit line and method icons |
355
+ | `LoginTermsProps`, `LoginPrivacyNoticeProps`, `LoginCreditProps` | type | Their props |
356
356
  | `scalingToStyles(horizontal?, vertical?)` | function | `BlockScaling` to Tailwind width/height classes |
357
357
  | `useBreakPoint()` | hook | The current Tailwind breakpoint name |
358
358
  | `useMapBreakpoint(map, def?, breakpoint?)` | hook | The map entry for the current breakpoint; throws `SyntaxError` when nothing matches and no `def` is given |
@@ -496,7 +496,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
496
496
  your project's skill store (`.agents/skills/`):
497
497
 
498
498
  ```sh
499
- npx @owlmeans/agent-skills@^0.1.18-rc.38
499
+ npx @owlmeans/agent-skills@^0.1.18-rc.39
500
500
  ```
501
501
 
502
502
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/web-panel",
4
- "version": "0.1.18-rc.60",
5
- "generatedAt": "2026-09-23T19:57:40.709Z",
4
+ "version": "0.1.18-rc.61",
5
+ "generatedAt": "2026-09-24T14:00:35.085Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/web-panel
9
9
 
10
10
  **Layer:** Web (React)
11
- **Install:** `"@owlmeans/web-panel": "^0.1.18-rc.60"` in `dependencies`
11
+ **Install:** `"@owlmeans/web-panel": "^0.1.18-rc.61"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
@@ -33,7 +33,7 @@ user-invocable: false
33
33
  | `useBreakPoint()` | The current Tailwind breakpoint name, tracked on `resize` (`lg` when there is no `window`) |
34
34
  | `useMapBreakpoint(map, def?, breakpoint?)` | The `map` entry for the current breakpoint (or for the `breakpoint` passed), falling back to `def`. It **throws a `SyntaxError`** when neither yields a value, so give it a `def` or cover every breakpoint |
35
35
  | Re-exports from `@owlmeans/client-panel` | Cross-platform panel primitives, incl. `usePanelNav` and the `PanelNav*` types |
36
- | Re-exports from `@owlmeans/client` / `@owlmeans/client-entrypoint` / `@owlmeans/route` | `bind`, `bindAll`, `bindScreen`, `handler`, `provideRequest`, `stab`, `route`, `croute`, `frontend`, `useNavigate`, `useEntrypoint`, `useValue` |
36
+ | Re-exports from `@owlmeans/client` / `@owlmeans/client-entrypoint` / `@owlmeans/route` | `bind`, `bindAll`, `bindScreen`, `handler`, `lazyHandler` / `lazyComponent` (+ `LazyHandler` / `LazyComponent` / `LazyComponentOptions` types — see the `client` skill), `provideRequest`, `stab`, `route`, `croute`, `frontend`, `useNavigate`, `useEntrypoint`, `useValue` |
37
37
  | Re-exports from the surrounding layers | `config`, `service`, `addWebService`, `AppType` / `HOME` / `ROOT` / `BASE` / `GUEST`, `DISPATCHER`, `CAUTHEN_FLOW_ENTER`, `DAUTH_GUARD`, `bindExternalAuthentication`, `Dispatcher`, `appendWebAuthService`, `flow` / `configureFlows` / `useFlow` / `FLOW_PARAM` / `SERVICE_PARAM`, `useI18n*` / `useLanguage` / `composePrefix`, `addI18nApp` / `addI18nLib` / `SUPPORTED_LNGS` |
38
38
 
39
39
  ## Subpath Exports
@@ -421,20 +421,28 @@ import { Form, TextInput, SubmitButton, Button } from '@owlmeans/web-panel'
421
421
  `contained` and the rest `outlined`, calling `onSelect(option)`. `name` prefixes each option's
422
422
  label key as `<name>.<option>`.
423
423
 
424
- ### The terms confirmation — `LoginTerms`
424
+ ### The terms confirmation — `LoginTerms`, `LoginPrivacyNotice`
425
425
 
426
426
  `components/login/terms.tsx` renders `LoginTermsModel` (`@owlmeans/client-panel/auth`) via
427
- `termsSentence` (`@owlmeans/client-auth/login`) rather than re-deriving link/label pairs itself.
428
- **`[data-login-terms]` marks exactly one checkbox, always** — an e2e suite elsewhere in the platform
429
- treats it as a strict locator, so a change here must never add a second one, whatever
427
+ `termsSentence` + `termsLabelResolver` (`@owlmeans/client-auth/login`) rather than re-deriving
428
+ link/label pairs itself. **`[data-login-terms]` marks exactly one checkbox — or NONE, once
429
+ `model.terms.deferred` is true** (`LoginScreen` renders `LoginTerms` when not deferred,
430
+ `LoginPrivacyNotice` when it is): an e2e suite elsewhere in the platform treats the checkbox as a
431
+ strict, at-most-one-match locator, so a change here must never add a second one, whatever
430
432
  billing/product/custom documents a configuration adds. `[data-login-privacy]` is a SIBLING
431
433
  paragraph of the checkbox's `<label>`, never nested inside it — a privacy disclosure is not
432
- something the checkbox consents to. A linked document/notice fragment carries
433
- `data-login-document="<key>"`. `[data-login-revised]` renders only when the resolved terms carry a
434
- `revisedAt` (i.e. the configuration set `showRevision: true` and at least one document has its own
435
- revision date). `LoginTerms` takes `locale?: string` — from `LoginScreen`'s own `locale` prop, never
436
- read from context directly — for `Intl.ListFormat` and a custom document's locale-keyed `labelMap`.
437
- Pinned by `tests/login.spec.ts` → the "with billing, product and custom documents configured" block.
434
+ something the checkbox consents to — and it is exported ON ITS OWN as `LoginPrivacyNotice`: it
435
+ still renders while deferred, since the disclosure was never the checkbox's to remove. A linked
436
+ document/notice fragment carries `data-login-document="<key>"`. `[data-login-revised]` renders only
437
+ when NOT deferred and the resolved terms carry a `revisedAt` (i.e. the configuration set
438
+ `showRevision: true` and at least one document has its own revision date) — deferred, that date
439
+ belongs to the document the checkbox agreed to, and there is no checkbox on this screen to attach it
440
+ to. `LoginTerms`/`LoginPrivacyNotice` take `locale?: string` — from `LoginScreen`'s own `locale`
441
+ prop, never read from context directly — for `Intl.ListFormat` and a custom document's locale-keyed
442
+ `labelMap`. Method buttons carry `aria-disabled={model.blocked || undefined}`, never a literal
443
+ `"false"` — a deferred screen (which never sets `blocked`) then renders identically to an unblocked
444
+ ordinary one, with no special case. Pinned by `tests/login.spec.ts` → the "with billing, product and
445
+ custom documents configured" and "the Terms confirmation deferred to a step" blocks.
438
446
 
439
447
  ## Subpath: `./consent`
440
448
 
@@ -1,5 +1,5 @@
1
- export { handler, useEntrypoint, useValue, useNavigate } from '@owlmeans/client';
2
- export type { Navigator } from '@owlmeans/client';
1
+ export { handler, lazyHandler, lazyComponent, useEntrypoint, useValue, useNavigate } from '@owlmeans/client';
2
+ export type { Navigator, LazyComponent, LazyComponentOptions, LazyHandler } from '@owlmeans/client';
3
3
  export { config } from '@owlmeans/client-context';
4
4
  export { service } from '@owlmeans/config';
5
5
  export { EntrypointOutcome } from '@owlmeans/entrypoint';
@@ -1 +1 @@
1
- {"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../../src/auth/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAChF,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAC7F,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,aAAa,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAA;AACvG,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAC9D,YAAY,EAAE,oBAAoB,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC3E,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAExC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,cAAc,6BAA6B,CAAA;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAC/D,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,aAAa,IAAI,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACnE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../../src/auth/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC5G,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnG,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAC7F,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,aAAa,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAA;AACvG,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAC9D,YAAY,EAAE,oBAAoB,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC3E,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAExC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,cAAc,6BAA6B,CAAA;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAC/D,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,aAAa,IAAI,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACnE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA"}
@@ -1,4 +1,4 @@
1
- export { handler, useEntrypoint, useValue, useNavigate } from '@owlmeans/client';
1
+ export { handler, lazyHandler, lazyComponent, useEntrypoint, useValue, useNavigate } from '@owlmeans/client';
2
2
  export { config } from '@owlmeans/client-context';
3
3
  export { service } from '@owlmeans/config';
4
4
  export { EntrypointOutcome } from '@owlmeans/entrypoint';
@@ -1 +1 @@
1
- {"version":3,"file":"exports.js","sourceRoot":"","sources":["../../src/auth/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEhF,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAExD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAE7F,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAE9D,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAExC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,cAAc,6BAA6B,CAAA;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG/D,OAAO,EAAE,aAAa,IAAI,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAEnE,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"exports.js","sourceRoot":"","sources":["../../src/auth/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE5G,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAExD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAE7F,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAE9D,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAExC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,cAAc,6BAA6B,CAAA;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG/D,OAAO,EAAE,aAAa,IAAI,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAEnE,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../../src/components/login/screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAQ/B,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAWhF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CA4F5C,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,gBAAgB,CASrD,CAAA"}
1
+ {"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../../src/components/login/screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAQ/B,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAWhF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAgG5C,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,gBAAgB,CASrD,CAAA"}
@@ -8,7 +8,7 @@ import { useI18nLib, useLanguage } from '@owlmeans/client-i18n';
8
8
  import { useLoginMethods } from '@owlmeans/client-panel/auth';
9
9
  import { loginAttemptError } from '@owlmeans/client-auth/login';
10
10
  import { LoginMethodIcon } from './icons.js';
11
- import { LoginTerms } from './terms.js';
11
+ import { LoginPrivacyNotice, LoginTerms } from './terms.js';
12
12
  import { LoginCredit } from './credit.js';
13
13
  const VARIANT = {
14
14
  primary: 'default',
@@ -50,13 +50,15 @@ export const LoginScreen = props => {
50
50
  return _jsx("div", { "data-login-screen": true, style: { minHeight: '100dvh', ...props.style }, className: cn('flex w-full items-center justify-center p-4', props.className), children: _jsxs(Card, { className: cn('w-full max-w-sm', props.containerClassName), children: [_jsxs(CardHeader, { className: "items-center gap-2 text-center", children: [Logo != null && _jsx("div", { className: "flex justify-center pb-2", children: isValidElement(Logo) ? Logo : typeof Logo === 'function'
51
51
  ? _jsx(Logo, { className: "h-10 w-auto" }) : Logo }), _jsx(CardTitle, { className: "text-xl", children: props.title ?? t('login.title', 'Sign in') }), _jsx(CardDescription, { children: props.subtitle ?? t('login.subtitle', 'Choose how you would like to continue.') })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [model.methods.length < 1
52
52
  ? _jsx("p", { role: "status", className: "text-sm text-muted-foreground text-center", children: t('login.empty', 'No sign-in method is configured for this application.') })
53
- : _jsx("div", { className: "flex flex-col gap-2", children: model.methods.map((method) => _jsxs(Button, { type: "button", "data-login-method": method.id, variant: VARIANT[method.emphasis ?? 'secondary'] ?? 'outline', "aria-disabled": model.blocked, "data-blocked": model.blocked ? 'true' : undefined,
53
+ : _jsx("div", { className: "flex flex-col gap-2", children: model.methods.map((method) => _jsxs(Button, { type: "button", "data-login-method": method.id, variant: VARIANT[method.emphasis ?? 'secondary'] ?? 'outline', "aria-disabled": model.blocked || undefined, "data-blocked": model.blocked ? 'true' : undefined,
54
54
  // `cursor-pointer` explicitly: the package's private shadcn button stays compatible
55
55
  // with the older primitive style too. Stating it here keeps the sign-in control
56
56
  // visibly actionable independently of a consumer's shadcn setup.
57
57
  className: cn('w-full justify-center gap-2 cursor-pointer', model.blocked && 'opacity-60'), autoFocus: method.id === model.primary?.id, onClick: () => model.select(method), children: [model.busy === method.id
58
58
  ? _jsx(Loader2, { className: "size-4 animate-spin" })
59
- : _jsx(LoginMethodIcon, { name: method.icon, className: "size-4" }), method.label ?? t(`login.method.${method.i18nKey ?? method.id}`, method.id)] }, method.id)) }), model.terms.required && _jsx(LoginTerms, { model: model.terms, translate: t, locale: props.locale }), (model.error ?? attemptError) != null
59
+ : _jsx(LoginMethodIcon, { name: method.icon, className: "size-4" }), method.label ?? t(`login.method.${method.i18nKey ?? method.id}`, method.id)] }, method.id)) }), model.terms.required && (model.terms.deferred
60
+ ? _jsx(LoginPrivacyNotice, { model: model.terms, translate: t, locale: props.locale })
61
+ : _jsx(LoginTerms, { model: model.terms, translate: t, locale: props.locale })), (model.error ?? attemptError) != null
60
62
  && _jsx("p", { role: "alert", "data-login-error": true, className: "text-sm text-destructive text-center", children: model.error ?? t(attemptError, 'Sign-in did not complete. Please try again.') })] }), _jsx("div", { className: "px-6", children: props.footer ?? _jsx(LoginCredit, { model: model.credit, translate: t }) })] }) });
61
63
  };
62
64
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"screen.js","sourceRoot":"","sources":["../../../src/components/login/screen.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"screen.js","sourceRoot":"","sources":["../../../src/components/login/screen.tsx"],"names":[],"mappings":"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"}
@@ -7,6 +7,16 @@ export interface LoginTermsProps {
7
7
  locale?: string;
8
8
  className?: string;
9
9
  }
10
+ export type LoginPrivacyNoticeProps = LoginTermsProps;
11
+ /**
12
+ * The privacy disclosure alone — `notices` (privacy, plus cookies per its own rule), never
13
+ * consented to and never nested inside a checkbox's `<label>`.
14
+ *
15
+ * Split out of {@link LoginTerms} so a sign-in screen whose Terms confirmation has moved to a
16
+ * post-login step (`termsDeferred`, `@owlmeans/client-auth/login`) can still show this line on its
17
+ * own — the disclosure does not move just because the confirmation did.
18
+ */
19
+ export declare const LoginPrivacyNotice: FC<LoginPrivacyNoticeProps>;
10
20
  /**
11
21
  * The sign-in screen's terms confirmation: one consented checkbox for `documents` (terms, plus
12
22
  * billing/product/custom when configured), and a SEPARATE, non-consented disclosure line for
@@ -1 +1 @@
1
- {"version":3,"file":"terms.d.ts","sourceRoot":"","sources":["../../../src/components/login/terms.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAA;AAE1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAIlE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAA;IACtB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACxD,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA+CD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAuD1C,CAAA"}
1
+ {"version":3,"file":"terms.d.ts","sourceRoot":"","sources":["../../../src/components/login/terms.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAA;AAE1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAIlE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAA;IACtB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACxD,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,uBAAuB,GAAG,eAAe,CAAA;AASrD;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC,uBAAuB,CAS1D,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CA8C1C,CAAA"}
@@ -1,41 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../../@/lib/utils.js';
3
- import { termsSentence } from '@owlmeans/client-auth/login';
3
+ import { termsLabelResolver, termsSentence } from '@owlmeans/client-auth/login';
4
+ const renderParts = (parts) => parts.map((part, index) => part.href != null
5
+ ? _jsx("a", { href: part.href, target: "_blank", rel: "noreferrer noopener", "data-login-document": part.documentKey, className: "underline underline-offset-2 hover:text-foreground", children: part.text }, index)
6
+ : _jsx("span", { children: part.text }, index));
4
7
  /**
5
- * English fallbacks for a document's own translated label, keyed by `ResolvedTermsDocument.key`.
8
+ * The privacy disclosure alone — `notices` (privacy, plus cookies per its own rule), never
9
+ * consented to and never nested inside a checkbox's `<label>`.
6
10
  *
7
- * Only the keys this package's own resolver ever produces need one — a custom document always
8
- * carries its own `label`/`labelMap`, or a caller's own `i18nKey` with its own bundle entry.
11
+ * Split out of {@link LoginTerms} so a sign-in screen whose Terms confirmation has moved to a
12
+ * post-login step (`termsDeferred`, `@owlmeans/client-auth/login`) can still show this line on its
13
+ * own — the disclosure does not move just because the confirmation did.
9
14
  */
10
- const DEFAULT_LABEL = {
11
- terms: 'Terms & Conditions',
12
- privacy: 'Privacy Policy',
13
- cookies: 'Cookie Policy',
14
- billing: 'Billing Terms',
15
- product: '{{product}} Product Terms',
15
+ export const LoginPrivacyNotice = ({ model, translate, locale, className }) => {
16
+ const resolveLabel = termsLabelResolver(translate, locale);
17
+ return _jsx("p", { "data-login-privacy": true, className: cn('text-xs text-muted-foreground', className), children: renderParts(termsSentence(translate('login.terms.notice', 'How we handle your personal data: {{notices}}.'), model, locale, resolveLabel)) });
16
18
  };
17
- /**
18
- * A document's own label: a caller's literal `label`, else its `labelMap` for the current locale,
19
- * else its `i18nKey` translated — each with `params` (e.g. `{ product: 'Acme' }`) interpolated
20
- * afterwards, since the `translate` contract this whole package shares is a plain
21
- * `(key, defaultValue) => string` with no interpolation option of its own.
22
- */
23
- const resolveLabelFor = (translate, locale) => (doc) => {
24
- const fromMap = doc.labelMap != null
25
- ? (locale != null ? doc.labelMap[locale] : undefined) ?? Object.values(doc.labelMap)[0]
26
- : undefined;
27
- let label = doc.label ?? fromMap
28
- ?? (doc.i18nKey != null ? translate(doc.i18nKey, DEFAULT_LABEL[doc.key] ?? doc.key) : doc.key);
29
- if (doc.params != null) {
30
- for (const [key, value] of Object.entries(doc.params)) {
31
- label = label.split(`{{${key}}}`).join(value);
32
- }
33
- }
34
- return label;
35
- };
36
- const renderParts = (parts) => parts.map((part, index) => part.href != null
37
- ? _jsx("a", { href: part.href, target: "_blank", rel: "noreferrer noopener", "data-login-document": part.documentKey, className: "underline underline-offset-2 hover:text-foreground", children: part.text }, index)
38
- : _jsx("span", { children: part.text }, index));
39
19
  /**
40
20
  * The sign-in screen's terms confirmation: one consented checkbox for `documents` (terms, plus
41
21
  * billing/product/custom when configured), and a SEPARATE, non-consented disclosure line for
@@ -43,10 +23,10 @@ const renderParts = (parts) => parts.map((part, index) => part.href != null
43
23
  * because nothing is being agreed to there.
44
24
  */
45
25
  export const LoginTerms = ({ model, translate, locale, className }) => {
46
- const resolveLabel = resolveLabelFor(translate, locale);
26
+ const resolveLabel = termsLabelResolver(translate, locale);
47
27
  // Centred, like every other row in the card. The checkbox stays at the start of the sentence
48
28
  // rather than above it, so `justify-center` centres the pair and `text-center` centres the
49
29
  // wrapped lines within it.
50
- return _jsxs("div", { className: cn('flex flex-col items-center gap-1.5 text-center', className), children: [_jsxs("label", { className: "flex items-start justify-center gap-2 text-sm text-muted-foreground cursor-pointer", children: [_jsx("input", { type: "checkbox", "data-login-terms": true, className: "mt-0.5 size-4 shrink-0 accent-primary", checked: model.accepted, "aria-invalid": model.attempted && !model.accepted, onChange: event => model.accept(event.target.checked) }), _jsx("span", { children: renderParts(termsSentence(translate('login.terms.accept', 'I have read and agree to the {{documents}}.'), model, locale, resolveLabel)) })] }), model.revisedAt != null && _jsx("p", { "data-login-revised": true, className: "text-xs text-muted-foreground", children: translate('login.terms.revised', 'Last updated: {{date}}').split('{{date}}').join(model.revisedAt) }), _jsx("p", { "data-login-privacy": true, className: "text-xs text-muted-foreground", children: renderParts(termsSentence(translate('login.terms.notice', 'How we handle your personal data: {{notices}}.'), model, locale, resolveLabel)) }), model.attempted && !model.accepted && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: renderParts(termsSentence(translate('login.terms.required', 'Please confirm the {{documents}} to continue.'), model, locale, resolveLabel)) })] });
30
+ return _jsxs("div", { className: cn('flex flex-col items-center gap-1.5 text-center', className), children: [_jsxs("label", { className: "flex items-start justify-center gap-2 text-sm text-muted-foreground cursor-pointer", children: [_jsx("input", { type: "checkbox", "data-login-terms": true, className: "mt-0.5 size-4 shrink-0 accent-primary", checked: model.accepted, "aria-invalid": model.attempted && !model.accepted, onChange: event => model.accept(event.target.checked) }), _jsx("span", { children: renderParts(termsSentence(translate('login.terms.accept', 'I have read and agree to the {{documents}}.'), model, locale, resolveLabel)) })] }), model.revisedAt != null && _jsx("p", { "data-login-revised": true, className: "text-xs text-muted-foreground", children: translate('login.terms.revised', 'Last updated: {{date}}').split('{{date}}').join(model.revisedAt) }), _jsx(LoginPrivacyNotice, { model: model, translate: translate, locale: locale }), model.attempted && !model.accepted && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: renderParts(termsSentence(translate('login.terms.required', 'Please confirm the {{documents}} to continue.'), model, locale, resolveLabel)) })] });
51
31
  };
52
32
  //# sourceMappingURL=terms.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terms.js","sourceRoot":"","sources":["../../../src/components/login/terms.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"terms.js","sourceRoot":"","sources":["../../../src/components/login/terms.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAA;AAEzC,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAa/E,MAAM,WAAW,GAAG,CAAC,KAA0B,EAAa,EAAE,CAC5D,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI;IAC1C,CAAC,CAAC,YAAe,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,qBAAqB,yBACpD,IAAI,CAAC,WAAW,EACrC,SAAS,EAAC,oDAAoD,YAAE,IAAI,CAAC,IAAI,IAFjE,KAAK,CAEiE;IAChF,CAAC,CAAC,yBAAmB,IAAI,CAAC,IAAI,IAAjB,KAAK,CAAoB,CAAC,CAAA;AAE3C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAgC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE;IACzG,MAAM,YAAY,GAAG,kBAAkB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IAE1D,OAAO,wCAAsB,SAAS,EAAE,EAAE,CAAC,+BAA+B,EAAE,SAAS,CAAC,YACnF,WAAW,CAAC,aAAa,CACxB,SAAS,CAAC,oBAAoB,EAAE,gDAAgD,CAAC,EACjF,KAAK,EAAE,MAAM,EAAE,YAAY,CAC5B,CAAC,GACA,CAAA;AACN,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,UAAU,GAAwB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE;IACzF,MAAM,YAAY,GAAG,kBAAkB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IAE1D,6FAA6F;IAC7F,2FAA2F;IAC3F,2BAA2B;IAC3B,OAAO,eAAK,SAAS,EAAE,EAAE,CAAC,gDAAgD,EAAE,SAAS,CAAC,aAKpF,iBAAO,SAAS,EAAC,oFAAoF,aAOnG,gBACE,IAAI,EAAC,UAAU,4BACf,SAAS,EAAC,uCAAuC,EACjD,OAAO,EAAE,KAAK,CAAC,QAAQ,kBACT,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,EAChD,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GACrD,EACF,yBACG,WAAW,CAAC,aAAa,CACxB,SAAS,CAAC,oBAAoB,EAAE,6CAA6C,CAAC,EAC9E,KAAK,EAAE,MAAM,EAAE,YAAY,CAC5B,CAAC,GACG,IACD,EAEP,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,wCAAsB,SAAS,EAAC,+BAA+B,YACxF,SAAS,CAAC,qBAAqB,EAAE,wBAAwB,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GACjG,EAEJ,KAAC,kBAAkB,IAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAI,EAEzE,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,0BAA0B,YACxF,WAAW,CAAC,aAAa,CACxB,SAAS,CAAC,sBAAsB,EAAE,+CAA+C,CAAC,EAClF,KAAK,EAAE,MAAM,EAAE,YAAY,CAC5B,CAAC,GACA,IACA,CAAA;AACR,CAAC,CAAA"}
@@ -1,6 +1,8 @@
1
- export declare const entrypoints: (import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<any> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<import("@owlmeans/entrypoint").OpenRequest, import("@owlmeans/entrypoint").OpenValue>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
1
+ export declare const entrypoints: (import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<import("@owlmeans/entrypoint").OpenRequest, import("@owlmeans/entrypoint").OpenValue>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
2
2
  query: import("@owlmeans/auth").AuthToken;
3
3
  }, undefined>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
4
4
  query: import("@owlmeans/auth-common").SurrogateQuery;
5
- }, undefined>>)[];
5
+ }, undefined>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
6
+ body: import("@owlmeans/auth").AuthToken;
7
+ }, import("@owlmeans/auth").AuthToken>>)[];
6
8
  //# sourceMappingURL=entrypoints.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW;;;;iBAA4C,CAAA"}
1
+ {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW;;;;;;0CAA4C,CAAA"}
@@ -1,4 +1,5 @@
1
- export { handler, useNavigate, useValue, useEntrypoint } from '@owlmeans/client';
1
+ export { handler, lazyHandler, lazyComponent, useNavigate, useValue, useEntrypoint } from '@owlmeans/client';
2
+ export type { LazyComponent, LazyComponentOptions, LazyHandler } from '@owlmeans/client';
2
3
  export { config } from '@owlmeans/client-context';
3
4
  export { service } from '@owlmeans/config';
4
5
  export { addWebService } from '@owlmeans/client-config';
@@ -1 +1 @@
1
- {"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAC7F,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,MAAM,6BAA6B,CAAA;AAC7E,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AAEhG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAC/D,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnG,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC5G,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACxF,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAC7F,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,MAAM,6BAA6B,CAAA;AAC7E,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AAEhG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAC/D,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnG,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA"}
package/build/exports.js CHANGED
@@ -1,4 +1,4 @@
1
- export { handler, useNavigate, useValue, useEntrypoint } from '@owlmeans/client';
1
+ export { handler, lazyHandler, lazyComponent, useNavigate, useValue, useEntrypoint } from '@owlmeans/client';
2
2
  export { config } from '@owlmeans/client-context';
3
3
  export { service } from '@owlmeans/config';
4
4
  export { addWebService } from '@owlmeans/client-config';
@@ -1 +1 @@
1
- {"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAE7F,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AAEhG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG/D,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnG,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAE5G,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAE7F,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AAEhG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG/D,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnG,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/web-panel",
3
- "version": "0.1.18-rc.60",
3
+ "version": "0.1.18-rc.61",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -51,33 +51,33 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@hookform/resolvers": "^5.2.2",
54
- "@owlmeans/api-config-client": "^0.1.18-rc.35",
55
- "@owlmeans/auth": "^0.1.18-rc.35",
56
- "@owlmeans/auth-common": "^0.1.18-rc.37",
57
- "@owlmeans/basic-envelope": "^0.1.18-rc.36",
58
- "@owlmeans/client": "^0.1.18-rc.40",
59
- "@owlmeans/client-auth": "^0.1.18-rc.47",
60
- "@owlmeans/client-config": "^0.1.18-rc.37",
61
- "@owlmeans/client-context": "^0.1.18-rc.38",
62
- "@owlmeans/client-entrypoint": "^0.1.18-rc.37",
63
- "@owlmeans/client-flow": "^0.1.18-rc.43",
64
- "@owlmeans/client-i18n": "^0.1.18-rc.39",
65
- "@owlmeans/client-panel": "^0.1.18-rc.47",
66
- "@owlmeans/client-route": "^0.1.18-rc.38",
67
- "@owlmeans/client-socket": "^0.1.18-rc.38",
68
- "@owlmeans/config": "^0.1.18-rc.37",
54
+ "@owlmeans/api-config-client": "^0.1.18-rc.36",
55
+ "@owlmeans/auth": "^0.1.18-rc.36",
56
+ "@owlmeans/auth-common": "^0.1.18-rc.38",
57
+ "@owlmeans/basic-envelope": "^0.1.18-rc.37",
58
+ "@owlmeans/client": "^0.1.18-rc.41",
59
+ "@owlmeans/client-auth": "^0.1.18-rc.48",
60
+ "@owlmeans/client-config": "^0.1.18-rc.38",
61
+ "@owlmeans/client-context": "^0.1.18-rc.39",
62
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.38",
63
+ "@owlmeans/client-flow": "^0.1.18-rc.44",
64
+ "@owlmeans/client-i18n": "^0.1.18-rc.40",
65
+ "@owlmeans/client-panel": "^0.1.18-rc.48",
66
+ "@owlmeans/client-route": "^0.1.18-rc.39",
67
+ "@owlmeans/client-socket": "^0.1.18-rc.39",
68
+ "@owlmeans/config": "^0.1.18-rc.38",
69
69
  "@owlmeans/context": "^0.1.18-rc.32",
70
- "@owlmeans/entrypoint": "^0.1.18-rc.36",
71
- "@owlmeans/error": "^0.1.18-rc.35",
72
- "@owlmeans/flow": "^0.1.18-rc.38",
73
- "@owlmeans/i18n": "^0.1.18-rc.36",
70
+ "@owlmeans/entrypoint": "^0.1.18-rc.37",
71
+ "@owlmeans/error": "^0.1.18-rc.36",
72
+ "@owlmeans/flow": "^0.1.18-rc.39",
73
+ "@owlmeans/i18n": "^0.1.18-rc.37",
74
74
  "@owlmeans/route": "^0.1.18-rc.33",
75
- "@owlmeans/state": "^0.1.18-rc.34",
76
- "@owlmeans/web-client": "^0.1.18-rc.50",
77
- "@owlmeans/web-consent": "^0.1.18-rc.31",
78
- "@owlmeans/web-db": "^0.1.18-rc.38",
79
- "@owlmeans/web-flow": "^0.1.18-rc.42",
80
- "@owlmeans/web-router": "^0.1.18-rc.38",
75
+ "@owlmeans/state": "^0.1.18-rc.35",
76
+ "@owlmeans/web-client": "^0.1.18-rc.51",
77
+ "@owlmeans/web-consent": "^0.1.18-rc.32",
78
+ "@owlmeans/web-db": "^0.1.18-rc.39",
79
+ "@owlmeans/web-flow": "^0.1.18-rc.43",
80
+ "@owlmeans/web-router": "^0.1.18-rc.39",
81
81
  "i18next-browser-languagedetector": "^8.0.0",
82
82
  "qrcode.react": "^4.2.0",
83
83
  "react-google-recaptcha": "^3.1.0",
@@ -103,7 +103,7 @@
103
103
  },
104
104
  "devDependencies": {
105
105
  "@owlmeans/dep-config": "workspace:*",
106
- "@owlmeans/test-ui": "^0.1.18-rc.38",
106
+ "@owlmeans/test-ui": "^0.1.18-rc.39",
107
107
  "@radix-ui/react-alert-dialog": "^1.1.15",
108
108
  "@radix-ui/react-dialog": "^1.1.15",
109
109
  "@radix-ui/react-label": "^2.1.0",
@@ -1,6 +1,6 @@
1
1
 
2
- export { handler, useEntrypoint, useValue, useNavigate } from '@owlmeans/client'
3
- export type { Navigator } from '@owlmeans/client'
2
+ export { handler, lazyHandler, lazyComponent, useEntrypoint, useValue, useNavigate } from '@owlmeans/client'
3
+ export type { Navigator, LazyComponent, LazyComponentOptions, LazyHandler } from '@owlmeans/client'
4
4
  export { config } from '@owlmeans/client-context'
5
5
  export { service } from '@owlmeans/config'
6
6
  export { EntrypointOutcome } from '@owlmeans/entrypoint'
@@ -9,7 +9,7 @@ import { useLoginMethods } from '@owlmeans/client-panel/auth'
9
9
  import { loginAttemptError } from '@owlmeans/client-auth/login'
10
10
  import type { LoginMethod, LoginScreenProps } from '@owlmeans/client-auth/login'
11
11
  import { LoginMethodIcon } from './icons.js'
12
- import { LoginTerms } from './terms.js'
12
+ import { LoginPrivacyNotice, LoginTerms } from './terms.js'
13
13
  import { LoginCredit } from './credit.js'
14
14
 
15
15
  const VARIANT: Record<string, 'default' | 'outline' | 'link'> = {
@@ -84,7 +84,7 @@ export const LoginScreen: FC<LoginScreenProps> = props => {
84
84
  // who has not confirmed the terms would press it and be told nothing at all — the
85
85
  // screen would simply seem broken. Blocking happens in the handler, which then says
86
86
  // why.
87
- aria-disabled={model.blocked}
87
+ aria-disabled={model.blocked || undefined}
88
88
  data-blocked={model.blocked ? 'true' : undefined}
89
89
  // `cursor-pointer` explicitly: the package's private shadcn button stays compatible
90
90
  // with the older primitive style too. Stating it here keeps the sign-in control
@@ -103,7 +103,11 @@ export const LoginScreen: FC<LoginScreenProps> = props => {
103
103
  </Button>)}
104
104
  </div>}
105
105
 
106
- {model.terms.required && <LoginTerms model={model.terms} translate={t} locale={props.locale} />}
106
+ {/* Terms mode: the checkbox lives on a post-login step instead (`termsDeferred`) — this
107
+ screen shows the privacy disclosure alone and blocks nothing on it. */}
108
+ {model.terms.required && (model.terms.deferred
109
+ ? <LoginPrivacyNotice model={model.terms} translate={t} locale={props.locale} />
110
+ : <LoginTerms model={model.terms} translate={t} locale={props.locale} />)}
107
111
 
108
112
  {/*
109
113
  A thrown message first, because it names the actual fault; otherwise whatever the
@@ -1,8 +1,8 @@
1
1
  import type { FC, ReactNode } from 'react'
2
2
  import { cn } from '../../@/lib/utils.js'
3
3
  import type { LoginTermsModel } from '@owlmeans/client-panel/auth'
4
- import { termsSentence } from '@owlmeans/client-auth/login'
5
- import type { ResolvedTermsDocument, TermsSentencePart } from '@owlmeans/client-auth/login'
4
+ import { termsLabelResolver, termsSentence } from '@owlmeans/client-auth/login'
5
+ import type { TermsSentencePart } from '@owlmeans/client-auth/login'
6
6
 
7
7
  export interface LoginTermsProps {
8
8
  model: LoginTermsModel
@@ -12,43 +12,7 @@ export interface LoginTermsProps {
12
12
  className?: string
13
13
  }
14
14
 
15
- /**
16
- * English fallbacks for a document's own translated label, keyed by `ResolvedTermsDocument.key`.
17
- *
18
- * Only the keys this package's own resolver ever produces need one — a custom document always
19
- * carries its own `label`/`labelMap`, or a caller's own `i18nKey` with its own bundle entry.
20
- */
21
- const DEFAULT_LABEL: Record<string, string> = {
22
- terms: 'Terms & Conditions',
23
- privacy: 'Privacy Policy',
24
- cookies: 'Cookie Policy',
25
- billing: 'Billing Terms',
26
- product: '{{product}} Product Terms',
27
- }
28
-
29
- /**
30
- * A document's own label: a caller's literal `label`, else its `labelMap` for the current locale,
31
- * else its `i18nKey` translated — each with `params` (e.g. `{ product: 'Acme' }`) interpolated
32
- * afterwards, since the `translate` contract this whole package shares is a plain
33
- * `(key, defaultValue) => string` with no interpolation option of its own.
34
- */
35
- const resolveLabelFor = (
36
- translate: (key: string, defaultValue: string) => string, locale: string | undefined
37
- ) => (doc: ResolvedTermsDocument): string => {
38
- const fromMap = doc.labelMap != null
39
- ? (locale != null ? doc.labelMap[locale] : undefined) ?? Object.values(doc.labelMap)[0]
40
- : undefined
41
- let label = doc.label ?? fromMap
42
- ?? (doc.i18nKey != null ? translate(doc.i18nKey, DEFAULT_LABEL[doc.key] ?? doc.key) : doc.key)
43
-
44
- if (doc.params != null) {
45
- for (const [key, value] of Object.entries(doc.params)) {
46
- label = label.split(`{{${key}}}`).join(value)
47
- }
48
- }
49
-
50
- return label
51
- }
15
+ export type LoginPrivacyNoticeProps = LoginTermsProps
52
16
 
53
17
  const renderParts = (parts: TermsSentencePart[]): ReactNode =>
54
18
  parts.map((part, index) => part.href != null
@@ -57,6 +21,25 @@ const renderParts = (parts: TermsSentencePart[]): ReactNode =>
57
21
  className="underline underline-offset-2 hover:text-foreground">{part.text}</a>
58
22
  : <span key={index}>{part.text}</span>)
59
23
 
24
+ /**
25
+ * The privacy disclosure alone — `notices` (privacy, plus cookies per its own rule), never
26
+ * consented to and never nested inside a checkbox's `<label>`.
27
+ *
28
+ * Split out of {@link LoginTerms} so a sign-in screen whose Terms confirmation has moved to a
29
+ * post-login step (`termsDeferred`, `@owlmeans/client-auth/login`) can still show this line on its
30
+ * own — the disclosure does not move just because the confirmation did.
31
+ */
32
+ export const LoginPrivacyNotice: FC<LoginPrivacyNoticeProps> = ({ model, translate, locale, className }) => {
33
+ const resolveLabel = termsLabelResolver(translate, locale)
34
+
35
+ return <p data-login-privacy className={cn('text-xs text-muted-foreground', className)}>
36
+ {renderParts(termsSentence(
37
+ translate('login.terms.notice', 'How we handle your personal data: {{notices}}.'),
38
+ model, locale, resolveLabel
39
+ ))}
40
+ </p>
41
+ }
42
+
60
43
  /**
61
44
  * The sign-in screen's terms confirmation: one consented checkbox for `documents` (terms, plus
62
45
  * billing/product/custom when configured), and a SEPARATE, non-consented disclosure line for
@@ -64,7 +47,7 @@ const renderParts = (parts: TermsSentencePart[]): ReactNode =>
64
47
  * because nothing is being agreed to there.
65
48
  */
66
49
  export const LoginTerms: FC<LoginTermsProps> = ({ model, translate, locale, className }) => {
67
- const resolveLabel = resolveLabelFor(translate, locale)
50
+ const resolveLabel = termsLabelResolver(translate, locale)
68
51
 
69
52
  // Centred, like every other row in the card. The checkbox stays at the start of the sentence
70
53
  // rather than above it, so `justify-center` centres the pair and `text-center` centres the
@@ -100,16 +83,7 @@ export const LoginTerms: FC<LoginTermsProps> = ({ model, translate, locale, clas
100
83
  {translate('login.terms.revised', 'Last updated: {{date}}').split('{{date}}').join(model.revisedAt)}
101
84
  </p>}
102
85
 
103
- {/*
104
- Outside the checkbox's `<label>` on purpose: a privacy disclosure is not something the
105
- checkbox consents to, so it is a sibling paragraph rather than nested inside it.
106
- */}
107
- <p data-login-privacy className="text-xs text-muted-foreground">
108
- {renderParts(termsSentence(
109
- translate('login.terms.notice', 'How we handle your personal data: {{notices}}.'),
110
- model, locale, resolveLabel
111
- ))}
112
- </p>
86
+ <LoginPrivacyNotice model={model} translate={translate} locale={locale} />
113
87
 
114
88
  {model.attempted && !model.accepted && <p role="alert" className="text-sm text-destructive">
115
89
  {renderParts(termsSentence(
package/src/exports.ts CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
- export { handler, useNavigate, useValue, useEntrypoint } from '@owlmeans/client'
2
+ export { handler, lazyHandler, lazyComponent, useNavigate, useValue, useEntrypoint } from '@owlmeans/client'
3
+ export type { LazyComponent, LazyComponentOptions, LazyHandler } from '@owlmeans/client'
3
4
  export { config } from '@owlmeans/client-context'
4
5
  export { service } from '@owlmeans/config'
5
6
  export { addWebService } from '@owlmeans/client-config'
@@ -44,4 +44,19 @@ describe('@owlmeans/web-panel/consent — the dialog and the menu widget', () =>
44
44
  await close()
45
45
  }
46
46
  }, TIMEOUT)
47
+
48
+ test('linker passes through to the underlying dialog unchanged', async () => {
49
+ // `?domains=1` hands `PanelCookieConsent` a `linker` — nothing here re-derives it, so seeing
50
+ // the domain line pins that `{...props}` still carries it all the way to `CookieConsent`.
51
+ const { page, close } = await open('/prefs?consent=bare&footer=node&domains=1')
52
+ try {
53
+ await page.waitForSelector('#prefs')
54
+ await page.locator('[data-consent-dialog]').waitFor()
55
+ const domains = page.locator('[data-consent-domains]')
56
+ await domains.waitFor()
57
+ expect(await domains.textContent()).toContain('harness-partner.test')
58
+ } finally {
59
+ await close()
60
+ }
61
+ }, TIMEOUT)
47
62
  })
@@ -0,0 +1,9 @@
1
+ import type { FC } from 'react'
2
+ import { useEntrypoint } from '@owlmeans/client'
3
+
4
+ /** The screen `mount.tsx` loads lazily — a separate module, so it is a separate request. */
5
+ export const LazyScreen: FC = () => {
6
+ const { alias } = useEntrypoint()
7
+
8
+ return <div id="lazy-screen" data-alias={alias}>lazy-screen</div>
9
+ }
@@ -13,7 +13,8 @@ import { handler, useNavigate } from '@owlmeans/client'
13
13
  import { toast } from 'sonner'
14
14
  import type { PanelNavConfig, PanelNavLink } from '../../src/index.js'
15
15
  import {
16
- makeContext, useContext, entrypoints as baseEntrypoints, NavLayout, PanelApp, Toaster
16
+ makeContext, useContext, entrypoints as baseEntrypoints, NavLayout, PanelApp, Toaster,
17
+ lazyHandler, lazyComponent,
17
18
  } from '../../src/index.js'
18
19
  import { LoginScreen } from '../../src/components/login/index.js'
19
20
  import {
@@ -36,6 +37,8 @@ const alias = {
36
37
  prefs: `${SERVICE}:web:prefs`,
37
38
  login: `${SERVICE}:web:login`,
38
39
  socket: `${SERVICE}:web:socket`,
40
+ lazy: `${SERVICE}:web:lazy`,
41
+ lazyEntry: `${SERVICE}:web:lazy-entry`,
39
42
  }
40
43
 
41
44
  /** A harness id for the ONE tracked connection this screen simulates — a real `ws()`/`useWs()`
@@ -115,6 +118,15 @@ const themeToggle = new URLSearchParams(window.location.search).get('themeToggle
115
118
  // confirmation — the shape that pins the "still exactly one checkbox" and "documents render
116
119
  // outside the notice" rules even when there is more than terms+privacy to show.
117
120
  const extendedTerms = new URLSearchParams(window.location.search).get('terms') === 'extended'
121
+ // `?defer=bound` registers a step that CONFIRMS the terms, bound to a real screen — the sign-in
122
+ // screen must then show no checkbox, only the privacy notice, and block nothing.
123
+ // `?defer=unbound` registers the same step but never binds its entrypoint — the checkbox must
124
+ // stay (fail-closed): a step nobody can reach must never silently remove the confirmation.
125
+ const defer = new URLSearchParams(window.location.search).get('defer')
126
+ // `?domains=1` passes `linker` through to `PanelCookieConsent` — proving the prop reaches
127
+ // `@owlmeans/web-consent`'s `CookieConsent` with nothing lost in between (the props already just
128
+ // spread through; this is what pins that they keep doing so).
129
+ const withDomains = new URLSearchParams(window.location.search).get('domains') === '1'
118
130
 
119
131
  /**
120
132
  * The footer's "Cookie settings" control — the menu widget, rendered from an always-mounted
@@ -222,6 +234,37 @@ const ReportsIndex: FC = () => {
222
234
  </div>
223
235
  }
224
236
 
237
+ /**
238
+ * Lazily-loaded screens, declared at MODULE SCOPE as the helpers require — the route renderer
239
+ * remounts a screen on every navigation, so a lazy object made during render would re-suspend
240
+ * each time. Both go through the package root, which is what an application imports.
241
+ *
242
+ * `flakyScreen` fails its first load on purpose: a rejected load must be retried, never cached.
243
+ * The globals let a spec drive `preload()` without a screen of its own.
244
+ */
245
+ let flakyLoads = 0
246
+ const lazyScreen = lazyHandler(
247
+ () => import('./lazy-screen.js'), 'LazyScreen', { fallback: <div id="lazy-fallback">loading</div> }
248
+ )
249
+ const flakyScreen = lazyComponent(async () => {
250
+ if (++flakyLoads === 1) throw new Error('first-load-fails')
251
+ return import('./lazy-screen.js')
252
+ }, 'LazyScreen')
253
+ ;(globalThis as unknown as { __lazyPreload: () => Promise<boolean> }).__lazyPreload =
254
+ async () => typeof await lazyScreen.preload() === 'function'
255
+ ;(globalThis as unknown as { __flakyPreload: () => Promise<string> }).__flakyPreload =
256
+ () => flakyScreen.preload().then(() => 'loaded', (e: Error) => e.message)
257
+
258
+ /** The lazy screen's way in: an in-app navigation, so the layout around it is already mounted. */
259
+ const LazyEntry: FC = () => {
260
+ const nav = useNavigate()
261
+
262
+ return <div id="lazy-entry">
263
+ lazy-entry-screen
264
+ <button id="to-lazy" onClick={nav.press(alias.lazy)}>lazy</button>
265
+ </div>
266
+ }
267
+
225
268
  // The panel context registers the api-config middleware, which resolves the advertise
226
269
  // entrypoint during init — that entrypoint needs a declared backend service route, so both
227
270
  // sides are declared here exactly as a real app declares them.
@@ -280,6 +323,19 @@ ensureLoginService(context as never).registerMethodSource({
280
323
  ],
281
324
  })
282
325
 
326
+ if (defer != null) {
327
+ ensureLoginService(context as never).registerStep({
328
+ alias: 'harness-terms-step',
329
+ // "bound" points at a real screen of this harness (`/prefs`, already registered below) —
330
+ // `termsDeferred` only reads `ctx.hasEntrypoint`, so any bound alias proves the point.
331
+ // "unbound" points nowhere: `hasEntrypoint` answers false and the step stays unreachable.
332
+ entrypoint: defer === 'bound' ? alias.prefs : 'harness-terms-step-nowhere',
333
+ confirmsTerms: true,
334
+ required: true,
335
+ pending: async () => false,
336
+ })
337
+ }
338
+
283
339
  const protocols = {
284
340
  base: openProtocol(route(BASE, '/', frontend())),
285
341
  home: openProtocol(route(HOME, '/', frontend({ default: true, parent: BASE }))),
@@ -290,6 +346,8 @@ const protocols = {
290
346
  prefs: openProtocol(route(alias.prefs, '/prefs', frontend({ parent: BASE }))),
291
347
  login: openProtocol(route(alias.login, '/login', frontend({ parent: BASE }))),
292
348
  socket: openProtocol(route(alias.socket, '/socket', frontend({ parent: BASE }))),
349
+ lazy: openProtocol(route(alias.lazy, '/lazy', frontend({ parent: BASE }))),
350
+ lazyEntry: openProtocol(route(alias.lazyEntry, '/lazy-entry', frontend({ parent: BASE }))),
293
351
  }
294
352
 
295
353
  const entrypoints = [
@@ -308,6 +366,8 @@ const entrypoints = [
308
366
  bindScreen(protocols.prefs, handler(PrefsScreen)),
309
367
  bindScreen(protocols.login, handler(LoginHarness)),
310
368
  bindScreen(protocols.socket, handler(SocketStatusScreen)),
369
+ bindScreen(protocols.lazy, lazyScreen),
370
+ bindScreen(protocols.lazyEntry, handler(LazyEntry)),
311
371
  ]
312
372
 
313
373
  context.registerEntrypoints(entrypoints)
@@ -315,5 +375,8 @@ context.registerEntrypoints(entrypoints)
315
375
  // The consent dialog is a sibling of the Router, as in an application — and only on the consent
316
376
  // branches, since its first-visit overlay covers the page every other test clicks through.
317
377
  createRoot(document.getElementById('root')!).render(<PanelApp context={context as never}>
318
- {consentMode != null && <PanelCookieConsent policyHref="/cookies" />}
378
+ {consentMode != null && <PanelCookieConsent
379
+ policyHref="/cookies"
380
+ {...(withDomains ? { linker: { domains: ['harness-partner.test'] } } : {})}
381
+ />}
319
382
  </PanelApp>)
@@ -0,0 +1,104 @@
1
+ import { afterAll, describe, expect, test } from 'bun:test'
2
+ import { closeBrowser, mountComponent } from '@owlmeans/test-ui'
3
+ import { HARNESS_URL } from './context.js'
4
+
5
+ // Browser work does not fit the 5s default: a cold harness compiles the app on first request.
6
+ const TIMEOUT = 30_000
7
+
8
+ // The lazy screen's alias as the harness declares it (`${SERVICE}:web:lazy`).
9
+ const LAZY_ALIAS = 'web-panel-test:web:lazy'
10
+ // The request the lazy screen's chunk arrives by — the harness's own module, served by Vite.
11
+ const LAZY_MODULE = '**/lazy-screen.tsx*'
12
+
13
+ afterAll(async () => {
14
+ await closeBrowser()
15
+ })
16
+
17
+ const open = async (path: string) => mountComponent({ url: `${HARNESS_URL.replace(/\/$/, '')}${path}` })
18
+
19
+ describe('@owlmeans/web-panel — lazily-loaded screens', () => {
20
+ test('the fallback renders inside the layout, and the layout survives the load', async () => {
21
+ const { page, close } = await open('/lazy-entry')
22
+ try {
23
+ await page.waitForSelector('#lazy-entry')
24
+
25
+ // Hold the chunk's request open, so the fallback is observable for as long as the test needs.
26
+ let release!: () => void
27
+ const held = new Promise<void>(resolve => { release = resolve })
28
+ await page.route(LAZY_MODULE, async route => {
29
+ await held
30
+ await route.continue()
31
+ })
32
+
33
+ await page.locator('#to-lazy').click()
34
+ await page.waitForSelector('#lazy-fallback')
35
+ // The boundary is the lazy screen's own: the shell stays up and the fallback sits in it.
36
+ expect(await page.locator('header nav').first().isVisible()).toBe(true)
37
+ expect(await page.locator('main #lazy-fallback').count()).toBe(1)
38
+
39
+ // Keep the layout's header node itself — a remount would replace it with a fresh one.
40
+ await page.evaluate(() => {
41
+ const header = document.querySelector('header')!
42
+ header.setAttribute('data-lazy-mark', '1')
43
+ ;(window as unknown as { __header: Element }).__header = header
44
+ })
45
+
46
+ release()
47
+ await page.waitForSelector('#lazy-screen')
48
+
49
+ expect(await page.locator('#lazy-fallback').count()).toBe(0)
50
+ expect(await page.locator('header[data-lazy-mark]').count()).toBe(1)
51
+ expect(await page.evaluate(() => {
52
+ const kept = (window as unknown as { __header: Element }).__header
53
+ return kept.isConnected && document.querySelector('header') === kept
54
+ })).toBe(true)
55
+ expect(await page.locator('#lazy-screen').getAttribute('data-alias')).toBe(LAZY_ALIAS)
56
+ } finally {
57
+ await close()
58
+ }
59
+ }, TIMEOUT)
60
+
61
+ test('a preloaded screen renders without its fallback', async () => {
62
+ const { page, close } = await open('/lazy-entry')
63
+ try {
64
+ await page.waitForSelector('#lazy-entry')
65
+ expect(await page.evaluate(() => (window as unknown as { __lazyPreload: () => Promise<boolean> }).__lazyPreload())).toBe(true)
66
+
67
+ // Records every node ever added, so a fallback that is mounted and replaced in the same
68
+ // task still counts — reading the DOM afterwards would miss it.
69
+ await page.evaluate(() => {
70
+ const flags = window as unknown as { __fallbackSeen: boolean }
71
+ flags.__fallbackSeen = false
72
+ new MutationObserver(records => {
73
+ for (const record of records) {
74
+ for (const node of Array.from(record.addedNodes)) {
75
+ if (node instanceof Element && (node.id === 'lazy-fallback' || node.querySelector('#lazy-fallback') != null)) {
76
+ flags.__fallbackSeen = true
77
+ }
78
+ }
79
+ }
80
+ }).observe(document.body, { childList: true, subtree: true })
81
+ })
82
+
83
+ await page.locator('#to-lazy').click()
84
+ await page.waitForSelector('#lazy-screen')
85
+ expect(await page.evaluate(() => (window as unknown as { __fallbackSeen: boolean }).__fallbackSeen)).toBe(false)
86
+ expect(await page.locator('#lazy-screen').getAttribute('data-alias')).toBe(LAZY_ALIAS)
87
+ } finally {
88
+ await close()
89
+ }
90
+ }, TIMEOUT)
91
+
92
+ test('a failed load is retried, not cached', async () => {
93
+ const { page, close } = await open('/lazy-entry')
94
+ try {
95
+ await page.waitForSelector('#lazy-entry')
96
+ const preload = () => page.evaluate(() => (window as unknown as { __flakyPreload: () => Promise<string> }).__flakyPreload())
97
+
98
+ expect(await preload()).toBe('first-load-fails')
99
+ expect(await preload()).toBe('loaded')
100
+ } finally {
101
+ await close()
102
+ }
103
+ }, TIMEOUT)
104
+ })
@@ -168,8 +168,11 @@ describe('@owlmeans/web-panel — the sign-in screen', () => {
168
168
  await page.waitForSelector('[data-login-method]')
169
169
 
170
170
  expect(await page.locator('[data-login-terms]').isChecked()).toBe(true)
171
+ // Omitted rather than `"false"`: `aria-disabled={blocked || undefined}` drops the attribute
172
+ // entirely once unblocked, which is what lets a deferred screen (no `blocked` state at all)
173
+ // render the same way with no special case.
171
174
  expect(await page.locator('[data-login-method="primary"]').getAttribute('aria-disabled'))
172
- .toBe('false')
175
+ .toBeNull()
173
176
  } finally {
174
177
  await close()
175
178
  }
@@ -259,4 +262,49 @@ describe('@owlmeans/web-panel — the sign-in screen', () => {
259
262
  }
260
263
  }, TIMEOUT)
261
264
  })
265
+
266
+ describe('the Terms confirmation deferred to a step', () => {
267
+ test('bound: no checkbox, the privacy notice still renders, and methods are never blocked', async () => {
268
+ const { page, close } = await open('?defer=bound')
269
+ try {
270
+ expect(await page.locator('[data-login-terms]').count()).toBe(0)
271
+ expect(await page.locator('[data-login-privacy]').count()).toBe(1)
272
+ expect(await page.locator('[role="alert"]').count()).toBe(0)
273
+
274
+ const disabled = await page.locator('[data-login-method="primary"]').getAttribute('aria-disabled')
275
+ expect(disabled).not.toBe('true')
276
+
277
+ await page.locator('[data-login-method="primary"]').click()
278
+ expect(await page.evaluate(() => (window as never as { __loginStarted: string[] }).__loginStarted))
279
+ .toEqual(['primary'])
280
+ } finally {
281
+ await close()
282
+ }
283
+ }, TIMEOUT)
284
+
285
+ test('bound + extended documents: still no checkbox, still no blocking', async () => {
286
+ const { page, close } = await open('?defer=bound&terms=extended')
287
+ try {
288
+ expect(await page.locator('[data-login-terms]').count()).toBe(0)
289
+ expect(await page.locator('[data-login-privacy]').count()).toBe(1)
290
+
291
+ await page.locator('[data-login-method="primary"]').click()
292
+ expect(await page.evaluate(() => (window as never as { __loginStarted: string[] }).__loginStarted))
293
+ .toEqual(['primary'])
294
+ } finally {
295
+ await close()
296
+ }
297
+ }, TIMEOUT)
298
+
299
+ test('unbound: the checkbox stays — a step nobody can reach never removes the confirmation', async () => {
300
+ const { page, close } = await open('?defer=unbound')
301
+ try {
302
+ expect(await page.locator('[data-login-terms]').count()).toBe(1)
303
+ expect(await page.locator('[data-login-method="primary"]').getAttribute('aria-disabled'))
304
+ .toBe('true')
305
+ } finally {
306
+ await close()
307
+ }
308
+ }, TIMEOUT)
309
+ })
262
310
  })