@cosmicdrift/kumiko-renderer 0.385.0 → 0.387.0

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 (185) hide show
  1. package/LICENSE +21 -57
  2. package/dist/app/action-form-body.d.ts +2 -2
  3. package/dist/app/action-form-body.js +2 -2
  4. package/dist/app/action-form-shim.d.ts +8 -8
  5. package/dist/app/action-form-shim.js +20 -22
  6. package/dist/app/app-features-context.js +4 -4
  7. package/dist/app/column-renderers.d.ts +7 -9
  8. package/dist/app/column-renderers.js +15 -18
  9. package/dist/app/config-edit-body.js +1 -1
  10. package/dist/app/config-edit-shim.d.ts +7 -8
  11. package/dist/app/config-edit-shim.js +18 -20
  12. package/dist/app/custom-screens.d.ts +3 -3
  13. package/dist/app/custom-screens.js +8 -8
  14. package/dist/app/dashboard-body.d.ts +2 -2
  15. package/dist/app/dashboard-body.js +2 -2
  16. package/dist/app/entity-edit-prefill.js +9 -9
  17. package/dist/app/entity-edit-screen.js +4 -4
  18. package/dist/app/entity-list-screen.js +3 -3
  19. package/dist/app/extension-form-submit.d.ts +1 -1
  20. package/dist/app/extension-form-submit.js +14 -15
  21. package/dist/app/extension-sections.d.ts +24 -25
  22. package/dist/app/extension-sections.js +10 -10
  23. package/dist/app/feature-schema.js +11 -11
  24. package/dist/app/form-schema.js +1 -1
  25. package/dist/app/header-actions-bar.js +1 -1
  26. package/dist/app/kumiko-screen.js +8 -10
  27. package/dist/app/layout-fields.js +1 -1
  28. package/dist/app/list-facets.js +3 -3
  29. package/dist/app/nav.d.ts +10 -10
  30. package/dist/app/nav.js +13 -13
  31. package/dist/app/projection-detail-body.js +4 -4
  32. package/dist/app/projection-detail-shim.d.ts +6 -5
  33. package/dist/app/projection-detail-shim.js +23 -22
  34. package/dist/app/projection-list-body.js +10 -10
  35. package/dist/app/projection-list-shim.d.ts +5 -5
  36. package/dist/app/projection-list-shim.js +17 -18
  37. package/dist/app/qn.js +10 -12
  38. package/dist/app/qualify-screen-id.js +1 -1
  39. package/dist/app/screen-access.js +2 -2
  40. package/dist/app/screen-shared.js +1 -1
  41. package/dist/app/secret-mint-body.js +4 -4
  42. package/dist/app/write-failed-error.js +8 -7
  43. package/dist/components/embedded-list-field.js +4 -4
  44. package/dist/components/grid-cell-for-field.d.ts +1 -1
  45. package/dist/components/reference-create-dialog.js +3 -3
  46. package/dist/components/related-list-section.d.ts +1 -1
  47. package/dist/components/related-list-section.js +28 -5
  48. package/dist/components/render-edit-logic.js +1 -1
  49. package/dist/components/render-edit-types.d.ts +6 -6
  50. package/dist/components/render-edit.js +63 -65
  51. package/dist/components/render-field.d.ts +7 -7
  52. package/dist/components/render-field.js +57 -59
  53. package/dist/components/render-list.d.ts +43 -44
  54. package/dist/components/render-list.js +63 -67
  55. package/dist/components/use-wizard-form-draft.js +10 -10
  56. package/dist/context/dispatcher-context.js +5 -5
  57. package/dist/context/user-roles-context.js +2 -2
  58. package/dist/headless/apex/index.d.ts +1 -2
  59. package/dist/headless/apex/index.js +5 -6
  60. package/dist/headless/form/form-controller.js +6 -6
  61. package/dist/headless/form/zod-bridge.js +2 -2
  62. package/dist/headless/format/money.js +1 -1
  63. package/dist/headless/index.js +2 -2
  64. package/dist/headless/view-model/edit.js +2 -2
  65. package/dist/headless/view-model/list.js +3 -4
  66. package/dist/headless/view-model/types.d.ts +3 -3
  67. package/dist/hooks/reference-limits.js +11 -12
  68. package/dist/hooks/use-list-url-state.d.ts +13 -13
  69. package/dist/hooks/use-list-url-state.js +21 -22
  70. package/dist/hooks/use-query.js +3 -3
  71. package/dist/hooks/use-reference-lookup.js +16 -17
  72. package/dist/hooks/use-store.js +18 -22
  73. package/dist/hooks/use-stream-handler.js +4 -2
  74. package/dist/i18n-defaults.js +2 -8
  75. package/dist/i18n.d.ts +10 -10
  76. package/dist/i18n.js +25 -25
  77. package/dist/index.js +5 -4
  78. package/dist/live/dispatcher-live.js +2 -2
  79. package/dist/primitives.d.ts +225 -221
  80. package/dist/primitives.js +1 -1
  81. package/dist/sse/live-events.d.ts +5 -5
  82. package/dist/sse/live-events.js +17 -17
  83. package/dist/tokens.d.ts +8 -8
  84. package/dist/tokens.js +16 -17
  85. package/dist/web/app/browser-locale.d.ts +9 -9
  86. package/dist/web/app/browser-locale.js +19 -19
  87. package/dist/web/app/client-plugin.d.ts +44 -52
  88. package/dist/web/app/client-plugin.js +4 -5
  89. package/dist/web/app/create-app.d.ts +28 -31
  90. package/dist/web/app/create-app.js +21 -22
  91. package/dist/web/app/document-lang-sync.js +1 -1
  92. package/dist/web/app/draft-storage.js +1 -1
  93. package/dist/web/app/nav-providers-context.d.ts +3 -3
  94. package/dist/web/app/nav-providers-context.js +3 -3
  95. package/dist/web/app/nav.d.ts +18 -17
  96. package/dist/web/app/nav.js +66 -64
  97. package/dist/web/app/rich-content-editor.js +1 -1
  98. package/dist/web/components/config-cascade.js +17 -18
  99. package/dist/web/components/config-source-badge.js +4 -4
  100. package/dist/web/layout/app-layout.d.ts +9 -9
  101. package/dist/web/layout/app-layout.js +2 -2
  102. package/dist/web/layout/avatar.d.ts +4 -3
  103. package/dist/web/layout/avatar.js +15 -17
  104. package/dist/web/layout/default-app-shell.d.ts +22 -23
  105. package/dist/web/layout/editor-panel.js +3 -3
  106. package/dist/web/layout/nav-tree.js +86 -84
  107. package/dist/web/layout/profile-menu.d.ts +1 -1
  108. package/dist/web/layout/sidebar-brand.d.ts +7 -7
  109. package/dist/web/layout/sidebar-user.d.ts +1 -1
  110. package/dist/web/layout/sidebar-user.js +4 -4
  111. package/dist/web/layout/sidebar.d.ts +8 -8
  112. package/dist/web/layout/sidebar.js +7 -7
  113. package/dist/web/layout/target-resolver-stub.d.ts +5 -5
  114. package/dist/web/layout/target-resolver-stub.js +20 -20
  115. package/dist/web/layout/target-url.d.ts +7 -7
  116. package/dist/web/layout/target-url.js +22 -22
  117. package/dist/web/layout/theme-toggle.js +4 -5
  118. package/dist/web/layout/workspace-shell.d.ts +21 -21
  119. package/dist/web/layout/workspace-shell.js +29 -30
  120. package/dist/web/lib/cn.d.ts +4 -4
  121. package/dist/web/lib/cn.js +4 -4
  122. package/dist/web/primitives/action-menu.d.ts +8 -8
  123. package/dist/web/primitives/action-menu.js +14 -13
  124. package/dist/web/primitives/calendar-popover.d.ts +1 -1
  125. package/dist/web/primitives/calendar-popover.js +10 -10
  126. package/dist/web/primitives/card-styles.js +1 -1
  127. package/dist/web/primitives/card.js +2 -2
  128. package/dist/web/primitives/combobox.d.ts +12 -12
  129. package/dist/web/primitives/combobox.js +34 -35
  130. package/dist/web/primitives/data-table-cell.js +2 -2
  131. package/dist/web/primitives/data-table.js +2 -3
  132. package/dist/web/primitives/date-field.js +2 -2
  133. package/dist/web/primitives/date-input.js +4 -4
  134. package/dist/web/primitives/default-form.js +2 -2
  135. package/dist/web/primitives/dropdown-menu.js +14 -15
  136. package/dist/web/primitives/embedded-list-input.js +3 -3
  137. package/dist/web/primitives/facet-filter.js +1 -1
  138. package/dist/web/primitives/field.js +9 -8
  139. package/dist/web/primitives/form.d.ts +1 -1
  140. package/dist/web/primitives/form.js +6 -6
  141. package/dist/web/primitives/input.js +2 -2
  142. package/dist/web/primitives/layout-elements.js +1 -1
  143. package/dist/web/primitives/layout.js +2 -2
  144. package/dist/web/primitives/located-timestamp-input.js +4 -4
  145. package/dist/web/primitives/money-input.js +13 -17
  146. package/dist/web/primitives/pager.js +1 -1
  147. package/dist/web/primitives/row-actions.js +3 -4
  148. package/dist/web/primitives/secret-reveal.js +1 -1
  149. package/dist/web/primitives/timestamp-input.d.ts +9 -9
  150. package/dist/web/primitives/timestamp-input.js +21 -21
  151. package/dist/web/primitives/toast.js +12 -13
  152. package/dist/web/sse/live-events.d.ts +6 -6
  153. package/dist/web/sse/live-events.js +3 -3
  154. package/dist/web/tokens.d.ts +11 -12
  155. package/dist/web/tokens.js +35 -37
  156. package/dist/web/version/update-checker.js +14 -13
  157. package/dist/web/widgets/ai-text-field.js +1 -1
  158. package/dist/web/widgets/charts.d.ts +10 -10
  159. package/dist/web/widgets/charts.js +8 -8
  160. package/dist/web/widgets/collapsible-section.d.ts +3 -3
  161. package/dist/web/widgets/collapsible-section.js +6 -6
  162. package/dist/web/widgets/detail-list.d.ts +1 -3
  163. package/dist/web/widgets/detail-list.js +1 -3
  164. package/dist/web/widgets/feed-list.d.ts +2 -2
  165. package/dist/web/widgets/feed-list.js +2 -2
  166. package/dist/web/widgets/form-fields.d.ts +14 -14
  167. package/dist/web/widgets/form-fields.js +14 -13
  168. package/dist/web/widgets/index.js +4 -4
  169. package/dist/web/widgets/infinity-list.js +3 -3
  170. package/dist/web/widgets/mode-switch.d.ts +2 -2
  171. package/dist/web/widgets/mode-switch.js +2 -2
  172. package/dist/web/widgets/progress-list.d.ts +1 -2
  173. package/dist/web/widgets/progress-list.js +1 -2
  174. package/dist/web/widgets/query-table.d.ts +6 -6
  175. package/dist/web/widgets/result-panel.d.ts +16 -16
  176. package/dist/web/widgets/result-panel.js +10 -10
  177. package/dist/web/widgets/section-card.d.ts +3 -3
  178. package/dist/web/widgets/section-card.js +3 -3
  179. package/dist/web/widgets/stat.d.ts +4 -4
  180. package/dist/web/widgets/stat.js +6 -6
  181. package/dist/web/widgets/states.d.ts +4 -4
  182. package/dist/web/widgets/states.js +4 -4
  183. package/dist/web/widgets/use-draft.d.ts +7 -7
  184. package/dist/web/widgets/use-draft.js +7 -7
  185. package/package.json +6 -6
@@ -3,11 +3,11 @@ type DispatchListener = (target: TargetRef) => void;
3
3
  export declare function dispatchTarget(target: TargetRef): void;
4
4
  /** Test-Hook: Exklusiver Spy. Returnt cleanup. */
5
5
  export declare function setDispatchListener(fn: DispatchListener): () => void;
6
- /** Production-Subscriber registrieren. Returnt unsubscribe. Läuft nur
7
- * wenn kein Test-Listener aktiv (Test-Isolation). */
6
+ /** Registers a production subscriber and returns an unsubscribe function.
7
+ * It runs only when no test listener is active (test isolation). */
8
8
  export declare function subscribeTargetDispatches(fn: DispatchListener): () => void;
9
- /** V.1.4b: empfohlener Production-Dispatch. Schreibt target in
10
- * nav.searchParams (URL-State, F5-fähig) UND ruft dispatchTarget für
11
- * Test-Listener + legacy-Subscribers. */
9
+ /** Recommended production dispatch. Writes the target to
10
+ * nav.searchParams (URL state, F5-compatible) AND calls dispatchTarget for
11
+ * test listeners and legacy subscribers. */
12
12
  export declare function useDispatchTarget(): (target: TargetRef) => void;
13
13
  export {};
@@ -1,20 +1,20 @@
1
- // TargetResolver — V.1.2: Multi-Listener-TargetDispatch mit Test-Hook.
2
- // V.1.4b: URL-State-Bridge via useDispatchTarget-Hook. Production schreibt
3
- // target in nav.searchParams (F5-recovery); Subscribe-Stream bleibt für
4
- // Test-Hooks (setDispatchListener) und Apps die kein NavProvider haben.
1
+ // TargetResolver: multi-listener target dispatch with a test hook, plus a
2
+ // URL-state bridge via the useDispatchTarget hook. Production writes
3
+ // the target to nav.searchParams (F5 recovery); the subscription stream remains
4
+ // available for test hooks (setDispatchListener) and apps without a NavProvider.
5
5
  //
6
- // Dispatch-Priority (V.1.2):
7
- // 1. Test-Listener (setDispatchListener) — exklusiv, kein Production-
8
- // Subscriber läuft während Tests (Test-Isolation).
9
- // 2. Production-Subscriber (subscribeTargetDispatches) — alle
10
- // registrierten Production-Subscriber.
11
- // 3. Kein Test-Listener + keine Subscriber → console.debug fallback
12
- // (damit unhandled Klicks sichtbar bleiben).
6
+ // Dispatch priority:
7
+ // 1. Test listener (setDispatchListener) — exclusive; no production
8
+ // subscriber runs during tests (test isolation).
9
+ // 2. Production subscribers (subscribeTargetDispatches) — all registered
10
+ // production subscribers.
11
+ // 3. No test listener and no subscribers → console.debug fallback so unhandled
12
+ // clicks remain visible.
13
13
  //
14
- // **useDispatchTarget (V.1.4b)** ist der empfohlene Production-Pfad.
15
- // NavTree ruft den Hook im Click-Handler eines target-Knotens — er schreibt
16
- // die URL via nav.setSearchParams UND ruft den globalen dispatchTarget für
17
- // Test-Listener-Kompatibilität.
14
+ // **useDispatchTarget** is the recommended production path.
15
+ // NavTree calls the hook from a target node's click handler. It writes the URL
16
+ // via nav.setSearchParams AND calls the global dispatchTarget for compatibility
17
+ // with test listeners.
18
18
  import { useCallback } from "react";
19
19
  import { rendererDebug } from "../../dev-console.js";
20
20
  import { requestLeave, useNav } from "../../index.js";
@@ -41,17 +41,17 @@ export function setDispatchListener(fn) {
41
41
  testListener = undefined;
42
42
  };
43
43
  }
44
- /** Production-Subscriber registrieren. Returnt unsubscribe. Läuft nur
45
- * wenn kein Test-Listener aktiv (Test-Isolation). */
44
+ /** Registers a production subscriber and returns an unsubscribe function.
45
+ * It runs only when no test listener is active (test isolation). */
46
46
  export function subscribeTargetDispatches(fn) {
47
47
  subscribers.add(fn);
48
48
  return () => {
49
49
  subscribers.delete(fn);
50
50
  };
51
51
  }
52
- /** V.1.4b: empfohlener Production-Dispatch. Schreibt target in
53
- * nav.searchParams (URL-State, F5-fähig) UND ruft dispatchTarget für
54
- * Test-Listener + legacy-Subscribers. */
52
+ /** Recommended production dispatch. Writes the target to
53
+ * nav.searchParams (URL state, F5-compatible) AND calls dispatchTarget for
54
+ * test listeners and legacy subscribers. */
55
55
  export function useDispatchTarget() {
56
56
  const nav = useNav();
57
57
  return useCallback((target) => {
@@ -1,12 +1,12 @@
1
1
  import type { TargetRef } from "@cosmicdrift/kumiko-framework/engine";
2
- /** Build a search-params update for `setSearchParams`. Clears all
3
- * existing `a_*` keys plus `t` (so wechsel target nicht alte args
4
- * liegen lässt). Returns null-value entries to clear, plus the new
2
+ /** Builds a search-params update for `setSearchParams`. Clears all
3
+ * existing `a_*` keys plus `t` so switching targets cannot leave stale
4
+ * arguments behind. Returns null-valued entries to clear, plus the new
5
5
  * target entries. */
6
6
  export declare function serializeTarget(target: TargetRef, currentParams: Readonly<Record<string, string>>): Readonly<Record<string, string | null>>;
7
- /** Inverse — parse the current search-params back into a TargetRef.
8
- * Returns undefined wenn der `t`-Key fehlt (kein active target). */
7
+ /** Parses the current search parameters back into a TargetRef.
8
+ * Returns undefined when the `t` key is missing, indicating no active target. */
9
9
  export declare function parseTargetFromSearchParams(params: Readonly<Record<string, string>>): TargetRef | undefined;
10
- /** Returns null-valued updates that clear target + all args. Used vom
11
- * Close-Button + onUnmount. */
10
+ /** Returns null-valued updates that clear the target and all arguments. Used by
11
+ * the close button and on unmount. */
12
12
  export declare function clearTargetSearchParams(currentParams: Readonly<Record<string, string>>): Readonly<Record<string, string | null>>;
@@ -1,30 +1,30 @@
1
1
  // @runtime client
2
- // URL-Bridge für Visual-Tree-Targets. Serialisiert TargetRef in
3
- // Search-Params + parsed zurück.
2
+ // URL bridge for visual-tree targets. Serializes TargetRef into
3
+ // search params and parses it back.
4
4
  //
5
- // **URL-Shape**:
5
+ // **URL shape**:
6
6
  // ?t=<featureId>:<action>&a_<argKey1>=<value1>&a_<argKey2>=<value2>...
7
7
  //
8
- // Beispiel: text-content edit "imprint/de"
8
+ // Example: text-content edit "imprint/de"
9
9
  // ?t=text-content:edit&a_slug=imprint&a_lang=de
10
10
  //
11
- // **Warum prefix `a_`**: vermeidet Naming-Konflikt mit anderen Query-
12
- // Params (Pagination, Sort, Filter). Plus klare Trennung target-meta
13
- // (`t`) vs. action-args (`a_*`).
11
+ // **Why the `a_` prefix**: avoids naming conflicts with other query
12
+ // params (pagination, sorting, filtering) and clearly separates target
13
+ // metadata (`t`) from action arguments (`a_*`).
14
14
  //
15
- // **Warum nicht JSON-encoded**: URL bleibt lesbar + bookmark-fähig.
16
- // JSON-base64 wäre robust für arbitrary Shapes aber unleserlich. arg-
17
- // values sind heute nur primitive strings (text-content: slug, lang) —
18
- // V.1.5 kann auf JSON wechseln wenn nested-args echten Bedarf zeigen.
15
+ // **Why not JSON-encoded**: keeps the URL readable and bookmarkable.
16
+ // JSON-base64 would be robust for arbitrary shapes but unreadable. Argument
17
+ // values are currently only primitive strings (text-content: slug, lang) —
18
+ // nested arguments could switch to JSON if they create a real need.
19
19
  const TARGET_PARAM = "t";
20
20
  const ARG_PREFIX = "a_";
21
- /** Build a search-params update for `setSearchParams`. Clears all
22
- * existing `a_*` keys plus `t` (so wechsel target nicht alte args
23
- * liegen lässt). Returns null-value entries to clear, plus the new
21
+ /** Builds a search-params update for `setSearchParams`. Clears all
22
+ * existing `a_*` keys plus `t` so switching targets cannot leave stale
23
+ * arguments behind. Returns null-valued entries to clear, plus the new
24
24
  * target entries. */
25
25
  export function serializeTarget(target, currentParams) {
26
26
  const updates = {};
27
- // Clear all previous arg-keys (vermeidet stale args bei target-switch).
27
+ // Clear previous argument keys so switching targets cannot leave stale arguments.
28
28
  for (const key of Object.keys(currentParams)) {
29
29
  if (key.startsWith(ARG_PREFIX))
30
30
  updates[key] = null;
@@ -32,9 +32,9 @@ export function serializeTarget(target, currentParams) {
32
32
  updates[TARGET_PARAM] = `${target.featureId}:${target.action}`;
33
33
  if (target.args !== undefined) {
34
34
  for (const [k, v] of Object.entries(target.args)) {
35
- // V.1.4b: nur string-args. Numbers/booleans werden via String()
36
- // koerziert; nested objects/arrays werden NICHT supported (würde
37
- // JSON-encoded URL brauchen, siehe Header-Comment).
35
+ // Only string arguments are supported. Numbers and booleans are coerced via String();
36
+ // nested objects and arrays are unsupported because they would require a
37
+ // JSON-encoded URL.
38
38
  if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
39
39
  updates[`${ARG_PREFIX}${k}`] = String(v);
40
40
  }
@@ -42,8 +42,8 @@ export function serializeTarget(target, currentParams) {
42
42
  }
43
43
  return updates;
44
44
  }
45
- /** Inverse — parse the current search-params back into a TargetRef.
46
- * Returns undefined wenn der `t`-Key fehlt (kein active target). */
45
+ /** Parses the current search parameters back into a TargetRef.
46
+ * Returns undefined when the `t` key is missing, indicating no active target. */
47
47
  export function parseTargetFromSearchParams(params) {
48
48
  const t = params[TARGET_PARAM];
49
49
  if (t === undefined || t === "")
@@ -65,8 +65,8 @@ export function parseTargetFromSearchParams(params) {
65
65
  }
66
66
  return hasArgs ? { featureId, action, args } : { featureId, action };
67
67
  }
68
- /** Returns null-valued updates that clear target + all args. Used vom
69
- * Close-Button + onUnmount. */
68
+ /** Returns null-valued updates that clear the target and all arguments. Used by
69
+ * the close button and on unmount. */
70
70
  export function clearTargetSearchParams(currentParams) {
71
71
  const updates = { [TARGET_PARAM]: null };
72
72
  for (const key of Object.keys(currentParams)) {
@@ -1,9 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // ThemeToggle — Button der die Theme-Wahl weiterschaltet (hell → dunkel → auto).
3
- // Icon-Slots als Props, damit renderer/web keine Icon-Lib als Hard-
4
- // Dependency zieht. Default: Unicode-Glyphs (☀ / ☾) — funktionieren in
5
- // jedem Browser, jede App kann lucide/heroicons/eigene SVG via Props
6
- // reinreichen.
2
+ // ThemeToggle — Button that cycles through the theme choices (light → dark → auto).
3
+ // Icon slots are props so renderer/web does not pull in an icon library as a hard
4
+ // dependency. Defaults: Unicode glyphs (☀ / ☾) — they work in every browser, and
5
+ // each app can pass lucide/heroicons/custom SVGs through the props.
7
6
  import { useContext } from "react";
8
7
  import { HeaderOverflowMenuContext, headerOverflowMenuItemClass } from "./header-overflow-menu.js";
9
8
  import { useThemeToggleLabel } from "./theme-label.js";
@@ -7,11 +7,11 @@ export type WorkspaceShellUser = {
7
7
  export type WorkspaceShellProps = {
8
8
  /** Branding / logo on the left side of the topbar. */
9
9
  readonly brand: ReactNode;
10
- /** Schema mit `workspaces` populated — die engine baut das aus
11
- * registry.getAllWorkspaces() + getWorkspaceNavs(). Akzeptiert
12
- * AppSchema (multi-feature) oder die legacy FeatureSchema (single-
13
- * feature, workspaces inline). Ohne Workspaces fällt der Shell auf
14
- * plain NavTree ohne Switcher zurück. */
10
+ /** Schema with `workspaces` populated — the engine builds this from
11
+ * registry.getAllWorkspaces() + getWorkspaceNavs(). Accepts
12
+ * AppSchema (multi-feature) or the legacy FeatureSchema (single-
13
+ * feature, workspaces inline). Without workspaces, the shell falls
14
+ * back to a plain NavTree without a switcher. */
15
15
  readonly schema: AppSchema | FeatureSchema;
16
16
  /** Optional topbar end-slot — TenantSwitcher / ThemeToggle / UserMenu. */
17
17
  readonly topbarActions?: ReactNode;
@@ -21,9 +21,9 @@ export type WorkspaceShellProps = {
21
21
  * workspace from schema, otherwise first accessible. Useful for SSR
22
22
  * and tests to pre-seed without hitting the URL. */
23
23
  readonly initialWorkspaceId?: string;
24
- /** Footer-Slot unten in der Sidebar — Profile-Row, Help-Link, Build-
25
- * Info. Klebt am unteren Rand via `mt-auto` (siehe Sidebar.footer).
26
- * Symmetrisch zu DefaultAppShell.sidebarFooter. */
24
+ /** Footer slot at the bottom of the sidebar — profile row, help link,
25
+ * build information. It stays at the bottom via `mt-auto` (see
26
+ * Sidebar.footer). Symmetric with DefaultAppShell.sidebarFooter. */
27
27
  readonly sidebarFooter?: ReactNode;
28
28
  /** Runtime badge slot per nav leaf (bare nav id), same as DefaultAppShell. */
29
29
  readonly navBadges?: ReadonlyMap<string, ReactNode>;
@@ -37,27 +37,27 @@ export type WorkspaceShellProps = {
37
37
  export declare function WorkspaceShell({ brand, schema, topbarActions, user, initialWorkspaceId, sidebarFooter, navBadges, fill, children, }: WorkspaceShellProps): ReactNode;
38
38
  export declare function filterByAccess(workspaces: readonly WorkspaceSchema[], userRoles: readonly string[] | undefined): readonly WorkspaceSchema[];
39
39
  /** Returns the short screen-id of the first nav-member that points at
40
- * a screen. Walks the workspace's nav-list in order and skips section-
41
- * headers (NavDefinitions ohne `screen`) so ein workspace dessen erstes
42
- * Member ein Header ist trotzdem auf einen renderbaren Screen auflöst.
40
+ * a screen. Walks the workspace's nav-list in order and skips section
41
+ * headers (NavDefinitions without `screen`) so a workspace whose first
42
+ * member is a header still resolves to a renderable screen.
43
43
  *
44
44
  * Returns "" when:
45
45
  * - navMembers is undefined or empty
46
- * - all members are section-headers (no `screen` property)
47
- * - all members are unknown to the schema (drift zwischen
48
- * r.workspace.nav und den registrierten navs — boot-validator fängt
49
- * das server-side, hier nur graceful fallback)
46
+ * - all members are section headers (no `screen` property)
47
+ * - all members are unknown to the schema (drift between
48
+ * r.workspace.nav and the registered navs — the boot validator catches
49
+ * this server-side; this is only a graceful fallback)
50
50
  *
51
- * The caller MUST loop-guard on "": rendering with screenId="" macht
52
- * KumikoScreen den not-found-banner zeigen — und ein nav.replace mit
53
- * leerem screenId würde den useLayoutEffect in WorkspaceShell bei
54
- * jedem Re-Render erneut feuern (siehe matching guard dort).
51
+ * The caller MUST loop-guard on "": rendering with screenId="" makes
52
+ * KumikoScreen show the not-found banner — and nav.replace with an empty
53
+ * screenId would fire the useLayoutEffect in WorkspaceShell on every
54
+ * re-render (see the matching guard there).
55
55
  *
56
- * Earlier code used `lastSegment(navQn)` which only worked when the
56
+ * Earlier code used `lastSegment(navQn)`, which only worked when the
57
57
  * feature followed the convention nav.id === screen.id (samples/apps/
58
58
  * workspaces does this). Apps with distinct ids (publicstatus: nav
59
59
  * "components" → screen "component-list") got "Screen not found".
60
60
  *
61
- * Exported für Edge-Case-Tests. */
61
+ * Exported for edge-case tests. */
62
62
  export declare function firstNavScreenId(app: AppSchema, navMembers: readonly string[] | undefined): string;
63
63
  export declare function resolveDefaultId(visible: readonly WorkspaceSchema[], preferredShortId: string | undefined): string | undefined;
@@ -63,7 +63,7 @@ export function WorkspaceShell({ brand, schema, topbarActions, user, initialWork
63
63
  const handleSelect = useCallback((id) => {
64
64
  // Pick the first nav-member that actually points at a screen so
65
65
  // the URL lands on something renderable instead of `/<workspace>`.
66
- // Section-headers (nav-Einträge ohne `screen`) werden übersprungen.
66
+ // Section headers (nav entries without `screen`) are skipped.
67
67
  const target = visible.find((ws) => ws.definition.id === id);
68
68
  const screenId = firstNavScreenId(app, target?.navMembers);
69
69
  nav.navigate({ workspaceId: id, screenId });
@@ -104,12 +104,11 @@ export function WorkspaceShell({ brand, schema, topbarActions, user, initialWork
104
104
  return;
105
105
  }
106
106
  const screenId = firstNavScreenId(app, navMembers);
107
- // Loop-guard: wenn keiner der nav-members eine screen-property hat
108
- // (alle section-headers oder unbekannte QNs), würde nav.replace mit
109
- // screenId="" den Effect bei jedem Re-Render erneut feuern und den
110
- // User auf "/<workspace>/" pinnen. Lieber gar nicht replacen — der
111
- // Workspace-Selector kann den User dann manuell auf einen leaf-nav
112
- // schicken.
107
+ // Loop guard: if none of the nav-members has a screen property
108
+ // (all are section headers or unknown QNs), nav.replace with
109
+ // screenId="" would fire the effect again on every re-render and pin
110
+ // the user to "/<workspace>/". Do not replace in that case — the
111
+ // workspace selector can then send the user to a leaf nav manually.
113
112
  if (screenId === "")
114
113
  return;
115
114
  nav.replace({ workspaceId: activeId, screenId });
@@ -132,13 +131,13 @@ export function WorkspaceShell({ brand, schema, topbarActions, user, initialWork
132
131
  return new Set(activeWorkspace.navMembers);
133
132
  }, [app.workspaces, activeWorkspace]);
134
133
  const switcher = activeId !== undefined && (_jsx(WorkspaceSwitcher, { workspaces: visible, activeId: activeId, onSelect: handleSelect }));
135
- // EINE Nav: immer NavTree. Dynamische Knoten (r.nav({ provider: true }))
136
- // ziehen ihre Children client-seitig aus den registrierten navProviders —
137
- // der frühere navigation:"tree"/VisualTree-Zweig ist hier eingefaltet.
138
- // Content-Bereich: ein aktives target (aus den nav.searchParams, gesetzt
139
- // beim Klick auf einen target-Knoten) rendert den EditorPanel statt des
140
- // RoutedScreen — sonst die normalen screen-children.
141
- // (WorkspaceDefinition.navigation ist seitdem ein No-op; deferred removal.)
134
+ // One Nav: always NavTree. Dynamic nodes (r.nav({ provider: true }))
135
+ // load their children client-side from the registered navProviders —
136
+ // the former navigation:"tree"/VisualTree branch is folded in here.
137
+ // In the content area, an active target (from nav.searchParams, set
138
+ // when a target node is clicked) renders EditorPanel instead of
139
+ // RoutedScreen; otherwise it renders the regular screen children.
140
+ // (WorkspaceDefinition.navigation has been a no-op since then; removal deferred.)
142
141
  const resolvers = useResolvers();
143
142
  const activeTarget = parseTargetFromSearchParams(nav.searchParams);
144
143
  const sidebarContent = (_jsx(NavTree, { schema: app, ...(user !== undefined && { user }), ...(allowedNavQns !== undefined && { allowedNavQns }), ...(navBadges !== undefined && { navBadges }) }));
@@ -148,8 +147,8 @@ export function WorkspaceShell({ brand, schema, topbarActions, user, initialWork
148
147
  // above the content. topbarActions render right inside the header row
149
148
  // instead of a separate topbar — one header line, not two.
150
149
  const fillCls = fillClasses(fill);
151
- // Zweite Spalte nur wenn ein Screen sie fuellt (sidebar-09-Muster) — sonst
152
- // stuende auf jedem anderen Screen ein leerer Streifen neben der Navigation.
150
+ // The second column appears only when a screen fills it (sidebar-09 pattern);
151
+ // otherwise every other screen would have an empty strip beside the navigation.
153
152
  const panel = useSidebarPanelHost();
154
153
  return (_jsx(SidebarProvider, { ...fillCls.provider, children: _jsxs(SidebarPanelProvider, { value: panel.value, children: [_jsxs(Sidebar, { collapsible: "icon", mobileTitle: t("kumiko.sidebar.title"), mobileDescription: t("kumiko.sidebar.description"), children: [_jsxs(SidebarHeader, { "data-kumiko-layout": "sidebar-header", children: [brand, switcher] }), _jsx(SidebarContent, { "data-kumiko-layout": "sidebar-nav", children: sidebarContent }), sidebarFooter !== undefined && (_jsx(SidebarFooter, { "data-kumiko-layout": "sidebar-footer", className: "border-t border-sidebar-border", children: sidebarFooter })), _jsx(SidebarRail, { "aria-label": t("kumiko.sidebar.toggle"), title: t("kumiko.sidebar.toggle") })] }), _jsx("div", { ref: panel.ref, "data-kumiko-layout": "sidebar-panel", className: panel.occupied ? "flex h-svh shrink-0 flex-col" : "hidden" }), _jsx(SidebarInset, { className: fillCls.inset, children: _jsxs(PageHeaderSlotProvider, { children: [_jsx(ShellHeader, { schema: app, ...(user !== undefined && { user }), ...(topbarActions !== undefined && { headerActions: topbarActions }) }), _jsx("main", { className: fillCls.main, children: activeTarget !== undefined ? (_jsx(EditorPanel, { resolvers: resolvers })) : (_jsx(UserRolesProvider, { roles: user?.roles, children: children })) })] }) })] }) }));
155
154
  }
@@ -166,28 +165,28 @@ function byOrderThenInsertion(a, b) {
166
165
  return ao - bo;
167
166
  }
168
167
  /** Returns the short screen-id of the first nav-member that points at
169
- * a screen. Walks the workspace's nav-list in order and skips section-
170
- * headers (NavDefinitions ohne `screen`) so ein workspace dessen erstes
171
- * Member ein Header ist trotzdem auf einen renderbaren Screen auflöst.
168
+ * a screen. Walks the workspace's nav-list in order and skips section
169
+ * headers (NavDefinitions without `screen`) so a workspace whose first
170
+ * member is a header still resolves to a renderable screen.
172
171
  *
173
172
  * Returns "" when:
174
173
  * - navMembers is undefined or empty
175
- * - all members are section-headers (no `screen` property)
176
- * - all members are unknown to the schema (drift zwischen
177
- * r.workspace.nav und den registrierten navs — boot-validator fängt
178
- * das server-side, hier nur graceful fallback)
174
+ * - all members are section headers (no `screen` property)
175
+ * - all members are unknown to the schema (drift between
176
+ * r.workspace.nav and the registered navs — the boot validator catches
177
+ * this server-side; this is only a graceful fallback)
179
178
  *
180
- * The caller MUST loop-guard on "": rendering with screenId="" macht
181
- * KumikoScreen den not-found-banner zeigen — und ein nav.replace mit
182
- * leerem screenId würde den useLayoutEffect in WorkspaceShell bei
183
- * jedem Re-Render erneut feuern (siehe matching guard dort).
179
+ * The caller MUST loop-guard on "": rendering with screenId="" makes
180
+ * KumikoScreen show the not-found banner — and nav.replace with an empty
181
+ * screenId would fire the useLayoutEffect in WorkspaceShell on every
182
+ * re-render (see the matching guard there).
184
183
  *
185
- * Earlier code used `lastSegment(navQn)` which only worked when the
184
+ * Earlier code used `lastSegment(navQn)`, which only worked when the
186
185
  * feature followed the convention nav.id === screen.id (samples/apps/
187
186
  * workspaces does this). Apps with distinct ids (publicstatus: nav
188
187
  * "components" → screen "component-list") got "Screen not found".
189
188
  *
190
- * Exported für Edge-Case-Tests. */
189
+ * Exported for edge-case tests. */
191
190
  export function firstNavScreenId(app, navMembers) {
192
191
  if (navMembers === undefined || navMembers.length === 0)
193
192
  return "";
@@ -197,7 +196,7 @@ export function firstNavScreenId(app, navMembers) {
197
196
  if (qualifyNavId(feature.featureName, nav.id) !== navQn)
198
197
  continue;
199
198
  if (nav.screen === undefined)
200
- break; // section-header → nächstes member
199
+ break; // section-header → next member
201
200
  return lastSegment(nav.screen);
202
201
  }
203
202
  }
@@ -1,6 +1,6 @@
1
1
  import { type ClassValue } from "clsx";
2
- /** shadcn-Standard-Helper: clsx für konditionales Zusammenstecken,
3
- * tailwind-merge für Konfliktauflösung (z.B. `px-2 px-4` → `px-4`).
4
- * Jedes Primitive nutzt das um Consumer-Klassen mit Default-Klassen
5
- * zu mergen ohne Duplikate. */
2
+ /** Standard shadcn helper: `clsx` combines conditional classes,
3
+ * while `tailwind-merge` resolves conflicts (for example,
4
+ * `px-2 px-4` → `px-4`). Primitives use this to merge consumer
5
+ * classes with default classes without duplicates. */
6
6
  export declare function cn(...inputs: ClassValue[]): string;
@@ -1,9 +1,9 @@
1
1
  import { clsx } from "clsx";
2
2
  import { twMerge } from "tailwind-merge";
3
- /** shadcn-Standard-Helper: clsx für konditionales Zusammenstecken,
4
- * tailwind-merge für Konfliktauflösung (z.B. `px-2 px-4` → `px-4`).
5
- * Jedes Primitive nutzt das um Consumer-Klassen mit Default-Klassen
6
- * zu mergen ohne Duplikate. */
3
+ /** Standard shadcn helper: `clsx` combines conditional classes,
4
+ * while `tailwind-merge` resolves conflicts (for example,
5
+ * `px-2 px-4` → `px-4`). Primitives use this to merge consumer
6
+ * classes with default classes without duplicates. */
7
7
  export function cn(...inputs) {
8
8
  return twMerge(clsx(inputs));
9
9
  }
@@ -3,15 +3,15 @@ export type MenuItemDef = {
3
3
  readonly kind: "item";
4
4
  readonly id: string;
5
5
  readonly label: string;
6
- /** Optional Icon links vor dem Label (16px). */
6
+ /** Optional icon displayed before the label (16px). */
7
7
  readonly icon?: ReactNode;
8
- /** Pure-display Shortcut-Hint rechts (monospace, gedimmt).
9
- * Caller registriert den Shortcut separat — Linear-Style
10
- * Strings wie "O then M", "Alt + Q", "⌘K". */
8
+ /** Display-only shortcut hint on the right (monospace, muted).
9
+ * The caller registers the shortcut separately; Linear-style
10
+ * strings include "O then M", "Alt + Q", and "⌘K". */
11
11
  readonly shortcut?: string;
12
12
  readonly onSelect: () => void;
13
13
  readonly disabled?: boolean;
14
- /** "danger" rendert Label rot — typisch Sign-out, Delete. */
14
+ /** "danger" renders the label in red, typically for sign-out or delete actions. */
15
15
  readonly variant?: "default" | "danger";
16
16
  } | {
17
17
  readonly kind: "separator";
@@ -22,12 +22,12 @@ export type MenuItemDef = {
22
22
  export type ActionMenuProps = {
23
23
  /** Free-form Trigger-Component — Avatar, IconButton, Pill, Custom. */
24
24
  readonly trigger: ReactNode;
25
- /** aria-label für den Trigger-Wrapper. */
25
+ /** aria-label for the trigger wrapper. */
26
26
  readonly triggerLabel?: string;
27
27
  readonly items: readonly MenuItemDef[];
28
- /** Wo das Popup relativ zum Trigger anchorn soll. Default: end. */
28
+ /** Where to anchor the popup relative to the trigger. Default: end. */
29
29
  readonly align?: "start" | "center" | "end";
30
- /** Min-width für den Content. Default: 14rem. */
30
+ /** Minimum width for the content. Default: 14rem. */
31
31
  readonly minWidth?: string;
32
32
  readonly testId?: string;
33
33
  };
@@ -1,28 +1,29 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Generic Action-Menu — Trigger + Items, auf @radix-ui/react-dropdown-
3
- // menu basiert. Wird benutzt für ProfileMenu (Topbar-Avatar), Edit-
4
- // Header-Menu (Three-Dots), List-Row-Kebab — die Mechanik ist überall
5
- // dieselbe, nur der Trigger unterscheidet sich.
2
+ // Generic action menu built on @radix-ui/react-dropdown-
3
+ // menu. Used for ProfileMenu (topbar avatar), Edit-
4
+ // Header-Menu (three dots), and list-row kebab menus; only the
5
+ // trigger differs between uses.
6
6
  //
7
- // Nicht zu verwechseln mit RowActionsCell (siehe primitives/index.tsx),
8
- // die hat die rowActions-Schema-API + per-row Visibility-Logik. Hier
9
- // ist der Layer drunter: stable, schema-frei, callable von App-Code
10
- // und vom Schema-Renderer (KumikoApp).
7
+ // Unlike RowActionsCell (see primitives/index.tsx), this is a
8
+ // stable, schema-free layer callable from app code and the schema
9
+ // renderer (KumikoApp), without the rowActions schema or per-row
10
+ // visibility logic.
11
11
  //
12
- // Items sind Discriminated Union (item / separator / label), Item kann
13
- // optional einen Keyboard-Shortcut-Hint rechts tragen (Linear-Pattern
14
- // "O then M") + Icon links + variant für danger-styling.
12
+ // Items form a discriminated union (item / separator / label). Items
13
+ // can optionally display a keyboard-shortcut hint on the right
14
+ // (Linear-style strings such as "O then M"), an icon on the left,
15
+ // and a variant for danger styling.
15
16
  import { useState } from "react";
16
17
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./dropdown-menu.js";
17
18
  export function ActionMenu({ trigger, triggerLabel, items, align = "end", minWidth = "14rem", testId, }) {
18
19
  const [open, setOpen] = useState(false);
19
20
  return (_jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-testid": testId ?? "action-menu-trigger", ...(triggerLabel !== undefined && { "aria-label": triggerLabel }), className: "rounded outline-none focus-visible:ring-1 focus-visible:ring-ring", children: trigger }) }), _jsx(DropdownMenuContent, { align: align, style: { minWidth }, children: items.map((item, idx) => {
20
21
  if (item.kind === "separator") {
21
- // biome-ignore lint/suspicious/noArrayIndexKey: Separators haben keine ID — Reihenfolge ist Identität
22
+ // biome-ignore lint/suspicious/noArrayIndexKey: Separators have no ID, so their position provides identity
22
23
  return _jsx(DropdownMenuSeparator, {}, `sep-${idx}`);
23
24
  }
24
25
  if (item.kind === "label") {
25
- // biome-ignore lint/suspicious/noArrayIndexKey: gleicher Grund wie separator
26
+ // biome-ignore lint/suspicious/noArrayIndexKey: Separators have no ID, so their position provides identity
26
27
  return _jsx(DropdownMenuLabel, { children: item.label }, `label-${idx}`);
27
28
  }
28
29
  return (_jsxs(DropdownMenuItem, { onSelect: item.onSelect, disabled: item.disabled === true, "data-testid": `action-menu-item-${item.id}`, className: item.variant === "danger" ? "text-destructive focus:text-destructive" : undefined, children: [item.icon !== undefined && _jsx("span", { className: "size-4 shrink-0", children: item.icon }), _jsx("span", { className: "flex-1", children: item.label }), item.shortcut !== undefined && (_jsx("span", { className: "ml-auto text-xs text-muted-foreground tracking-wider font-mono", children: item.shortcut }))] }, item.id));
@@ -6,7 +6,7 @@ export type CalendarPopoverProps = {
6
6
  readonly max?: Date;
7
7
  readonly disabled?: boolean;
8
8
  readonly hasError?: boolean;
9
- /** a11y-Label für den Icon-Button (kein sichtbarer Text). */
9
+ /** a11y label for the icon button (no visible text). */
10
10
  readonly triggerLabel: string;
11
11
  };
12
12
  export declare const inFieldIconButtonClass: string;
@@ -1,14 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // CalendarPopover — gemeinsamer Kalender-Trigger für DateInput und
3
- // TimestampInput. Ein Icon-Button öffnet ein Radix-Popover mit
2
+ // CalendarPopover — shared calendar trigger for DateInput and
3
+ // TimestampInput. An icon button opens a Radix popover with
4
4
  // react-day-picker (v9). captionLayout="dropdown" + startMonth/endMonth
5
- // geben Monats- UND Jahres-Dropdown statt nur Monats-Vor/Zurück — der
6
- // Grund für #369 (10 Jahre zurück war ~120 Klicks). min/max grauen
7
- // Tage außerhalb des erlaubten Bereichs aus.
5
+ // provide month AND year dropdowns instead of only month navigation —
6
+ // going back 10 years would otherwise take ~120 clicks. min/max gray out
7
+ // days outside the allowed range.
8
8
  //
9
- // Der Text-Input (Tippen) lebt bewusst NICHT hier, sondern im jeweiligen
10
- // Primitive — date hat ein Feld, timestamp zwei (Datum + Uhrzeit). So
11
- // teilen beide dieselbe Kalender-Mechanik ohne ihre Eingabe-Form zu teilen.
9
+ // The text input (typing) intentionally lives in each Primitive, not here:
10
+ // date has one field, while timestamp has two (date + time). This lets both
11
+ // share the calendar mechanics without sharing their input layout.
12
12
  import * as PopoverPrimitive from "@radix-ui/react-popover";
13
13
  import { Calendar as CalendarIcon } from "lucide-react";
14
14
  import { lazy, Suspense } from "react";
@@ -24,8 +24,8 @@ const popoverClass = "z-50 rounded-md border bg-popover p-3 text-popover-foregro
24
24
  "data-[state=open]:animate-in data-[state=closed]:animate-out " +
25
25
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 " +
26
26
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95";
27
- // Default-Range wenn das Feld kein min/max setzt — sonst wäre das
28
- // Jahres-Dropdown leer/unbrauchbar. Zur Render-Zeit berechnet.
27
+ // Default range when the field does not set min/max; otherwise the year
28
+ // dropdown would be empty or unusable. Computed at render time.
29
29
  function defaultRange() {
30
30
  const thisYear = new Date().getFullYear();
31
31
  return { start: new Date(1900, 0, 1), end: new Date(thisYear + 10, 11, 31) };
@@ -9,7 +9,7 @@ export const cardSurface = cva("flex flex-col border bg-card text-card-foregroun
9
9
  variants: { radius: { xl: "rounded-[var(--card-radius)]", lg: "rounded-lg" } },
10
10
  defaultVariants: { radius: "xl" },
11
11
  });
12
- // Wraps instead of running off-screen when the row outgrows its container (fw#2528).
12
+ // Wraps instead of running off-screen when the row outgrows its container.
13
13
  export const cardFooter = "flex flex-wrap items-center justify-end gap-2 py-4";
14
14
  export const cardFooterInset = "px-[var(--card-padding)]";
15
15
  // /30 read as nearly invisible against the light-theme card (white card,
@@ -26,8 +26,8 @@ export function DefaultCard({ slots, options, className, testId, dataAttributes,
26
26
  // An unframed card has no radius to clip; overflow-hidden would also
27
27
  // zero its flex-item min-height and cut off content inside a scroller.
28
28
  (framed || fillHeight) && "overflow-hidden",
29
- // Same "no flex-1" reasoning as DefaultForm's own fillHeight card
30
- // (fw#2722/#2778): sizes to content and only shrinks (min-h-0) once
29
+ // Same "no flex-1" reasoning as DefaultForm's own fillHeight card:
30
+ // sizes to content and only shrinks (min-h-0) once
31
31
  // an ancestor is itself height-constrained.
32
32
  fillHeight && "min-h-0 flex flex-col", className), children: [header, children != null && (_jsx("div", { className: cn("grow", screenBody && "flex flex-col gap-6", padded &&
33
33
  (hasHeader