@xemahq/ui-kernel 0.11.0 → 0.11.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.
@@ -22,6 +22,6 @@ interface PageShellProps {
22
22
  backTo?: PageBackTarget;
23
23
  topbarActions?: ReactNode;
24
24
  }
25
- export default function PageShell({ icon: _Icon, title, description, children, actions, tabs, activeTab, onTabChange, maxWidth, embedded, eyebrow, backTo, topbarActions, }: Readonly<PageShellProps>): import("react").JSX.Element;
25
+ export default function PageShell(props: Readonly<PageShellProps>): import("react").JSX.Element;
26
26
  export {};
27
27
  //# sourceMappingURL=page-shell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"page-shell.d.ts","sourceRoot":"","sources":["../../../src/ui/chrome/page-shell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG1E,UAAU,MAAM;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,OAAO,CAAC,EAAE,MAAM,CAAC;IAKjB,MAAM,CAAC,EAAE,cAAc,CAAC;IAOxB,aAAa,CAAC,EAAE,SAAS,CAAC;CAC3B;AAQD,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,IAAI,EAAE,KAAK,EACX,KAAK,EACL,WAAW,EACX,QAAQ,EACR,OAAO,EACP,IAAI,EACJ,SAAS,EACT,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,MAAM,EACN,aAAa,GACd,EAAE,QAAQ,CAAC,cAAc,CAAC,+BA+H1B"}
1
+ {"version":3,"file":"page-shell.d.ts","sourceRoot":"","sources":["../../../src/ui/chrome/page-shell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG1E,UAAU,MAAM;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,OAAO,CAAC,EAAE,MAAM,CAAC;IAKjB,MAAM,CAAC,EAAE,cAAc,CAAC;IAOxB,aAAa,CAAC,EAAE,SAAS,CAAC;CAC3B;AAsBD,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC,+BAMhE"}
@@ -3,7 +3,15 @@ import { FileQuestion } from 'lucide-react';
3
3
  import EmptyState from './EmptyState';
4
4
  import { useHostBridge } from '../../lib/biome-host';
5
5
  import { Tabs, TabsList, TabsTrigger } from '../primitives/tabs';
6
- export default function PageShell({ icon: _Icon, title, description, children, actions, tabs, activeTab, onTabChange, maxWidth, embedded, eyebrow, backTo, topbarActions, }) {
6
+ export default function PageShell(props) {
7
+ return props.embedded ? (_jsx(EmbeddedPageShell, { ...props })) : (_jsx(FullPageShell, { ...props }));
8
+ }
9
+ function EmbeddedPageShell({ children, actions, tabs, activeTab, onTabChange, }) {
10
+ {
11
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col bg-paper", children: [(tabs && tabs.length > 0) || actions ? (_jsx("div", { className: "shrink-0 border-b border-rule bg-paper-elev/40", children: _jsxs("div", { className: "flex items-center gap-3 px-4 py-2 lg:px-6", children: [tabs && tabs.length > 0 && (_jsx(Tabs, { value: activeTab, onValueChange: onTabChange, className: "flex-1 min-w-0", children: _jsx("div", { className: "overflow-x-auto", children: _jsx(TabsList, { className: "h-7 bg-transparent p-0 gap-0", children: tabs.map((tab) => (_jsxs(TabsTrigger, { value: tab.value, className: "rounded-none border-b-2 border-transparent px-2 py-1 text-body-2 font-medium text-ink-3 transition-colors data-[state=active]:border-primary data-[state=active]:text-ink data-[state=active]:bg-transparent data-[state=active]:shadow-none", children: [tab.label, tab.count !== undefined && (_jsx("span", { className: "ml-1.5 text-caption tabular-nums opacity-70", children: tab.count })), tab.indicator && (_jsx("span", { className: "ml-1.5 inline-block h-1.5 w-1.5 rounded-full bg-warning" }))] }, tab.value))) }) }) })), !tabs && _jsx("div", { className: "flex-1" }), actions && (_jsx("div", { className: "flex items-center gap-2 shrink-0", children: actions }))] }) })) : null, _jsx("div", { className: "flex-1 overflow-auto min-h-0", children: _jsx("div", { className: "flex h-full min-h-0 flex-col px-4 pb-4 pt-3 lg:px-6", children: children }) })] }));
12
+ }
13
+ }
14
+ function FullPageShell({ title, description, children, actions, tabs, activeTab, onTabChange, maxWidth, eyebrow, backTo, topbarActions, }) {
7
15
  useHostBridge().pageMeta.usePageMeta({
8
16
  title,
9
17
  description,
@@ -11,9 +19,6 @@ export default function PageShell({ icon: _Icon, title, description, children, a
11
19
  backTo,
12
20
  topbarActions,
13
21
  });
14
- if (embedded) {
15
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col bg-paper", children: [(tabs && tabs.length > 0) || actions ? (_jsx("div", { className: "shrink-0 border-b border-rule bg-paper-elev/40", children: _jsxs("div", { className: "flex items-center gap-3 px-4 py-2 lg:px-6", children: [tabs && tabs.length > 0 && (_jsx(Tabs, { value: activeTab, onValueChange: onTabChange, className: "flex-1 min-w-0", children: _jsx("div", { className: "overflow-x-auto", children: _jsx(TabsList, { className: "h-7 bg-transparent p-0 gap-0", children: tabs.map((tab) => (_jsxs(TabsTrigger, { value: tab.value, className: "rounded-none border-b-2 border-transparent px-2 py-1 text-body-2 font-medium text-ink-3 transition-colors data-[state=active]:border-primary data-[state=active]:text-ink data-[state=active]:bg-transparent data-[state=active]:shadow-none", children: [tab.label, tab.count !== undefined && (_jsx("span", { className: "ml-1.5 text-caption tabular-nums opacity-70", children: tab.count })), tab.indicator && (_jsx("span", { className: "ml-1.5 inline-block h-1.5 w-1.5 rounded-full bg-warning" }))] }, tab.value))) }) }) })), !tabs && _jsx("div", { className: "flex-1" }), actions && (_jsx("div", { className: "flex items-center gap-2 shrink-0", children: actions }))] }) })) : null, _jsx("div", { className: "flex-1 overflow-auto min-h-0", children: _jsx("div", { className: "flex h-full min-h-0 flex-col px-4 pb-4 pt-3 lg:px-6", children: children }) })] }));
16
- }
17
22
  return (_jsxs("div", { className: `flex h-full min-h-0 flex-col ${maxWidth ?? ''}`, children: [(tabs || actions) && (_jsx("div", { className: "shrink-0 border-b border-border/50 px-4 sm:px-5 lg:px-8", children: _jsxs("div", { className: "flex min-h-[34px] flex-col gap-2 py-1.5 sm:flex-row sm:items-center sm:gap-3 sm:py-0", children: [tabs && tabs.length > 0 && (_jsx(Tabs, { value: activeTab, onValueChange: onTabChange, className: "flex-1 min-w-0", children: _jsx("div", { className: "overflow-x-auto", children: _jsx(TabsList, { className: "h-7 bg-transparent p-0 gap-0", children: tabs.map((tab) => (_jsxs(TabsTrigger, { value: tab.value, className: "whitespace-nowrap rounded-none border-b-2 border-transparent px-2.5 py-1.5 text-caption font-medium text-ink-3/90 transition-colors data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:text-ink data-[state=active]:shadow-none hover:text-ink", children: [tab.label, tab.count !== undefined && (_jsx("span", { className: "ml-1.5 text-caption tabular-nums opacity-60", children: tab.count })), tab.indicator && (_jsx("span", { className: "ml-1.5 inline-block h-1.5 w-1.5 rounded-full bg-warning" }))] }, tab.value))) }) }) })), !tabs && _jsx("div", { className: "flex-1" }), actions && (_jsx("div", { className: "flex w-full flex-wrap items-center gap-2 sm:w-auto sm:shrink-0 sm:justify-end", children: actions }))] }) })), _jsx("div", { className: "flex min-h-0 flex-1 flex-col overflow-auto px-4 pb-5 pt-4 sm:px-6 sm:pb-6 sm:pt-5", children: children || (_jsx(EmptyState, { icon: FileQuestion, title: "Nothing to show yet", description: "This page has no content to display. Check back once the workspace has data." })) })] }));
18
23
  }
19
24
  //# sourceMappingURL=page-shell.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"page-shell.js","sourceRoot":"","sources":["../../../src/ui/chrome/page-shell.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"page-shell.js","sourceRoot":"","sources":["../../../src/ui/chrome/page-shell.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xemahq/ui-kernel",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Host-framework-agnostic UI kernel for the Xema OS. Defines the SystemBus orchestration contract (capability.invoke, cross-biome intents, command palette, xema:// deeplinks, window manager) AND the biome-host contract surface (FrontendBiome/FrontendBiomeFactory, HostBridge, the singleton biomeRegistry, session contributions) that every frontend biome composes against. No Vite, Next.js, or React-Router — React itself IS allowed as the shared component model (the contracts traffic in ReactNode/ComponentType and a React context). Concrete host adapters (router/auth/toast wiring) live in separate packages that consume this kernel. The SystemBus is pure orchestration: it never authorizes, the backend capability-router enforces all policy.",
@@ -74,10 +74,13 @@
74
74
  "@tanstack/react-query": "^5.100.13",
75
75
  "@types/node": "25.2.3",
76
76
  "@types/react": "^18.3.30",
77
+ "@types/react-dom": "^18.3.7",
78
+ "@xemahq/kernel-contracts": "^8.0.0",
77
79
  "prettier": "3.6.2",
78
80
  "react": "^18.3.1",
81
+ "react-dom": "^18.3.1",
79
82
  "typescript": "5.9.3",
80
- "@xemahq/kernel-contracts": "^5.0.0"
83
+ "vitest": "^3.2.4"
81
84
  },
82
85
  "exports": {
83
86
  ".": {
@@ -109,7 +112,7 @@
109
112
  "scripts": {
110
113
  "clean": "rm -rf dist",
111
114
  "build": "tsc -p tsconfig.json",
112
- "test": "echo '[ui-kernel] *.{test,spec} files run under host-web vitest (this package carries no vitest dep by design — see tsconfig exclude). Write tests with vitest; the frontend-bridge sync fails on a node:test file.'",
115
+ "test": "echo '[ui-kernel] *.{test,spec} files RUN under host-web vitest (this package has no vitest runner config). vitest is a devDependency here for TYPES only, so tsc typechecks the specs in the repo that owns them.'",
113
116
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
114
117
  "lint": "eslint .",
115
118
  "typecheck": "tsc -p tsconfig.lint.json --noEmit"
@@ -0,0 +1,117 @@
1
+ /**
2
+ * An EMBEDDED PageShell must not touch the topbar.
3
+ *
4
+ * ── THE DEFECT ────────────────────────────────────────────────────────────
5
+ *
6
+ * `embedded` means "the host page already owns the topbar — render only my
7
+ * body". But `usePageMeta` was called UNCONDITIONALLY, above the `embedded`
8
+ * check, and the page-meta store is a STACK: "nested pages can push their own
9
+ * meta; on unmount the outer meta is restored".
10
+ *
11
+ * So an embedded shell pushed its own `title`, `description`, `eyebrow`,
12
+ * `backTo` and `topbarActions` OVER its host's — the precise opposite of what
13
+ * embedding means. The host's title and back button silently became the
14
+ * child's for as long as the child was mounted, and the page-meta contract
15
+ * ("never render a back button inside the page body… move it into
16
+ * usePageMeta") made that the ONLY back affordance on screen, so it pointed at
17
+ * the wrong parent.
18
+ *
19
+ * A hook cannot be called conditionally, so the fix is structural: `PageShell`
20
+ * calls no hooks and only chooses between two components. These tests pin the
21
+ * OBSERVABLE property — whether meta is registered — not the file layout, so
22
+ * any future refactor that reintroduces the push fails them.
23
+ *
24
+ * Runs under the host-web vitest mirror (this package carries no vitest dep).
25
+ */
26
+ import { FileQuestion } from 'lucide-react';
27
+ import { renderToStaticMarkup } from 'react-dom/server';
28
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
29
+
30
+
31
+ const registered: unknown[] = [];
32
+
33
+ // The kernel reaches the page-meta store through the HostBridge singleton,
34
+ // never a host import — so the bridge is the seam to observe.
35
+ vi.mock('../../lib/biome-host/host-bridge', async () => {
36
+ const actual = await vi.importActual<Record<string, unknown>>(
37
+ '../../lib/biome-host/host-bridge',
38
+ );
39
+ return {
40
+ ...actual,
41
+ useHostBridge: () => ({
42
+ pageMeta: {
43
+ usePageMeta: (meta: unknown) => {
44
+ registered.push(meta);
45
+ },
46
+ },
47
+ }),
48
+ };
49
+ });
50
+
51
+ const { default: PageShell } = await import('./page-shell');
52
+
53
+ beforeEach(() => {
54
+ registered.length = 0;
55
+ });
56
+
57
+ const META = {
58
+ icon: FileQuestion,
59
+ title: 'Child Title',
60
+ description: 'Child description',
61
+ eyebrow: 'Child eyebrow',
62
+ backTo: { to: '/child-parent', label: 'Child parent' },
63
+ } as const;
64
+
65
+ describe('PageShell page-meta ownership', () => {
66
+ it('registers NOTHING when embedded — the host owns the topbar', () => {
67
+ // THE BUG, directly. Before the fix this registered the child's meta,
68
+ // replacing the host's title and back button on a stack-based store.
69
+ renderToStaticMarkup(
70
+ <PageShell {...META} embedded>
71
+ <p>body</p>
72
+ </PageShell>,
73
+ );
74
+ expect(registered).toEqual([]);
75
+ });
76
+
77
+ it('registers its meta when NOT embedded — a full page owns its topbar', () => {
78
+ // The other half: the fix must not silence the normal case.
79
+ renderToStaticMarkup(
80
+ <PageShell {...META}>
81
+ <p>body</p>
82
+ </PageShell>,
83
+ );
84
+ expect(registered).toHaveLength(1);
85
+ expect(registered[0]).toMatchObject({
86
+ title: 'Child Title',
87
+ description: 'Child description',
88
+ eyebrow: 'Child eyebrow',
89
+ backTo: { to: '/child-parent' },
90
+ });
91
+ });
92
+
93
+ it('still renders the embedded body — not registering is not "render nothing"', () => {
94
+ const html = renderToStaticMarkup(
95
+ <PageShell {...META} embedded>
96
+ <p>embedded-body-marker</p>
97
+ </PageShell>,
98
+ );
99
+ expect(html).toContain('embedded-body-marker');
100
+ });
101
+
102
+ it('renders the embedded sub-tab strip, which is the child’s own chrome', () => {
103
+ // Embedding suppresses the TOPBAR meta, not the child's in-body tabs.
104
+ const html = renderToStaticMarkup(
105
+ <PageShell
106
+ {...META}
107
+ embedded
108
+ tabs={[{ value: 'a', label: 'Alpha' }]}
109
+ activeTab="a"
110
+ >
111
+ <p>body</p>
112
+ </PageShell>,
113
+ );
114
+ expect(html).toContain('Alpha');
115
+ expect(registered).toEqual([]);
116
+ });
117
+ });
@@ -44,37 +44,41 @@ interface PageShellProps {
44
44
  * page-meta store) and renders an optional tab strip + action row above the
45
45
  * scrollable content canvas. Host-framework-agnostic: reaches the page-meta
46
46
  * store through the kernel `HostBridge` singleton, never a host import.
47
+ *
48
+ * ── WHY THIS DISPATCHES INSTEAD OF BRANCHING ──────────────────────────────
49
+ *
50
+ * `embedded` means "the host page already owns the topbar — render only my
51
+ * body". But `usePageMeta` used to be called UNCONDITIONALLY, above the
52
+ * `embedded` check, and the page-meta store is a STACK ("nested pages can push
53
+ * their own meta; on unmount the outer meta is restored"). So an embedded shell
54
+ * pushed its own title, description, eyebrow, backTo and topbarActions OVER its
55
+ * host's — the precise opposite of what embedding means. The host's back button
56
+ * and title silently became the child's for as long as the child was mounted.
57
+ *
58
+ * A hook cannot be called conditionally, so the fix is structural: this
59
+ * component calls NO hooks and only chooses. The embedded variant never reaches
60
+ * the page-meta hook because it is a different component, not a later branch.
47
61
  */
48
- export default function PageShell({
49
- icon: _Icon,
50
- title,
51
- description,
62
+ export default function PageShell(props: Readonly<PageShellProps>) {
63
+ return props.embedded ? (
64
+ <EmbeddedPageShell {...props} />
65
+ ) : (
66
+ <FullPageShell {...props} />
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Embedded body. Registers NO page meta — the host page owns the topbar.
72
+ * Renders only the child content plus an optional sub-tab strip + actions.
73
+ */
74
+ function EmbeddedPageShell({
52
75
  children,
53
76
  actions,
54
77
  tabs,
55
78
  activeTab,
56
79
  onTabChange,
57
- maxWidth,
58
- embedded,
59
- eyebrow,
60
- backTo,
61
- topbarActions,
62
80
  }: Readonly<PageShellProps>) {
63
- // Single source of truth for the topbar title. Pages that bypass
64
- // PageShell call the page-meta hook directly; PageShell wraps the same
65
- // hook so consumers don't need to register meta twice.
66
- useHostBridge().pageMeta.usePageMeta({
67
- title,
68
- description,
69
- eyebrow,
70
- backTo,
71
- topbarActions,
72
- });
73
- if (embedded) {
74
- // Embedded mode: the host page already provides the title + tabs +
75
- // context, so we render ONLY the child content + any sub-tab strip +
76
- // actions inline. This eliminates the legacy duplicate icon-and-title
77
- // row that previously stole vertical space.
81
+ {
78
82
  return (
79
83
  <div className="flex h-full min-h-0 flex-col bg-paper">
80
84
  {(tabs && tabs.length > 0) || actions ? (
@@ -126,6 +130,34 @@ export default function PageShell({
126
130
  </div>
127
131
  );
128
132
  }
133
+ }
134
+
135
+ /**
136
+ * Full page. Owns the topbar meta for the route it renders.
137
+ */
138
+ function FullPageShell({
139
+ title,
140
+ description,
141
+ children,
142
+ actions,
143
+ tabs,
144
+ activeTab,
145
+ onTabChange,
146
+ maxWidth,
147
+ eyebrow,
148
+ backTo,
149
+ topbarActions,
150
+ }: Readonly<PageShellProps>) {
151
+ // Single source of truth for the topbar title. Pages that bypass
152
+ // PageShell call the page-meta hook directly; PageShell wraps the same
153
+ // hook so consumers don't need to register meta twice.
154
+ useHostBridge().pageMeta.usePageMeta({
155
+ title,
156
+ description,
157
+ eyebrow,
158
+ backTo,
159
+ topbarActions,
160
+ });
129
161
 
130
162
  return (
131
163
  <div className={`flex h-full min-h-0 flex-col ${maxWidth ?? ''}`}>