@eifi1/ui-kit 0.31.0 → 0.31.1

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 (130) hide show
  1. package/README.md +5 -5
  2. package/dist/account/delete-account-setting.d.ts +1 -1
  3. package/dist/admin/admin-action-confirm.d.ts +1 -1
  4. package/dist/admin/admin-parts.d.ts +8 -1
  5. package/dist/admin/admin-parts.js +1 -1
  6. package/dist/admin/admin-parts.js.map +1 -1
  7. package/dist/admin/invitations-panel.d.ts +1 -1
  8. package/dist/admin/reviewer-scope-editor.d.ts +1 -1
  9. package/dist/admin/role-select.d.ts +1 -1
  10. package/dist/admin/transfer-ownership-dialog.d.ts +1 -1
  11. package/dist/admin/user-roster.d.ts +1 -1
  12. package/dist/auth/register-form.d.ts +1 -1
  13. package/dist/auth/reset-password-form.d.ts +1 -1
  14. package/dist/auth/reset-password-form.js +2 -1
  15. package/dist/auth/reset-password-form.js.map +1 -1
  16. package/dist/auth/verify-email.d.ts +4 -0
  17. package/dist/auth/verify-email.js +3 -2
  18. package/dist/auth/verify-email.js.map +1 -1
  19. package/dist/chart.d.ts +1 -1
  20. package/dist/components/amount-input.d.ts +1 -1
  21. package/dist/components/button-group.d.ts +1 -1
  22. package/dist/components/calculator.d.ts +1 -1
  23. package/dist/components/confirm-dialog.d.ts +1 -1
  24. package/dist/components/confirm-dialog.js +7 -1
  25. package/dist/components/confirm-dialog.js.map +1 -1
  26. package/dist/components/copy-button.d.ts +1 -1
  27. package/dist/components/danger-confirm.d.ts +1 -1
  28. package/dist/components/facing-pair.d.ts +1 -1
  29. package/dist/components/file-button.d.ts +1 -1
  30. package/dist/components/file-dropzone.d.ts +1 -1
  31. package/dist/components/form-actions.d.ts +1 -1
  32. package/dist/components/iban-input.d.ts +1 -1
  33. package/dist/components/language-select.d.ts +1 -1
  34. package/dist/components/legal-page.d.ts +1 -1
  35. package/dist/components/legal.d.ts +1 -1
  36. package/dist/components/money-field.d.ts +1 -1
  37. package/dist/components/number-field.d.ts +1 -1
  38. package/dist/components/number-input.d.ts +1 -1
  39. package/dist/components/numpad-sheet.d.ts +1 -1
  40. package/dist/components/phone-input.d.ts +1 -1
  41. package/dist/components/series-chart.d.ts +1 -1
  42. package/dist/components/settings-fields.d.ts +1 -1
  43. package/dist/components/share-card.d.ts +1 -1
  44. package/dist/components/text-link.d.ts +1 -1
  45. package/dist/components/time-input.d.ts +1 -1
  46. package/dist/components/ui.d.ts +1 -1
  47. package/dist/{delete-account-setting-DCFS5Vof.d.ts → delete-account-setting-CpIhzxxZ.d.ts} +8 -0
  48. package/dist/feedback/feedback-attachment.d.ts +1 -1
  49. package/dist/feedback/feedback-context.d.ts +1 -1
  50. package/dist/feedback/feedback-crash.d.ts +1 -1
  51. package/dist/feedback/feedback-dialog.d.ts +1 -1
  52. package/dist/feedback/feedback-inbox.d.ts +1 -1
  53. package/dist/feedback/feedback-labels.d.ts +1 -1
  54. package/dist/feedback/feedback-menu.d.ts +1 -1
  55. package/dist/feedback/feedback-record.d.ts +1 -1
  56. package/dist/feedback/feedback-row-detail.d.ts +1 -1
  57. package/dist/feedback/feedback-status-undo.d.ts +1 -1
  58. package/dist/feedback/feedback-submit.d.ts +1 -1
  59. package/dist/feedback/feedback-swipe.d.ts +1 -1
  60. package/dist/feedback/feedback-table.d.ts +1 -1
  61. package/dist/feedback/feedback-thread.d.ts +1 -1
  62. package/dist/feedback.d.ts +1 -1
  63. package/dist/hooks/use-file-drop.d.ts +1 -1
  64. package/dist/i18n/defaults.d.ts +1 -1
  65. package/dist/i18n/german.d.ts +1 -1
  66. package/dist/i18n/german.js +2 -1
  67. package/dist/i18n/german.js.map +1 -1
  68. package/dist/i18n/kit-labels.d.ts +1 -1
  69. package/dist/i18n/languages.d.ts +1 -1
  70. package/dist/i18n/locales/de-CH.d.ts +1 -1
  71. package/dist/i18n/locales/en.d.ts +1 -1
  72. package/dist/i18n/locales/es.d.ts +1 -1
  73. package/dist/i18n/locales/es.js +2 -1
  74. package/dist/i18n/locales/es.js.map +1 -1
  75. package/dist/i18n/locales/fr.d.ts +1 -1
  76. package/dist/i18n/locales/fr.js +2 -1
  77. package/dist/i18n/locales/fr.js.map +1 -1
  78. package/dist/i18n/locales/hu.d.ts +1 -1
  79. package/dist/i18n/locales/hu.js +2 -1
  80. package/dist/i18n/locales/hu.js.map +1 -1
  81. package/dist/i18n/locales/it.d.ts +1 -1
  82. package/dist/i18n/locales/it.js +2 -1
  83. package/dist/i18n/locales/it.js.map +1 -1
  84. package/dist/i18n/locales/zh.d.ts +1 -1
  85. package/dist/i18n/locales/zh.js +2 -1
  86. package/dist/i18n/locales/zh.js.map +1 -1
  87. package/dist/i18n/review.d.ts +1 -1
  88. package/dist/i18n/review.js +1 -0
  89. package/dist/i18n/review.js.map +1 -1
  90. package/dist/index.d.ts +3 -3
  91. package/dist/index.js +2 -1
  92. package/dist/index.js.map +1 -1
  93. package/dist/landing/landing-actions.d.ts +5 -3
  94. package/dist/landing/landing-actions.js +1 -1
  95. package/dist/landing/landing-actions.js.map +1 -1
  96. package/dist/landing/landing-sections.d.ts +1 -1
  97. package/dist/landing/public-header.d.ts +1 -1
  98. package/dist/landing/routing.d.ts +20 -7
  99. package/dist/landing/routing.js +5 -2
  100. package/dist/landing/routing.js.map +1 -1
  101. package/dist/rhf/fields.d.ts +1 -1
  102. package/dist/rhf/form.d.ts +1 -1
  103. package/dist/rhf.d.ts +1 -1
  104. package/dist/settings/settings-labels.d.ts +8 -1
  105. package/dist/settings/settings-labels.js +7 -2
  106. package/dist/settings/settings-labels.js.map +1 -1
  107. package/dist/settings/settings-section.d.ts +1 -1
  108. package/dist/shell/app-shell.d.ts +1 -1
  109. package/dist/shell/top-bar-brand.d.ts +1 -1
  110. package/dist/shell.d.ts +2 -2
  111. package/dist/shell.js.map +1 -1
  112. package/dist/wizard/stepper-nav.d.ts +1 -1
  113. package/dist/wizard.d.ts +1 -1
  114. package/package.json +1 -1
  115. package/src/admin/admin-parts.ts +9 -2
  116. package/src/auth/reset-password-form.tsx +6 -2
  117. package/src/auth/verify-email.tsx +14 -2
  118. package/src/components/confirm-dialog.tsx +20 -1
  119. package/src/i18n/german.ts +2 -1
  120. package/src/i18n/locales/es.ts +2 -1
  121. package/src/i18n/locales/fr.ts +2 -1
  122. package/src/i18n/locales/hu.ts +2 -1
  123. package/src/i18n/locales/it.ts +2 -1
  124. package/src/i18n/locales/zh.ts +2 -1
  125. package/src/i18n/review.ts +1 -0
  126. package/src/index.ts +8 -2
  127. package/src/landing/landing-actions.tsx +9 -5
  128. package/src/landing/routing.tsx +22 -5
  129. package/src/settings/settings-labels.ts +13 -1
  130. package/src/shell.ts +7 -1
package/dist/rhf.d.ts CHANGED
@@ -4,7 +4,7 @@ export { RhfCheckbox, RhfCheckboxProps, RhfCombobox, RhfComboboxProps, RhfCountr
4
4
  export { RhfLineItemCellContext, RhfLineItems, RhfLineItemsColumn, RhfLineItemsProps } from './rhf/line-items.js';
5
5
  import 'react';
6
6
  import 'react-hook-form';
7
- import './delete-account-setting-DCFS5Vof.js';
7
+ import './delete-account-setting-CpIhzxxZ.js';
8
8
  import './components/tooltip.js';
9
9
  import './hooks/use-anchored-rect.js';
10
10
  import './lib/clipping.js';
@@ -37,5 +37,12 @@ interface SettingsLabels {
37
37
  groups: Record<SettingsCoreGroup, SettingsGroupLabels>;
38
38
  }
39
39
  declare const DEFAULT_SETTINGS_LABELS: SettingsLabels;
40
+ /**
41
+ * 0.31.1: the `settings` namespace, resolved — English, then the provider, then `labels`.
42
+ * What an app reads for its catalogue's core group titles and help lines, and for the
43
+ * page title "<group> · Settings · <app>" (settings contract §3.1), without restating
44
+ * the namespace and its defaults (Kurvenschmiede's 0.31 adoption).
45
+ */
46
+ declare function useSettingsLabels(labels?: Partial<SettingsLabels>): SettingsLabels;
40
47
 
41
- export { DEFAULT_SETTINGS_LABELS, type SettingsCoreGroup, type SettingsGroupLabels, type SettingsLabels };
48
+ export { DEFAULT_SETTINGS_LABELS, type SettingsCoreGroup, type SettingsGroupLabels, type SettingsLabels, useSettingsLabels };
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { useKitLabels } from "../i18n/kit-labels.js";
2
3
  const DEFAULT_SETTINGS_LABELS = {
3
4
  title: "Settings",
4
5
  search: "Search settings",
@@ -20,7 +21,7 @@ const DEFAULT_SETTINGS_LABELS = {
20
21
  },
21
22
  security: {
22
23
  title: "Security",
23
- help: "How you sign in: password, two-factor codes, passkeys and sessions."
24
+ help: "How you sign in, and where you are signed in."
24
25
  },
25
26
  notifications: {
26
27
  title: "Notifications",
@@ -32,7 +33,11 @@ const DEFAULT_SETTINGS_LABELS = {
32
33
  }
33
34
  }
34
35
  };
36
+ function useSettingsLabels(labels) {
37
+ return useKitLabels("settings", DEFAULT_SETTINGS_LABELS, labels);
38
+ }
35
39
  export {
36
- DEFAULT_SETTINGS_LABELS
40
+ DEFAULT_SETTINGS_LABELS,
41
+ useSettingsLabels
37
42
  };
38
43
  //# sourceMappingURL=settings-labels.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/settings/settings-labels.ts"],"sourcesContent":["/**\n * The `settings` namespace (docs/settings-harmonization.md §4.3): the settings page's\n * own words, and the names and help lines of the five core groups every app shares —\n * appearance, account, security, notifications, data (§2.6). An app's own groups bring\n * their own words; any core label may be overridden like any kit label (prop >\n * `<UiKitProvider labels={{ settings }}>` > English).\n */\n\n/** A core group's two strings, as {@link SettingsGroup} takes them. */\nexport interface SettingsGroupLabels {\n /** The group's name. */\n title: string;\n /** One line: under the heading, and in the phone list. */\n help: string;\n}\n\n/** The five groups whose words the kit ships (§2.6, §4.2). */\nexport type SettingsCoreGroup = \"appearance\" | \"account\" | \"security\" | \"notifications\" | \"data\";\n\nexport interface SettingsLabels {\n /** The page's heading, and the back link's visible text on a phone's group page. */\n title: string;\n /** The search field's accessible name. */\n search: string;\n /** The search field's placeholder. */\n searchPlaceholder: string;\n /** The hit list's accessible name. */\n results: string;\n /** No hits for the trimmed query (§3.4). */\n noMatches: (query: string) => string;\n /** The button beside it that empties the field. */\n clearSearch: string;\n /** The phone's back link's accessible name: \"‹ Settings\" says where, this says what. */\n back: string;\n /** The sidebar's accessible name (the vertical tablist). */\n sections: string;\n /** A sidebar row's hit badge, read in place of the bare number while searching. */\n matchCount: (count: number) => string;\n /** The core groups' names and help lines. */\n groups: Record<SettingsCoreGroup, SettingsGroupLabels>;\n}\n\nexport const DEFAULT_SETTINGS_LABELS: SettingsLabels = {\n title: \"Settings\",\n search: \"Search settings\",\n searchPlaceholder: \"Search settings…\",\n results: \"Matching settings\",\n noMatches: (query) => `No settings match “${query}”.`,\n clearSearch: \"Clear search\",\n back: \"Back to settings\",\n sections: \"Settings sections\",\n matchCount: (count) => (count === 1 ? \"1 match\" : `${count} matches`),\n groups: {\n appearance: {\n title: \"Appearance\",\n help: \"Language, theme and how the app looks on this device.\",\n },\n account: {\n title: \"Account\",\n help: \"Your profile and your email address.\",\n },\n security: {\n title: \"Security\",\n help: \"How you sign in: password, two-factor codes, passkeys and sessions.\",\n },\n notifications: {\n title: \"Notifications\",\n help: \"What you are told about, when, and on which devices.\",\n },\n data: {\n title: \"Data\",\n help: \"Export what is yours, or delete it together with your account.\",\n },\n },\n};\n"],"mappings":";AA0CO,MAAM,0BAA0C;AAAA,EACrD,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,mBAAmB;AAAA,EACnB,SAAS;AAAA,EACT,WAAW,CAAC,UAAU,2BAAsB,KAAK;AAAA,EACjD,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU;AAAA,EACV,YAAY,CAAC,UAAW,UAAU,IAAI,YAAY,GAAG,KAAK;AAAA,EAC1D,QAAQ;AAAA,IACN,YAAY;AAAA,MACV,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,SAAS;AAAA,MACP,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,UAAU;AAAA,MACR,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,eAAe;AAAA,MACb,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/settings/settings-labels.ts"],"sourcesContent":["import { useKitLabels } from \"../i18n/kit-labels\";\n\n/**\n * The `settings` namespace (docs/settings-harmonization.md §4.3): the settings page's\n * own words, and the names and help lines of the five core groups every app shares —\n * appearance, account, security, notifications, data (§2.6). An app's own groups bring\n * their own words; any core label may be overridden like any kit label (prop >\n * `<UiKitProvider labels={{ settings }}>` > English).\n */\n\n/** A core group's two strings, as {@link SettingsGroup} takes them. */\nexport interface SettingsGroupLabels {\n /** The group's name. */\n title: string;\n /** One line: under the heading, and in the phone list. */\n help: string;\n}\n\n/** The five groups whose words the kit ships (§2.6, §4.2). */\nexport type SettingsCoreGroup = \"appearance\" | \"account\" | \"security\" | \"notifications\" | \"data\";\n\nexport interface SettingsLabels {\n /** The page's heading, and the back link's visible text on a phone's group page. */\n title: string;\n /** The search field's accessible name. */\n search: string;\n /** The search field's placeholder. */\n searchPlaceholder: string;\n /** The hit list's accessible name. */\n results: string;\n /** No hits for the trimmed query (§3.4). */\n noMatches: (query: string) => string;\n /** The button beside it that empties the field. */\n clearSearch: string;\n /** The phone's back link's accessible name: \"‹ Settings\" says where, this says what. */\n back: string;\n /** The sidebar's accessible name (the vertical tablist). */\n sections: string;\n /** A sidebar row's hit badge, read in place of the bare number while searching. */\n matchCount: (count: number) => string;\n /** The core groups' names and help lines. */\n groups: Record<SettingsCoreGroup, SettingsGroupLabels>;\n}\n\nexport const DEFAULT_SETTINGS_LABELS: SettingsLabels = {\n title: \"Settings\",\n search: \"Search settings\",\n searchPlaceholder: \"Search settings…\",\n results: \"Matching settings\",\n noMatches: (query) => `No settings match “${query}”.`,\n clearSearch: \"Clear search\",\n back: \"Back to settings\",\n sections: \"Settings sections\",\n matchCount: (count) => (count === 1 ? \"1 match\" : `${count} matches`),\n groups: {\n appearance: {\n title: \"Appearance\",\n help: \"Language, theme and how the app looks on this device.\",\n },\n account: {\n title: \"Account\",\n help: \"Your profile and your email address.\",\n },\n security: {\n title: \"Security\",\n help: \"How you sign in, and where you are signed in.\",\n },\n notifications: {\n title: \"Notifications\",\n help: \"What you are told about, when, and on which devices.\",\n },\n data: {\n title: \"Data\",\n help: \"Export what is yours, or delete it together with your account.\",\n },\n },\n};\n\n/**\n * 0.31.1: the `settings` namespace, resolved — English, then the provider, then `labels`.\n * What an app reads for its catalogue's core group titles and help lines, and for the\n * page title \"<group> · Settings · <app>\" (settings contract §3.1), without restating\n * the namespace and its defaults (Kurvenschmiede's 0.31 adoption).\n */\nexport function useSettingsLabels(labels?: Partial<SettingsLabels>): SettingsLabels {\n return useKitLabels(\"settings\", DEFAULT_SETTINGS_LABELS, labels);\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AA4CtB,MAAM,0BAA0C;AAAA,EACrD,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,mBAAmB;AAAA,EACnB,SAAS;AAAA,EACT,WAAW,CAAC,UAAU,2BAAsB,KAAK;AAAA,EACjD,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU;AAAA,EACV,YAAY,CAAC,UAAW,UAAU,IAAI,YAAY,GAAG,KAAK;AAAA,EAC1D,QAAQ;AAAA,IACN,YAAY;AAAA,MACV,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,SAAS;AAAA,MACP,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,UAAU;AAAA,MACR,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,eAAe;AAAA,MACb,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAQO,SAAS,kBAAkB,QAAkD;AAClF,SAAO,aAAa,YAAY,yBAAyB,MAAM;AACjE;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentPropsWithoutRef, ReactNode } from 'react';
3
- import { bZ as CardTone } from '../delete-account-setting-DCFS5Vof.js';
3
+ import { bZ as CardTone } from '../delete-account-setting-CpIhzxxZ.js';
4
4
  import '../components/tooltip.js';
5
5
  import '../hooks/use-anchored-rect.js';
6
6
  import '../lib/clipping.js';
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentPropsWithoutRef, ReactNode } from 'react';
3
3
  import { LucideIcon } from 'lucide-react';
4
- import { dR as KitLinkComponent } from '../delete-account-setting-DCFS5Vof.js';
4
+ import { dR as KitLinkComponent } from '../delete-account-setting-CpIhzxxZ.js';
5
5
  import '../components/tooltip.js';
6
6
  import '../hooks/use-anchored-rect.js';
7
7
  import '../lib/clipping.js';
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { dS as KitLinkProps, dR as KitLinkComponent } from '../delete-account-setting-DCFS5Vof.js';
3
+ import { dS as KitLinkProps, dR as KitLinkComponent } from '../delete-account-setting-CpIhzxxZ.js';
4
4
  import '../components/tooltip.js';
5
5
  import '../hooks/use-anchored-rect.js';
6
6
  import '../lib/clipping.js';
package/dist/shell.d.ts CHANGED
@@ -13,7 +13,7 @@ export { SettingsSection, SettingsSectionProps } from './settings/settings-secti
13
13
  export { SettingsLayoutContextValue, useSettingsLayout } from './settings/settings-context.js';
14
14
  export { SettingsHeadingLevel, SettingsHeadingLevelProps, SettingsHeadingTag, useSettingsHeadingLevel } from './settings/settings-heading.js';
15
15
  export { SettingsEntry, SettingsGroup, SettingsSearchEntriesOptions, settingsHref, settingsSearchEntries, visibleSettingsEntries, visibleSettingsGroups } from './settings/settings-catalogue.js';
16
- export { DEFAULT_LAST_VISITED_EXCLUDES, LastVisitedPageOptions, NEXT_PARAM, PathPattern, RedirectIfAuthed, RedirectIfAuthedProps, RootEntry, RootEntryProps, clearLastVisitedPage, readLastVisitedPage, safeNextPath, useLastVisitedPage } from './landing/routing.js';
16
+ export { DEFAULT_LAST_VISITED_EXCLUDES, LastVisitedPageOptions, NEXT_PARAM, PathPattern, RedirectIfAuthed, RedirectIfAuthedProps, RootEntry, RootEntryProps, RoutingSession, clearLastVisitedPage, readLastVisitedPage, safeNextPath, useLastVisitedPage } from './landing/routing.js';
17
17
  export { PublicHeader, PublicHeaderBrand, PublicHeaderProps } from './landing/public-header.js';
18
18
  import 'react';
19
19
  import './theme/palette-presets.js';
@@ -22,7 +22,7 @@ import './theme/theme-store.js';
22
22
  import 'zustand/middleware';
23
23
  import 'zustand';
24
24
  import 'lucide-react';
25
- import './delete-account-setting-DCFS5Vof.js';
25
+ import './delete-account-setting-CpIhzxxZ.js';
26
26
  import './components/tooltip.js';
27
27
  import './hooks/use-anchored-rect.js';
28
28
  import './lib/clipping.js';
package/dist/shell.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shell.ts"],"sourcesContent":["// `@eifi1/ui-kit/shell` — the composable app chrome.\n//\n// A re-slicing of the main barrel, not a new API. This entry point requires\n// `react-router`: AppShell renders NavLink and TopBarActionMenu renders Link.\nexport * from \"./shell/topbar-controls\";\nexport * from \"./shell/top-bar\";\nexport * from \"./shell/app-shell\";\nexport * from \"./shell/option-switcher-menu\";\nexport * from \"./shell/role-switcher\";\nexport * from \"./shell/topbar-action-menu\";\nexport * from \"./shell/top-bar-brand\";\nexport * from \"./shell/auth-layout\";\n// 0.31.0: the settings and admin pages' shell (docs/settings-harmonization.md §3, §5).\nexport { SettingsLayout, SETTINGS_SEARCH_THRESHOLD } from \"./settings/settings-layout\";\nexport type { SettingsLayoutProps, SettingsLayoutWidth } from \"./settings/settings-layout\";\nexport {\n useSettingsRoute,\n resolveSettingsLocation,\n settingsFromListState,\n SETTINGS_FROM_LIST_STATE,\n} from \"./settings/use-settings-route\";\nexport type {\n UseSettingsRouteOptions,\n SettingsRoute,\n SettingsLayoutMode,\n SettingsLocation,\n SettingsLocationRules,\n SettingsLocationResult,\n} from \"./settings/use-settings-route\";\nexport { useSettingsFocus, SETTINGS_FOCUS_RING, SETTINGS_FOCUS_MS } from \"./settings/use-settings-focus\";\nexport { SettingsSection } from \"./settings/settings-section\";\nexport type { SettingsSectionProps } from \"./settings/settings-section\";\nexport { useSettingsLayout } from \"./settings/settings-context\";\nexport type { SettingsLayoutContextValue } from \"./settings/settings-context\";\nexport { SettingsHeadingLevel, useSettingsHeadingLevel } from \"./settings/settings-heading\";\nexport type { SettingsHeadingLevelProps, SettingsHeadingTag } from \"./settings/settings-heading\";\nexport {\n settingsSearchEntries,\n visibleSettingsGroups,\n visibleSettingsEntries,\n settingsHref,\n} from \"./settings/settings-catalogue\";\nexport type { SettingsGroup, SettingsEntry, SettingsSearchEntriesOptions } from \"./settings/settings-catalogue\";\n// 0.31.0: the public pages' header and the resume (docs/landing-demo-harmonization.md §3, §4.1).\nexport {\n RootEntry,\n RedirectIfAuthed,\n useLastVisitedPage,\n readLastVisitedPage,\n clearLastVisitedPage,\n safeNextPath,\n NEXT_PARAM,\n DEFAULT_LAST_VISITED_EXCLUDES,\n} from \"./landing/routing\";\nexport type { RootEntryProps, RedirectIfAuthedProps, LastVisitedPageOptions, PathPattern } from \"./landing/routing\";\nexport { PublicHeader } from \"./landing/public-header\";\nexport type { PublicHeaderProps, PublicHeaderBrand } from \"./landing/public-header\";\n"],"mappings":";AAIA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,SAAS,gBAAgB,iCAAiC;AAE1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP,SAAS,kBAAkB,qBAAqB,yBAAyB;AACzE,SAAS,uBAAuB;AAEhC,SAAS,yBAAyB;AAElC,SAAS,sBAAsB,+BAA+B;AAE9D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,oBAAoB;","names":[]}
1
+ {"version":3,"sources":["../src/shell.ts"],"sourcesContent":["// `@eifi1/ui-kit/shell` — the composable app chrome.\n//\n// A re-slicing of the main barrel, not a new API. This entry point requires\n// `react-router`: AppShell renders NavLink and TopBarActionMenu renders Link.\nexport * from \"./shell/topbar-controls\";\nexport * from \"./shell/top-bar\";\nexport * from \"./shell/app-shell\";\nexport * from \"./shell/option-switcher-menu\";\nexport * from \"./shell/role-switcher\";\nexport * from \"./shell/topbar-action-menu\";\nexport * from \"./shell/top-bar-brand\";\nexport * from \"./shell/auth-layout\";\n// 0.31.0: the settings and admin pages' shell (docs/settings-harmonization.md §3, §5).\nexport { SettingsLayout, SETTINGS_SEARCH_THRESHOLD } from \"./settings/settings-layout\";\nexport type { SettingsLayoutProps, SettingsLayoutWidth } from \"./settings/settings-layout\";\nexport {\n useSettingsRoute,\n resolveSettingsLocation,\n settingsFromListState,\n SETTINGS_FROM_LIST_STATE,\n} from \"./settings/use-settings-route\";\nexport type {\n UseSettingsRouteOptions,\n SettingsRoute,\n SettingsLayoutMode,\n SettingsLocation,\n SettingsLocationRules,\n SettingsLocationResult,\n} from \"./settings/use-settings-route\";\nexport { useSettingsFocus, SETTINGS_FOCUS_RING, SETTINGS_FOCUS_MS } from \"./settings/use-settings-focus\";\nexport { SettingsSection } from \"./settings/settings-section\";\nexport type { SettingsSectionProps } from \"./settings/settings-section\";\nexport { useSettingsLayout } from \"./settings/settings-context\";\nexport type { SettingsLayoutContextValue } from \"./settings/settings-context\";\nexport { SettingsHeadingLevel, useSettingsHeadingLevel } from \"./settings/settings-heading\";\nexport type { SettingsHeadingLevelProps, SettingsHeadingTag } from \"./settings/settings-heading\";\nexport {\n settingsSearchEntries,\n visibleSettingsGroups,\n visibleSettingsEntries,\n settingsHref,\n} from \"./settings/settings-catalogue\";\nexport type { SettingsGroup, SettingsEntry, SettingsSearchEntriesOptions } from \"./settings/settings-catalogue\";\n// 0.31.0: the public pages' header and the resume (docs/landing-demo-harmonization.md §3, §4.1).\nexport {\n RootEntry,\n RedirectIfAuthed,\n useLastVisitedPage,\n readLastVisitedPage,\n clearLastVisitedPage,\n safeNextPath,\n NEXT_PARAM,\n DEFAULT_LAST_VISITED_EXCLUDES,\n} from \"./landing/routing\";\nexport type {\n RootEntryProps,\n RedirectIfAuthedProps,\n RoutingSession,\n LastVisitedPageOptions,\n PathPattern,\n} from \"./landing/routing\";\nexport { PublicHeader } from \"./landing/public-header\";\nexport type { PublicHeaderProps, PublicHeaderBrand } from \"./landing/public-header\";\n"],"mappings":";AAIA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,SAAS,gBAAgB,iCAAiC;AAE1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP,SAAS,kBAAkB,qBAAqB,yBAAyB;AACzE,SAAS,uBAAuB;AAEhC,SAAS,yBAAyB;AAElC,SAAS,sBAAsB,+BAA+B;AAE9D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAQP,SAAS,oBAAoB;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ReactElement } from 'react';
3
- import { bH as ButtonVariant } from '../delete-account-setting-DCFS5Vof.js';
3
+ import { bH as ButtonVariant } from '../delete-account-setting-CpIhzxxZ.js';
4
4
  import { UseWizardReturn, WizardLabels } from './types.js';
5
5
  import '../components/tooltip.js';
6
6
  import '../hooks/use-anchored-rect.js';
package/dist/wizard.d.ts CHANGED
@@ -6,7 +6,7 @@ export { WizardStep, WizardStepProps } from './wizard/wizard-step.js';
6
6
  export { WizardSummary } from './wizard/wizard-summary.js';
7
7
  export { StepperNav } from './wizard/stepper-nav.js';
8
8
  import 'react';
9
- import './delete-account-setting-DCFS5Vof.js';
9
+ import './delete-account-setting-CpIhzxxZ.js';
10
10
  import './components/tooltip.js';
11
11
  import './hooks/use-anchored-rect.js';
12
12
  import './lib/clipping.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eifi1/ui-kit",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "type": "module",
5
5
  "description": "App-agnostic shared design system: domain-free React components, theme and tokens.",
6
6
  "_sideEffects": "The barrel (src/index.ts) re-exports EVERY component, including components/chart, which statically imports recharts. Without this field a bundler must assume each of those modules has side effects, so it cannot drop the unused ones: importing a Button from '@eifi1/ui-kit' dragged all 433KB of recharts into the consumer's ENTRY chunk, and Keksdose was preloading it on every page load, login included. Nothing in src/ imports for side effects (no bare imports, no CSS imports from JS, no module-level global writes) — the token stylesheet is its own './tokens.css' export — so this is safe. CSS is still listed so any future stylesheet survives. Re-checked when `flag-icons` left `dependencies` and tsup started emitting a `\"use client\"` banner: neither touches this. The flag stylesheet was never imported from JS in the first place (that was the finding), and a directive prologue is a string literal every bundler lifts out before it decides whether a module is inert.",
@@ -16,11 +16,18 @@ import { formatPersonName } from "../lib/person-name";
16
16
  export type MaybePromise<T = unknown> = void | Promise<T>;
17
17
 
18
18
  /** A person as the admin parts show one: the two name parts of the auth contract
19
- * (§3.2), an older whole `name` as the fallback, and the address. */
19
+ * (§3.2), an older whole `name` as the fallback, and the address. The name parts are
20
+ * read in either spelling — the kit's `first` / `last`, or server-kit's `PersonRef`
21
+ * as it serialises (`first_name` / `last_name`), so an app hands the server's object
22
+ * over as it came (keksdose's and kastlan's 0.30 adoptions mapped one to the other). */
20
23
  export interface AdminPerson {
21
24
  id?: string | number | null;
22
25
  first?: string | null;
23
26
  last?: string | null;
27
+ /** server-kit `PersonRef.first_name`; `first` wins when both are given. */
28
+ first_name?: string | null;
29
+ /** server-kit `PersonRef.last_name`; `last` wins when both are given. */
30
+ last_name?: string | null;
24
31
  /** A whole name, for an API that still sends `display_name` only. */
25
32
  name?: string | null;
26
33
  email?: string | null;
@@ -39,7 +46,7 @@ export function hasMessage(node: ReactNode): boolean {
39
46
  export function personLabel(person: AdminPerson | null | undefined, locale: string | undefined): string {
40
47
  if (!person) return "";
41
48
  return (
42
- formatPersonName({ first: person.first, last: person.last }, locale) ||
49
+ formatPersonName({ first: person.first ?? person.first_name, last: person.last ?? person.last_name }, locale) ||
43
50
  (person.name ?? "").trim() ||
44
51
  (person.email ?? "").trim()
45
52
  );
@@ -144,7 +144,7 @@ export interface ResetPasswordFormProps
144
144
  forgotHref?: string;
145
145
  /** The app's words for a failed save (the device offline), or `undefined` for the
146
146
  * kit's: `rateLimited` for a bare `429` (0.30.0), else `failed`. Not asked for
147
- * `token_invalid`, which is a state. */
147
+ * `token_invalid` or `token_expired`, which are a state. */
148
148
  describeError?: (error: unknown) => ReactNode | undefined;
149
149
  /** The heading. Default `labels.title`; `null` draws none, for a page that puts it
150
150
  * in `AuthLayout`'s `title` instead. */
@@ -299,7 +299,11 @@ export function ResetPasswordForm({
299
299
  },
300
300
  (error: unknown) => {
301
301
  setPending(false);
302
- if (authErrorCode(error) === "token_invalid") {
302
+ // A link that died between the check and the save — used, or run out — is the
303
+ // dead-link state, under either code (0.31.1: keksdose mapped `token_expired`
304
+ // to it itself, `asDeadLink`).
305
+ const code = authErrorCode(error);
306
+ if (code === "token_invalid" || code === "token_expired") {
303
307
  setPassword("");
304
308
  setConfirm("");
305
309
  setPhase({ token, phase: { kind: "invalid" } });
@@ -8,7 +8,7 @@ import { AlertBanner } from "../components/alert-banner";
8
8
  import { LoadingState } from "../components/loading-state";
9
9
  import { Button, EmptyState } from "../components/ui";
10
10
  import { OUTCOME_CLASS, STATUS_HEADING_CLASS, isThenable, useOncePerKey } from "./status-parts";
11
- import { authErrorCode } from "./auth-errors";
11
+ import { authErrorCode, englishRateLimited, isRateLimited, retryAfterSeconds } from "./auth-errors";
12
12
  import type { AuthHeadingLevel } from "./status-parts";
13
13
 
14
14
  /* ── Labels ──────────────────────────────────────────────────────────────── */
@@ -41,6 +41,10 @@ export interface VerifyEmailLabels {
41
41
  resent: string;
42
42
  /** Said (and shown) after a resend failed. */
43
43
  resendError: string;
44
+ /** 0.31.1: a resend refused as throttled — HTTP 429 (`isRateLimited`) — given the
45
+ * `Retry-After` seconds, when the app's `describeError` has no words of its own
46
+ * (keksdose borrowed the sign-in namespace's). */
47
+ rateLimited?: (seconds?: number) => string;
44
48
  /** The banner's message. */
45
49
  banner: string;
46
50
  /** The banner's ×: it defers, it does not close anything. */
@@ -62,6 +66,7 @@ export const DEFAULT_VERIFY_EMAIL_LABELS: VerifyEmailLabels = {
62
66
  resendIn: (seconds) => `Send again in ${seconds} s`,
63
67
  resent: "Confirmation email sent",
64
68
  resendError: "Could not send the confirmation email",
69
+ rateLimited: englishRateLimited,
65
70
  banner: "Please confirm your email address.",
66
71
  dismiss: "Not now",
67
72
  };
@@ -160,7 +165,14 @@ function ResendStatus({
160
165
  labels: VerifyEmailLabels;
161
166
  className?: string;
162
167
  }) {
163
- const failed = phase === "failed" ? (describeError?.(error) ?? labels.resendError) : "";
168
+ // The app's words first, then the throttle's, then the kit's catch-all.
169
+ const failed =
170
+ phase === "failed"
171
+ ? (describeError?.(error) ??
172
+ (isRateLimited(error)
173
+ ? (labels.rateLimited ?? englishRateLimited)(retryAfterSeconds(error))
174
+ : labels.resendError))
175
+ : "";
164
176
  return (
165
177
  <span
166
178
  role="status"
@@ -6,6 +6,7 @@ import { TypedConfirmField, typedMatches } from "./danger-confirm";
6
6
  import type { TypedMatch } from "./danger-confirm";
7
7
  import { DialogFrame } from "./dialog-frame";
8
8
  import { Button } from "./ui";
9
+ import { useWriteLock } from "./write-lock";
9
10
 
10
11
  /**
11
12
  * The `confirmDialog` namespace of `<UiKitProvider labels>`: the two buttons' fallback
@@ -76,6 +77,14 @@ export interface ConfirmOptions {
76
77
  * keeps spaces too — for a phrase like "DELETE" whose contract is "exactly this".
77
78
  */
78
79
  typedMatch?: TypedMatch;
80
+ /**
81
+ * 0.31.1: the confirm button COMMITS, as `Button`'s `commit` does. Under a locked
82
+ * `WriteLockProvider` — read where `useConfirm` was called, not where the one dialog
83
+ * host sits — it is `aria-disabled` with the lock's reason, so a read-only page (a
84
+ * demo, a viewer's budget) can't confirm a delete through a dialog. One prop for the
85
+ * many confirm sites an app has (kastlan's 0.31 adoption counted ~23).
86
+ */
87
+ commit?: boolean;
79
88
  }
80
89
 
81
90
  export type ConfirmFn = (options: ConfirmOptions) => Promise<boolean>;
@@ -97,12 +106,21 @@ export function useConfirm(): ConfirmFn {
97
106
  if (!confirm) {
98
107
  throw new Error("useConfirm() must be called below a <ConfirmProvider>.");
99
108
  }
100
- return confirm;
109
+ // The lock of the CALLER's place: the dialog host usually sits above the app's
110
+ // `WriteLockProvider`, so it could not read the lock itself.
111
+ const lock = useWriteLock();
112
+ return useCallback(
113
+ (options: ConfirmOptions) =>
114
+ confirm(options.commit && lock.locked ? ({ ...options, lockReason: lock.reason } as ConfirmOptions) : options),
115
+ [confirm, lock.locked, lock.reason],
116
+ );
101
117
  }
102
118
 
103
119
  interface Request extends ConfirmOptions {
104
120
  id: number;
105
121
  resolve: (answer: boolean) => void;
122
+ /** Set by {@link useConfirm} for a `commit` confirm under a locked provider. */
123
+ lockReason?: ReactNode;
106
124
  }
107
125
 
108
126
  export interface ConfirmProviderProps {
@@ -279,6 +297,7 @@ function ConfirmDialogView({
279
297
  type="button"
280
298
  variant={tone === "danger" ? "danger" : "primary"}
281
299
  disabled={!matches}
300
+ disabledReason={request.lockReason}
282
301
  onClick={() => {
283
302
  accepted.current = true;
284
303
  close();
@@ -994,6 +994,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
994
994
  resendIn: (seconds) => `In ${n(seconds)} s erneut senden`,
995
995
  resent: "Bestätigungs-E-Mail gesendet",
996
996
  resendError: "Bestätigungs-E-Mail konnte nicht gesendet werden",
997
+ rateLimited,
997
998
  banner: "Bitte bestätigen Sie Ihre E-Mail-Adresse.",
998
999
  dismiss: "Später",
999
1000
  },
@@ -1398,7 +1399,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1398
1399
  },
1399
1400
  security: {
1400
1401
  title: "Sicherheit",
1401
- help: "Wie Sie sich anmelden: Passwort, Zwei-Faktor-Codes, Passkeys und Sitzungen.",
1402
+ help: "Wie Sie sich anmelden und wo Sie angemeldet sind.",
1402
1403
  },
1403
1404
  notifications: {
1404
1405
  title: "Benachrichtigungen",
@@ -975,6 +975,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
975
975
  resendIn: (seconds) => `Enviar de nuevo en ${n(seconds)} s`,
976
976
  resent: "Correo de confirmación enviado",
977
977
  resendError: "No se pudo enviar el correo de confirmación",
978
+ rateLimited,
978
979
  banner: "Confirme su dirección de correo electrónico.",
979
980
  dismiss: "Ahora no",
980
981
  },
@@ -1363,7 +1364,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1363
1364
  },
1364
1365
  security: {
1365
1366
  title: "Seguridad",
1366
- help: "Cómo inicia sesión: contraseña, verificación en dos pasos, llaves de acceso y sesiones.",
1367
+ help: "Cómo inicia sesión, y en qué dispositivos tiene la sesión abierta.",
1367
1368
  },
1368
1369
  notifications: {
1369
1370
  title: "Notificaciones",
@@ -997,6 +997,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
997
997
  resendIn: (seconds) => `Envoyer à nouveau dans ${n(seconds)}\u00a0s`,
998
998
  resent: "E-mail de confirmation envoyé",
999
999
  resendError: "Impossible d’envoyer l’e-mail de confirmation",
1000
+ rateLimited,
1000
1001
  banner: "Veuillez confirmer votre adresse e-mail.",
1001
1002
  dismiss: "Pas maintenant",
1002
1003
  },
@@ -1390,7 +1391,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1390
1391
  },
1391
1392
  security: {
1392
1393
  title: "Sécurité",
1393
- help: "Comment vous vous connectez\u00a0: mot de passe, codes 2FA, clés d’accès et sessions.",
1394
+ help: "Votre connexion, et les appareils où elle est ouverte.",
1394
1395
  },
1395
1396
  notifications: {
1396
1397
  title: "Notifications",
@@ -986,6 +986,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
986
986
  resendIn: (seconds) => `Újraküldés ${n(seconds)} mp múlva`,
987
987
  resent: "Megerősítő e-mail elküldve",
988
988
  resendError: "Nem sikerült elküldeni a megerősítő e-mailt",
989
+ rateLimited,
989
990
  banner: "Kérjük, erősítse meg az e-mail-címét.",
990
991
  dismiss: "Most nem",
991
992
  },
@@ -1365,7 +1366,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1365
1366
  },
1366
1367
  security: {
1367
1368
  title: "Biztonság",
1368
- help: "Hogyan jelentkezik be: jelszó, ellenőrző kódok, hozzáférési kulcsok és munkamenetek.",
1369
+ help: "Hogyan jelentkezik be, és hol van bejelentkezve.",
1369
1370
  },
1370
1371
  notifications: {
1371
1372
  title: "Értesítések",
@@ -982,6 +982,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
982
982
  resendIn: (seconds) => `Invia di nuovo tra ${n(seconds)} s`,
983
983
  resent: "Email di conferma inviata",
984
984
  resendError: "Impossibile inviare l’email di conferma",
985
+ rateLimited,
985
986
  banner: "Confermi il Suo indirizzo email.",
986
987
  dismiss: "Non ora",
987
988
  },
@@ -1374,7 +1375,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1374
1375
  },
1375
1376
  security: {
1376
1377
  title: "Sicurezza",
1377
- help: "Come accede: password, codici 2FA, passkey e sessioni.",
1378
+ help: "Come accede, e su quali dispositivi la sessione è aperta.",
1378
1379
  },
1379
1380
  notifications: {
1380
1381
  title: "Notifiche",
@@ -949,6 +949,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
949
949
  resendIn: (seconds) => `${n(seconds)} 秒后可重新发送`,
950
950
  resent: "确认邮件已发送",
951
951
  resendError: "无法发送确认邮件",
952
+ rateLimited,
952
953
  banner: "请确认您的电子邮箱地址。",
953
954
  dismiss: "暂不",
954
955
  },
@@ -1308,7 +1309,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1308
1309
  },
1309
1310
  security: {
1310
1311
  title: "安全",
1311
- help: "您的登录方式:密码、双重验证码、通行密钥和会话。",
1312
+ help: "您的登录方式,以及已在哪些设备上登录。",
1312
1313
  },
1313
1314
  notifications: {
1314
1315
  title: "通知",
@@ -309,6 +309,7 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
309
309
  "register.rateLimited": [[], [30], [300]],
310
310
  "forgotPassword.rateLimited": [[], [30], [300]],
311
311
  "resetPassword.rateLimited": [[], [30], [300]],
312
+ "verifyEmail.rateLimited": [[], [30], [300]],
312
313
  "emailChange.emailTagUse": [["{{address}}"]],
313
314
  "emailChange.pending": [["{{newEmail}}"]],
314
315
  "emailChange.pendingHint": [["{{currentEmail}}"]],
package/src/index.ts CHANGED
@@ -667,7 +667,7 @@ export {
667
667
  settingsHref,
668
668
  } from "./settings/settings-catalogue";
669
669
  export type { SettingsGroup, SettingsEntry, SettingsSearchEntriesOptions } from "./settings/settings-catalogue";
670
- export { DEFAULT_SETTINGS_LABELS } from "./settings/settings-labels";
670
+ export { DEFAULT_SETTINGS_LABELS, useSettingsLabels } from "./settings/settings-labels";
671
671
  export type { SettingsLabels, SettingsGroupLabels, SettingsCoreGroup } from "./settings/settings-labels";
672
672
  // Only the language follows the account (§6.2).
673
673
  export { useAccountLanguage, resolveAccountLanguage, matchOfferedLanguage } from "./settings/use-account-language";
@@ -709,7 +709,13 @@ export {
709
709
  NEXT_PARAM,
710
710
  DEFAULT_LAST_VISITED_EXCLUDES,
711
711
  } from "./landing/routing";
712
- export type { RootEntryProps, RedirectIfAuthedProps, LastVisitedPageOptions, PathPattern } from "./landing/routing";
712
+ export type {
713
+ RootEntryProps,
714
+ RedirectIfAuthedProps,
715
+ RoutingSession,
716
+ LastVisitedPageOptions,
717
+ PathPattern,
718
+ } from "./landing/routing";
713
719
  export { PublicHeader } from "./landing/public-header";
714
720
  export type { PublicHeaderProps, PublicHeaderBrand } from "./landing/public-header";
715
721
  export { DEFAULT_DEMO_LABELS, useDemoLabels } from "./demo/demo-labels";
@@ -23,8 +23,10 @@ export interface LandingActionsProps extends Omit<ComponentPropsWithoutRef<"div"
23
23
  session: LandingSession;
24
24
  access: AccessChoice;
25
25
  /** "Try the demo" without a session, "Continue the demo" with a demo one. Default
26
- * `/demo`, which starts a demo — or, for a live one, continues it (§3.1). */
27
- demoHref?: string;
26
+ * `/demo`, which starts a demo — or, for a live one, continues it (§3.1). `null`
27
+ * (0.31.1): the app has no demo yet, and the action is left out — "Request access"
28
+ * stands alone (Kurvenschmiede's 0.31 adoption). */
29
+ demoHref?: string | null;
28
30
  /** "Open app" for a real session: the resume target. Default `/`, which resumes
29
31
  * through `RootEntry`. */
30
32
  openAppHref?: string;
@@ -82,9 +84,11 @@ export function LandingActions({
82
84
  <Button href={accessLink.href} variant="brand" size={buttonSize} className={extra}>
83
85
  {accessLink.label}
84
86
  </Button>
85
- <Button href={demoHref} variant="secondary" size={buttonSize} className={extra}>
86
- {session === "demo" ? labels.continueDemo : labels.tryDemo}
87
- </Button>
87
+ {demoHref !== null && (
88
+ <Button href={demoHref} variant="secondary" size={buttonSize} className={extra}>
89
+ {session === "demo" ? labels.continueDemo : labels.tryDemo}
90
+ </Button>
91
+ )}
88
92
  </>
89
93
  )}
90
94
  </div>
@@ -4,6 +4,15 @@ import { Navigate, Outlet, useLocation } from "react-router";
4
4
 
5
5
  import { readStored, writeStored } from "../lib/safe-storage";
6
6
  import type { LandingSession } from "./landing-actions";
7
+ import { LoadingState } from "../components/loading-state";
8
+
9
+ /**
10
+ * 0.31.1: a session that is still being restored — an app whose access token lives in
11
+ * memory reads it back from its refresh token first (kastlan's 0.31 adoption). Until
12
+ * then `/` neither shows the landing nor resumes, and a public page neither shows nor
13
+ * redirects: both render `loading` instead.
14
+ */
15
+ export type RoutingSession = LandingSession | "loading";
7
16
 
8
17
  /**
9
18
  * The public routes' logic (docs/landing-demo-harmonization.md §3): what `/` shows,
@@ -144,8 +153,11 @@ export function clearLastVisitedPage(key: string): void {
144
153
  /* ── RootEntry ───────────────────────────────────────────────────────────── */
145
154
 
146
155
  export interface RootEntryProps {
147
- /** Without a session `/` is the landing; with one (real or demo) it resumes. */
148
- session: LandingSession;
156
+ /** Without a session `/` is the landing; with one (real or demo) it resumes;
157
+ * `"loading"` while the app restores one. */
158
+ session: RoutingSession;
159
+ /** What shows while `session` is `"loading"`. Default: the kit's `LoadingState`. */
160
+ loading?: ReactNode;
149
161
  /** The remembered page — `readLastVisitedPage(key)`. */
150
162
  resumePath?: string | null;
151
163
  /** The landing page element. */
@@ -164,7 +176,8 @@ export interface RootEntryProps {
164
176
  * home instead, because a session at `/` came to use the app. The landing stays one tap
165
177
  * away at `/welcome`, where the signed-in brand link points.
166
178
  */
167
- export function RootEntry({ session, resumePath, landing, home }: RootEntryProps) {
179
+ export function RootEntry({ session, resumePath, landing, home, loading }: RootEntryProps) {
180
+ if (session === "loading") return <>{loading ?? <LoadingState />}</>;
168
181
  if (session === "none") return <>{landing}</>;
169
182
  const resume = safeNextPath(resumePath);
170
183
  return <Navigate to={resume && resume !== "/" ? resume : home} replace />;
@@ -173,7 +186,10 @@ export function RootEntry({ session, resumePath, landing, home }: RootEntryProps
173
186
  /* ── RedirectIfAuthed ────────────────────────────────────────────────────── */
174
187
 
175
188
  export interface RedirectIfAuthedProps {
176
- session: LandingSession;
189
+ /** `"loading"` while the app restores a session: neither the page nor a redirect. */
190
+ session: RoutingSession;
191
+ /** What shows while `session` is `"loading"`. Default: the kit's `LoadingState`. */
192
+ loading?: ReactNode;
177
193
  /**
178
194
  * Let a DEMO session through (§5.6): on `/login`, `/register` and the app's invitation
179
195
  * pages, so a demo visitor can sign in or accept an invitation — a successful one
@@ -199,8 +215,9 @@ export interface RedirectIfAuthedProps {
199
215
  * `allowDemo` around `/login`, `/register` and the invitation pages, and one without
200
216
  * around `/demo` (and `/forgot-password`).
201
217
  */
202
- export function RedirectIfAuthed({ session, allowDemo = false, fallback, children }: RedirectIfAuthedProps) {
218
+ export function RedirectIfAuthed({ session, allowDemo = false, fallback, children, loading }: RedirectIfAuthedProps) {
203
219
  const { search } = useLocation();
220
+ if (session === "loading") return <>{loading ?? <LoadingState />}</>;
204
221
  if (session === "none" || (session === "demo" && allowDemo)) {
205
222
  return children === undefined ? <Outlet /> : <>{children}</>;
206
223
  }
@@ -1,3 +1,5 @@
1
+ import { useKitLabels } from "../i18n/kit-labels";
2
+
1
3
  /**
2
4
  * The `settings` namespace (docs/settings-harmonization.md §4.3): the settings page's
3
5
  * own words, and the names and help lines of the five core groups every app shares —
@@ -61,7 +63,7 @@ export const DEFAULT_SETTINGS_LABELS: SettingsLabels = {
61
63
  },
62
64
  security: {
63
65
  title: "Security",
64
- help: "How you sign in: password, two-factor codes, passkeys and sessions.",
66
+ help: "How you sign in, and where you are signed in.",
65
67
  },
66
68
  notifications: {
67
69
  title: "Notifications",
@@ -73,3 +75,13 @@ export const DEFAULT_SETTINGS_LABELS: SettingsLabels = {
73
75
  },
74
76
  },
75
77
  };
78
+
79
+ /**
80
+ * 0.31.1: the `settings` namespace, resolved — English, then the provider, then `labels`.
81
+ * What an app reads for its catalogue's core group titles and help lines, and for the
82
+ * page title "<group> · Settings · <app>" (settings contract §3.1), without restating
83
+ * the namespace and its defaults (Kurvenschmiede's 0.31 adoption).
84
+ */
85
+ export function useSettingsLabels(labels?: Partial<SettingsLabels>): SettingsLabels {
86
+ return useKitLabels("settings", DEFAULT_SETTINGS_LABELS, labels);
87
+ }
package/src/shell.ts CHANGED
@@ -52,6 +52,12 @@ export {
52
52
  NEXT_PARAM,
53
53
  DEFAULT_LAST_VISITED_EXCLUDES,
54
54
  } from "./landing/routing";
55
- export type { RootEntryProps, RedirectIfAuthedProps, LastVisitedPageOptions, PathPattern } from "./landing/routing";
55
+ export type {
56
+ RootEntryProps,
57
+ RedirectIfAuthedProps,
58
+ RoutingSession,
59
+ LastVisitedPageOptions,
60
+ PathPattern,
61
+ } from "./landing/routing";
56
62
  export { PublicHeader } from "./landing/public-header";
57
63
  export type { PublicHeaderProps, PublicHeaderBrand } from "./landing/public-header";