@xzibit/ui 0.1.0 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,34 @@
2
2
 
3
3
  All notable changes to `@xzibit/ui` are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/) loosely; versioning follows [SemVer](https://semver.org/).
4
4
 
5
- ## [0.1.0] — 2026-05-22
5
+ ## [0.1.1] — 2026-05-24
6
+
7
+ ### Fixed
8
+ - **Peer dependency widened** to accept React 19 (`react@"^18.0.0 || ^19.0.0"` and `react-dom` same). Surfaced during ERP Overview migration — `--legacy-peer-deps` was needed to install on the React 19 app. Affects every React 19 app planning to adopt; v0.1.0 blocked clean install.
9
+ - **API field-name contract resilience.** `useApps` now normalizes raw Supabase column names (`app_url`, `display_section`, `display_order`) to the canonical `App` shape (`url`, `section`, `section_order`) via internal `normalizeApp()` at the fetch boundary. Apps with existing endpoints returning raw query rows now work without changes; new endpoints can return either shape. Surfaced during ERP Overview migration — package rendered empty dropdown until CC added a manual `.map()` normalization in the route handler.
10
+
11
+ ### Added
12
+ - `RawApp` type exported for apps that want to type their endpoint response explicitly
13
+ - `normalizeApp(raw: RawApp): App` helper exported for apps that want to call it directly (rare; `useApps` calls it automatically)
14
+
15
+ ### Documentation
16
+ - README's `/api/me/apps` section now documents both accepted field-name conventions and the normalization behaviour.
17
+
18
+ ---
19
+
20
+ ## [0.1.0] — 2026-05-22 (drafted) / 2026-05-23 (published)
21
+
22
+ ### Published artifact
23
+ - npm: https://www.npmjs.com/package/@xzibit/ui
24
+ - Provenance attestation: present
25
+ - Install: `npm install @xzibit/ui`
26
+
27
+ ### Bug fixes applied between draft and publish (sync'd back into this draft 2026-05-23)
28
+ - **`package-lock.json` generated** via `npm install` (not present in initial draft — required for `npm ci` in CI).
29
+ - **Removed stale `import React` namespace imports** from 4 source files (`TopBar.tsx`, `XzibitMark.tsx`, `BackToLauncher.tsx`, `AppsDropdown.tsx`). The package's `tsconfig.json` uses `"jsx": "react-jsx"` (modern transform, doesn't need React import) + `noUnusedLocals: true`, so bare `import React from 'react'` fails the DTS build. Fixed by removing the unused namespace import — named imports (`useState`, `useEffect`, etc.) where actually needed are kept.
30
+
31
+ ### Future-package note
32
+ When drafting any future React component package for the `@xzibit/*` scope, omit `import React from 'react'` unless React is actually used directly (e.g. `React.forwardRef`). Just import the hooks / types you need by name.
6
33
 
7
34
  ### Added
8
35
 
package/README.md CHANGED
@@ -58,7 +58,15 @@ That's it. The bar renders:
58
58
 
59
59
  ### `/api/me/apps` endpoint required
60
60
 
61
- `AppsDropdown` calls `GET /api/me/apps` (same-origin) for the cross-app navigation list. Each consuming app must expose this endpoint per CODING-STANDARDS §6.3:
61
+ `AppsDropdown` calls `GET /api/me/apps` (same-origin) for the cross-app navigation list. Each consuming app must expose this endpoint per CODING-STANDARDS §6.3.
62
+
63
+ **Field-name contract (v0.1.1+):** the endpoint can return EITHER:
64
+ - **Canonical shape** (recommended for new endpoints): `{ name, url, description?, section?, section_order? }`
65
+ - **Supabase column-name passthrough** (for endpoints that return raw `public.apps` rows): `{ name, app_url, description?, display_section?, display_order? }`
66
+
67
+ `useApps` normalizes both shapes to the canonical `App` interface at the fetch boundary — components downstream never see the raw form. Apps with existing endpoints that return raw column names work without changes.
68
+
69
+ Example route handler:
62
70
 
63
71
  ```typescript
64
72
  // src/app/api/me/apps/route.ts
package/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  BackToLauncher: () => BackToLauncher,
25
25
  TopBar: () => TopBar,
26
26
  XzibitMark: () => XzibitMark,
27
+ normalizeApp: () => normalizeApp,
27
28
  useApps: () => useApps
28
29
  });
29
30
  module.exports = __toCommonJS(index_exports);
@@ -133,6 +134,19 @@ var import_react3 = require("react");
133
134
 
134
135
  // src/useApps.ts
135
136
  var import_react2 = require("react");
137
+
138
+ // src/types.ts
139
+ function normalizeApp(raw) {
140
+ return {
141
+ name: raw.name,
142
+ url: raw.url ?? raw.app_url ?? "",
143
+ description: raw.description,
144
+ section: raw.section ?? raw.display_section ?? null,
145
+ section_order: raw.section_order ?? raw.display_order
146
+ };
147
+ }
148
+
149
+ // src/useApps.ts
136
150
  function useApps(options = {}) {
137
151
  const { endpoint = "/api/me/apps", lazy = false } = options;
138
152
  const [apps, setApps] = (0, import_react2.useState)([]);
@@ -153,7 +167,7 @@ function useApps(options = {}) {
153
167
  if (data.error) {
154
168
  throw new Error(data.error);
155
169
  }
156
- setApps(data.apps ?? []);
170
+ setApps((data.apps ?? []).map(normalizeApp));
157
171
  } catch (err) {
158
172
  console.error("[@xzibit/ui useApps] fetch failed:", err);
159
173
  setError(err instanceof Error ? err.message : "Unknown error");
@@ -551,5 +565,6 @@ function BuildBadge({
551
565
  BackToLauncher,
552
566
  TopBar,
553
567
  XzibitMark,
568
+ normalizeApp,
554
569
  useApps
555
570
  });
package/dist/index.d.cts CHANGED
@@ -95,11 +95,12 @@ declare function XzibitMark({ size, className, ariaLabel }: XzibitMarkProps): re
95
95
  * Shared types for @xzibit/ui.
96
96
  */
97
97
  /**
98
- * An app in the Xzibit Apps portfolio.
98
+ * Canonical app shape consumed by `@xzibit/ui` components.
99
99
  *
100
- * Shape matches the response from each app's `/api/me/apps` endpoint,
101
- * which queries `public.apps` JOIN `public.role_app_permissions` and returns
102
- * the apps the authenticated user has access to per their role.
100
+ * This is what `useApps` returns after normalization. App authors writing
101
+ * NEW `/api/me/apps` endpoints SHOULD return this shape directly. Apps with
102
+ * existing endpoints that return raw Supabase column names (see `RawApp` below)
103
+ * are also accepted — `useApps` normalizes at the fetch boundary.
103
104
  */
104
105
  interface App {
105
106
  /** Display name, e.g. "Capacity Planner". */
@@ -113,16 +114,46 @@ interface App {
113
114
  /** Sort order for the section itself (matches launcher curation). */
114
115
  section_order?: number;
115
116
  }
117
+ /**
118
+ * Raw shape accepted from `/api/me/apps` endpoints.
119
+ *
120
+ * Supports two field-naming conventions:
121
+ * - **Canonical** (recommended for new endpoints): `url`, `section`, `section_order`
122
+ * - **Supabase column-name passthrough** (for endpoints that return raw query rows):
123
+ * `app_url`, `display_section`, `display_order`
124
+ *
125
+ * `useApps` normalizes to the canonical `App` shape via `normalizeApp()` at the
126
+ * fetch boundary — components downstream only see the canonical shape.
127
+ *
128
+ * Added in v0.1.1 (2026-05-24) after ERP Overview migration surfaced the contract
129
+ * mismatch with raw Supabase column names.
130
+ */
131
+ interface RawApp {
132
+ name: string;
133
+ url?: string;
134
+ app_url?: string;
135
+ description?: string;
136
+ section?: string | null;
137
+ display_section?: string | null;
138
+ section_order?: number;
139
+ display_order?: number;
140
+ }
116
141
  /**
117
142
  * Response shape from `/api/me/apps`.
118
143
  *
119
144
  * Per CODING-STANDARDS §6.4 — successful responses are wrapped in a `data`
120
- * or domain-specific key (in this case `apps`).
145
+ * or domain-specific key (in this case `apps`). The `apps` array contains
146
+ * `RawApp` items; `useApps` normalizes them to `App`.
121
147
  */
122
148
  interface AppsResponse {
123
- apps?: App[];
149
+ apps?: RawApp[];
124
150
  error?: string;
125
151
  }
152
+ /**
153
+ * Internal helper — normalizes a `RawApp` to the canonical `App` shape.
154
+ * Used by `useApps` at the fetch boundary; not typically called by consumers.
155
+ */
156
+ declare function normalizeApp(raw: RawApp): App;
126
157
 
127
158
  interface UseAppsResult {
128
159
  apps: App[];
@@ -150,4 +181,4 @@ interface UseAppsOptions {
150
181
  */
151
182
  declare function useApps(options?: UseAppsOptions): UseAppsResult;
152
183
 
153
- export { type App, AppsDropdown, type AppsDropdownProps, type AppsResponse, BackToLauncher, type BackToLauncherProps, TopBar, type TopBarProps, type UseAppsOptions, type UseAppsResult, XzibitMark, type XzibitMarkProps, useApps };
184
+ export { type App, AppsDropdown, type AppsDropdownProps, type AppsResponse, BackToLauncher, type BackToLauncherProps, type RawApp, TopBar, type TopBarProps, type UseAppsOptions, type UseAppsResult, XzibitMark, type XzibitMarkProps, normalizeApp, useApps };
package/dist/index.d.ts CHANGED
@@ -95,11 +95,12 @@ declare function XzibitMark({ size, className, ariaLabel }: XzibitMarkProps): re
95
95
  * Shared types for @xzibit/ui.
96
96
  */
97
97
  /**
98
- * An app in the Xzibit Apps portfolio.
98
+ * Canonical app shape consumed by `@xzibit/ui` components.
99
99
  *
100
- * Shape matches the response from each app's `/api/me/apps` endpoint,
101
- * which queries `public.apps` JOIN `public.role_app_permissions` and returns
102
- * the apps the authenticated user has access to per their role.
100
+ * This is what `useApps` returns after normalization. App authors writing
101
+ * NEW `/api/me/apps` endpoints SHOULD return this shape directly. Apps with
102
+ * existing endpoints that return raw Supabase column names (see `RawApp` below)
103
+ * are also accepted — `useApps` normalizes at the fetch boundary.
103
104
  */
104
105
  interface App {
105
106
  /** Display name, e.g. "Capacity Planner". */
@@ -113,16 +114,46 @@ interface App {
113
114
  /** Sort order for the section itself (matches launcher curation). */
114
115
  section_order?: number;
115
116
  }
117
+ /**
118
+ * Raw shape accepted from `/api/me/apps` endpoints.
119
+ *
120
+ * Supports two field-naming conventions:
121
+ * - **Canonical** (recommended for new endpoints): `url`, `section`, `section_order`
122
+ * - **Supabase column-name passthrough** (for endpoints that return raw query rows):
123
+ * `app_url`, `display_section`, `display_order`
124
+ *
125
+ * `useApps` normalizes to the canonical `App` shape via `normalizeApp()` at the
126
+ * fetch boundary — components downstream only see the canonical shape.
127
+ *
128
+ * Added in v0.1.1 (2026-05-24) after ERP Overview migration surfaced the contract
129
+ * mismatch with raw Supabase column names.
130
+ */
131
+ interface RawApp {
132
+ name: string;
133
+ url?: string;
134
+ app_url?: string;
135
+ description?: string;
136
+ section?: string | null;
137
+ display_section?: string | null;
138
+ section_order?: number;
139
+ display_order?: number;
140
+ }
116
141
  /**
117
142
  * Response shape from `/api/me/apps`.
118
143
  *
119
144
  * Per CODING-STANDARDS §6.4 — successful responses are wrapped in a `data`
120
- * or domain-specific key (in this case `apps`).
145
+ * or domain-specific key (in this case `apps`). The `apps` array contains
146
+ * `RawApp` items; `useApps` normalizes them to `App`.
121
147
  */
122
148
  interface AppsResponse {
123
- apps?: App[];
149
+ apps?: RawApp[];
124
150
  error?: string;
125
151
  }
152
+ /**
153
+ * Internal helper — normalizes a `RawApp` to the canonical `App` shape.
154
+ * Used by `useApps` at the fetch boundary; not typically called by consumers.
155
+ */
156
+ declare function normalizeApp(raw: RawApp): App;
126
157
 
127
158
  interface UseAppsResult {
128
159
  apps: App[];
@@ -150,4 +181,4 @@ interface UseAppsOptions {
150
181
  */
151
182
  declare function useApps(options?: UseAppsOptions): UseAppsResult;
152
183
 
153
- export { type App, AppsDropdown, type AppsDropdownProps, type AppsResponse, BackToLauncher, type BackToLauncherProps, TopBar, type TopBarProps, type UseAppsOptions, type UseAppsResult, XzibitMark, type XzibitMarkProps, useApps };
184
+ export { type App, AppsDropdown, type AppsDropdownProps, type AppsResponse, BackToLauncher, type BackToLauncherProps, type RawApp, TopBar, type TopBarProps, type UseAppsOptions, type UseAppsResult, XzibitMark, type XzibitMarkProps, normalizeApp, useApps };
package/dist/index.js CHANGED
@@ -103,6 +103,19 @@ import { useState as useState3, useEffect as useEffect2, useRef, Fragment } from
103
103
 
104
104
  // src/useApps.ts
105
105
  import { useState as useState2, useEffect, useCallback } from "react";
106
+
107
+ // src/types.ts
108
+ function normalizeApp(raw) {
109
+ return {
110
+ name: raw.name,
111
+ url: raw.url ?? raw.app_url ?? "",
112
+ description: raw.description,
113
+ section: raw.section ?? raw.display_section ?? null,
114
+ section_order: raw.section_order ?? raw.display_order
115
+ };
116
+ }
117
+
118
+ // src/useApps.ts
106
119
  function useApps(options = {}) {
107
120
  const { endpoint = "/api/me/apps", lazy = false } = options;
108
121
  const [apps, setApps] = useState2([]);
@@ -123,7 +136,7 @@ function useApps(options = {}) {
123
136
  if (data.error) {
124
137
  throw new Error(data.error);
125
138
  }
126
- setApps(data.apps ?? []);
139
+ setApps((data.apps ?? []).map(normalizeApp));
127
140
  } catch (err) {
128
141
  console.error("[@xzibit/ui useApps] fetch failed:", err);
129
142
  setError(err instanceof Error ? err.message : "Unknown error");
@@ -520,5 +533,6 @@ export {
520
533
  BackToLauncher,
521
534
  TopBar,
522
535
  XzibitMark,
536
+ normalizeApp,
523
537
  useApps
524
538
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xzibit/ui",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Shared chrome components for the Xzibit Apps portfolio. v0.1: TopBar + BackToLauncher + AppsDropdown + XzibitMark + useApps hook. Single source of truth for portfolio chrome — tweak once, every app picks it up on next deploy.",
5
5
  "license": "MIT",
6
6
  "author": "Xzibit Apps",
@@ -42,8 +42,8 @@
42
42
  "prepublishOnly": "npm run build"
43
43
  },
44
44
  "peerDependencies": {
45
- "react": "^18.0.0",
46
- "react-dom": "^18.0.0"
45
+ "react": "^18.0.0 || ^19.0.0",
46
+ "react-dom": "^18.0.0 || ^19.0.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "typescript": "^5.0.0",