@multiplatform.one/core 7.28.1 → 7.29.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/core",
3
- "version": "7.28.1",
3
+ "version": "7.29.0",
4
4
  "description": "Core framework shell, provider composition, and layout factories for multiplatform.one",
5
5
  "keywords": [
6
6
  "app",
@@ -41,24 +41,24 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@multiplatform.one/i18n": "7.28.1",
45
- "@multiplatform.one/logger": "7.28.1",
46
- "@multiplatform.one/platform": "7.28.1",
47
- "@multiplatform.one/store": "7.28.1",
48
- "@multiplatform.one/theme": "7.28.1",
49
- "@tanstack/devtools-event-client": "^0.4.3",
44
+ "@multiplatform.one/i18n": "7.29.0",
45
+ "@multiplatform.one/logger": "7.29.0",
46
+ "@multiplatform.one/platform": "7.29.0",
47
+ "@multiplatform.one/store": "7.29.0",
48
+ "@multiplatform.one/theme": "7.29.0",
50
49
  "@tanstack/devtools-utils": "^0.4.0",
51
50
  "@tanstack/pacer-devtools": "1.3.0",
52
- "@tanstack/react-devtools": "^0.10.2",
51
+ "@tanstack/react-devtools": "0.10.13",
53
52
  "@tanstack/react-form-devtools": "0.2.22",
54
53
  "react-cookie": "^8.1.2"
55
54
  },
56
55
  "devDependencies": {
57
- "@multiplatform.one/frappe": "7.28.1",
58
- "@multiplatform.one/web3": "7.28.1",
56
+ "@multiplatform.one/frappe": "7.29.0",
57
+ "@multiplatform.one/web3": "7.29.0",
59
58
  "@sentry/react": "^10.51.0",
60
59
  "@tanstack/react-query": "^5.100.7",
61
60
  "@tanstack/react-query-devtools": "^5.100.7",
61
+ "@vxrn/color-scheme": "~1.16.5",
62
62
  "i18next": "^25.10.10",
63
63
  "one": "^1.16.5",
64
64
  "react": "19.2.5",
@@ -72,14 +72,13 @@
72
72
  "vitest": "^4.1.5"
73
73
  },
74
74
  "peerDependencies": {
75
- "@multiplatform.one/frappe": "^7.28.1",
76
- "@multiplatform.one/frappe-ui": "^7.28.1",
77
- "@multiplatform.one/keycloak": "^7.28.1",
78
- "@multiplatform.one/web3": "^7.28.1",
75
+ "@multiplatform.one/frappe": "^7.29.0",
76
+ "@multiplatform.one/frappe-ui": "^7.29.0",
77
+ "@multiplatform.one/keycloak": "^7.29.0",
78
+ "@multiplatform.one/web3": "^7.29.0",
79
79
  "@sentry/react": "^9.0.0",
80
80
  "@tanstack/react-query": "^5.0.0",
81
81
  "@tanstack/react-query-devtools": "^5.0.0",
82
- "@vxrn/color-scheme": "^1.0.0",
83
82
  "expo-font": ">=11.0.0",
84
83
  "i18next": "^25.0.0",
85
84
  "one": "^1.0.0",
@@ -97,9 +96,6 @@
97
96
  "@sentry/react": {
98
97
  "optional": true
99
98
  },
100
- "@vxrn/color-scheme": {
101
- "optional": true
102
- },
103
99
  "i18next": {
104
100
  "optional": true
105
101
  },
@@ -103,8 +103,9 @@ export interface TanstackConfig {
103
103
  /** Options forwarded to the QueryClient constructor. */
104
104
  queryClientOptions?: Record<string, unknown>;
105
105
  /**
106
- * Pass `useUserScheme` from `one` / `@vxrn/color-scheme` so the theme
107
- * devtools panel can control the color scheme (light / dark / system).
106
+ * Pass `useUserScheme` from `one` so the theme devtools panel can control
107
+ * the color scheme (light / dark / system). It must be `one`'s: the root
108
+ * layout paints from that store.
108
109
  */
109
110
  useUserScheme?: () => {
110
111
  setting: "system" | "light" | "dark";
@@ -6,12 +6,14 @@ import { describe, expect, it, vi } from "vitest";
6
6
  import type { ReactNode } from "react";
7
7
  import { createRootLayout } from "./CreateRootLayout.web";
8
8
 
9
- vi.mock("one", () => ({ LoadProgressBar: () => null, Slot: () => null, useMatches: () => [] }));
10
9
  vi.mock("tamagui", async () => ({
11
10
  useDidFinishSSR: (await import("@tamagui/use-did-finish-ssr")).useDidFinishSSR,
12
11
  }));
13
12
  const scheme = vi.hoisted(() => ({ value: "light", stored: "dark", system: "dark" }));
14
- vi.mock("@vxrn/color-scheme", () => ({
13
+ vi.mock("one", () => ({
14
+ LoadProgressBar: () => null,
15
+ Slot: () => null,
16
+ useMatches: () => [],
15
17
  SchemeProvider: ({ children }: { children: ReactNode }) => children,
16
18
  setUserScheme: (value: string) => {
17
19
  scheme.value = value === "system" ? scheme.system : value;
@@ -1,7 +1,6 @@
1
- import { SchemeProvider, useUserScheme } from "@vxrn/color-scheme";
2
1
  import { useFonts } from "expo-font";
3
2
  import i18n from "i18next";
4
- import { Slot, routerStore } from "one";
3
+ import { SchemeProvider, Slot, routerStore, useUserScheme } from "one";
5
4
  import type { ComponentType, ReactNode } from "react";
6
5
  import { StyleSheet } from "react-native";
7
6
  import { GestureHandlerRootView } from "react-native-gesture-handler";
@@ -28,7 +27,7 @@ const styles = StyleSheet.create({
28
27
  /**
29
28
  * Creates a root layout component for **React Native** (iOS + Android).
30
29
  *
31
- * Handles i18n initialization, color-scheme detection via `@vxrn/color-scheme`,
30
+ * Handles i18n initialization, color-scheme detection via `one`'s scheme store,
32
31
  * and wraps content in `GestureHandlerRootView` → `SafeAreaProvider` →
33
32
  * `SchemeProvider` → `AppProvider` → `RootLayout` → `<Slot />`.
34
33
  *
@@ -31,7 +31,12 @@ const env = vi.hoisted(() => {
31
31
  return state;
32
32
  });
33
33
 
34
- vi.mock("one", () => ({ LoadProgressBar: () => null, Slot: () => null, useMatches: () => [] }));
34
+ vi.mock("one", async () => ({
35
+ ...(await vi.importActual<typeof import("@vxrn/color-scheme")>("@vxrn/color-scheme")),
36
+ LoadProgressBar: () => null,
37
+ Slot: () => null,
38
+ useMatches: () => [],
39
+ }));
35
40
  vi.mock("i18next", () => ({
36
41
  default: {
37
42
  use: () => ({ init: () => Promise.resolve() }),
@@ -0,0 +1,135 @@
1
+ // @vitest-environment jsdom
2
+ /**
3
+ * MPO-447: an app hands the devtools panel `useUserScheme` from `one`, and the
4
+ * panel's Scheme knob calls its `set()`. The root layout has to paint from that
5
+ * same store. When core reached `@vxrn/color-scheme` on its own, shc's lock gave
6
+ * core 1.16.12 while `one` pins 1.16.5: two stores, one localStorage key. The
7
+ * click wrote `vxrn-scheme` and `<html>` kept `t_light` until a reload (portal,
8
+ * console on 7.23.2).
9
+ *
10
+ * `one` below re-exports the real store, as the published `one` does. The bare
11
+ * `@vxrn/color-scheme` specifier is a second copy with its own state, standing
12
+ * in for the separately resolved one.
13
+ */
14
+ import type { ReactNode } from "react";
15
+ import { act } from "react";
16
+ import { createRoot } from "react-dom/client";
17
+ import { afterEach, describe, expect, it, vi } from "vitest";
18
+
19
+ vi.mock("one", async () => ({
20
+ ...(await vi.importActual<typeof import("@vxrn/color-scheme")>("@vxrn/color-scheme")),
21
+ LoadProgressBar: () => null,
22
+ Slot: () => null,
23
+ useMatches: () => [],
24
+ }));
25
+ vi.mock("@vxrn/color-scheme", async () => {
26
+ const { useEffect, useLayoutEffect, useReducer } = await import("react");
27
+ type Setting = "system" | "light" | "dark";
28
+ let setting: Setting = "light";
29
+ const listeners = new Set<() => void>();
30
+ const resolve = () => (setting === "dark" ? "dark" : "light");
31
+ const setUserScheme = (next: Setting) => {
32
+ setting = next;
33
+ for (const listener of listeners) listener();
34
+ };
35
+ function useUserScheme() {
36
+ const [, rerender] = useReducer((n: number) => n + 1, 0);
37
+ useEffect(() => {
38
+ listeners.add(rerender);
39
+ return () => {
40
+ listeners.delete(rerender);
41
+ };
42
+ }, []);
43
+ return { setting, value: resolve(), set: setUserScheme };
44
+ }
45
+ function SchemeProvider({ children }: { children: ReactNode }) {
46
+ const { value } = useUserScheme();
47
+ useLayoutEffect(() => {
48
+ document.documentElement.classList.remove("t_light", "t_dark");
49
+ document.documentElement.classList.add(`t_${value}`);
50
+ }, [value]);
51
+ return children;
52
+ }
53
+ return { SchemeProvider, setUserScheme, useUserScheme };
54
+ });
55
+ vi.mock("tamagui", () => ({ useDidFinishSSR: () => true }));
56
+ vi.mock("i18next", () => ({
57
+ default: {
58
+ use: () => ({ init: () => Promise.resolve() }),
59
+ changeLanguage: vi.fn(),
60
+ language: "en",
61
+ },
62
+ }));
63
+ vi.mock("react-i18next", () => ({ initReactI18next: {} }));
64
+ vi.mock("@multiplatform.one/platform", () => ({
65
+ config: { get: (_key: string, fallback?: string) => fallback },
66
+ isDev: false,
67
+ platform: { isServer: false },
68
+ }));
69
+ vi.mock("@multiplatform.one/i18n", () => ({ createI18nConfig: () => ({}) }));
70
+ vi.mock("@multiplatform.one/logger", () => ({ logger: { error: vi.fn() } }));
71
+ vi.mock("@multiplatform.one/store", () => ({
72
+ storage: { getItem: (key: string) => localStorage.getItem(key) },
73
+ }));
74
+ vi.mock("./RuntimePublicConfigScript", () => ({ RuntimePublicConfigScript: () => null }));
75
+
76
+ vi.stubGlobal("matchMedia", (query: string) => ({
77
+ matches: false,
78
+ media: query,
79
+ addEventListener() {},
80
+ removeEventListener() {},
81
+ addListener() {},
82
+ removeListener() {},
83
+ }));
84
+
85
+ afterEach(() => {
86
+ localStorage.clear();
87
+ document.documentElement.className = "";
88
+ });
89
+
90
+ describe("the devtools Scheme knob repaints the root live (MPO-447)", () => {
91
+ it("a set() through one's store flips <html> and the app's scheme without a reload", async () => {
92
+ localStorage.setItem("vxrn-scheme", "light");
93
+ const { createRootLayout } = await import("./CreateRootLayout.web");
94
+ const { useUserScheme } = await import("one");
95
+ const seen: string[] = [];
96
+ let setScheme: ((next: "system" | "light" | "dark") => void) | undefined;
97
+ function Panel() {
98
+ setScheme = useUserScheme().set;
99
+ return null;
100
+ }
101
+ const Layout = createRootLayout({
102
+ AppProvider: ({ systemTheme, children }: { systemTheme?: string; children?: ReactNode }) => {
103
+ seen.push(String(systemTheme));
104
+ return (
105
+ <>
106
+ <Panel />
107
+ {children}
108
+ </>
109
+ );
110
+ },
111
+ RootLayout: ({ children }: { children?: ReactNode }) => <>{children}</>,
112
+ i18n: { languages: ["en"], namespaces: ["common"], defaultLanguage: "en" },
113
+ } as unknown as Parameters<typeof createRootLayout>[0]);
114
+
115
+ const container = document.createElement("div");
116
+ const root = createRoot(container);
117
+ await act(async () => {
118
+ root.render(<Layout />);
119
+ });
120
+ expect(document.documentElement.classList.contains("t_light")).toBe(true);
121
+ expect(seen.at(-1)).toBe("light");
122
+
123
+ await act(async () => {
124
+ setScheme?.("dark");
125
+ });
126
+ expect(localStorage.getItem("vxrn-scheme")).toBe("dark");
127
+ expect(document.documentElement.classList.contains("t_dark")).toBe(true);
128
+ expect(document.documentElement.classList.contains("t_light")).toBe(false);
129
+ expect(seen.at(-1)).toBe("dark");
130
+
131
+ await act(async () => {
132
+ root.unmount();
133
+ });
134
+ });
135
+ });
@@ -3,8 +3,10 @@ import { describe, expect, it, vi } from "vitest";
3
3
  import type { ReactNode } from "react";
4
4
  import { createRootLayout } from "./CreateRootLayout.web";
5
5
 
6
- vi.mock("one", () => ({ LoadProgressBar: () => null, Slot: () => null, useMatches: () => [] }));
7
- vi.mock("@vxrn/color-scheme", () => ({
6
+ vi.mock("one", () => ({
7
+ LoadProgressBar: () => null,
8
+ Slot: () => null,
9
+ useMatches: () => [],
8
10
  SchemeProvider: ({ children }: { children: ReactNode }) => children,
9
11
  setUserScheme: vi.fn(),
10
12
  useUserScheme: () => ({ value: "light" }),
@@ -1,7 +1,16 @@
1
1
  import i18n from "i18next";
2
2
  import { useDidFinishSSR } from "tamagui";
3
- import { LoadProgressBar, Slot, useMatches } from "one";
4
- import { SchemeProvider, setUserScheme, useUserScheme } from "@vxrn/color-scheme";
3
+ // The scheme store comes through `one`, never a bare `@vxrn/color-scheme`: apps
4
+ // hand the devtools panel `one`'s `useUserScheme`, and a second resolved copy
5
+ // of the store would paint <html> from state the panel never writes (MPO-447).
6
+ import {
7
+ LoadProgressBar,
8
+ SchemeProvider,
9
+ Slot,
10
+ setUserScheme,
11
+ useMatches,
12
+ useUserScheme,
13
+ } from "one";
5
14
  import type { ComponentType, ReactNode } from "react";
6
15
  import { CookiesProvider, Cookies } from "react-cookie";
7
16
  import { initReactI18next } from "react-i18next";
@@ -14,7 +14,12 @@ import { ThemeDevtoolsPanel } from "@multiplatform.one/theme";
14
14
  import { storage } from "@multiplatform.one/store";
15
15
  import { useEffect, useState } from "react";
16
16
  import type { TanstackConfig } from "./CreateApp";
17
- import { devtoolsOpenHotkey, useDevtoolsKeyboardTrigger, useShakeToOpen } from "./devtoolsTrigger";
17
+ import {
18
+ devtoolsOpenHotkey,
19
+ useDevtoolsKeyboardTrigger,
20
+ useDevtoolsOpenRequests,
21
+ useShakeToOpen,
22
+ } from "./devtoolsTrigger";
18
23
 
19
24
  const devtoolsSettingsKey = "tanstack_devtools_settings";
20
25
 
@@ -136,6 +141,7 @@ export default function TanstackDevtoolsWrapper({
136
141
  // Cmd/Ctrl+Shift+` — capture-phase so apps cannot swallow it. TanStack
137
142
  // still receives openHotkey (US-layout e.key sequence) as a fallback.
138
143
  useDevtoolsKeyboardTrigger(true);
144
+ const openRequests = useDevtoolsOpenRequests();
139
145
 
140
146
  const plugins = [
141
147
  {
@@ -179,7 +185,7 @@ export default function TanstackDevtoolsWrapper({
179
185
 
180
186
  return (
181
187
  <TanStackDevtools
182
- key={resolvedScheme}
188
+ key={`${resolvedScheme}:${openRequests}`}
183
189
  config={{
184
190
  defaultOpen: false,
185
191
  triggerHidden: true,
@@ -14,9 +14,10 @@ import {
14
14
  devtoolsArmMotionEvent,
15
15
  devtoolsMotionArmedEvent,
16
16
  devtoolsOpenHotkey,
17
- devtoolsShellPluginId,
18
- devtoolsTriggerToggledEvent,
17
+ devtoolsOpenRequestEvent,
18
+ devtoolsShellStateKey,
19
19
  isDevtoolsOpenHotkeyEvent,
20
+ isDevtoolsShellOpen,
20
21
  isMotionPermissionRequired,
21
22
  motionMagnitude,
22
23
  openDevtoolsShell,
@@ -284,42 +285,75 @@ describe("createMotionPermissionFlow", () => {
284
285
  });
285
286
  });
286
287
 
287
- describe("openDevtoolsShell", () => {
288
+ /** A localStorage stand-in holding the shell's persisted state. */
289
+ function memoryStorage(initial?: Record<string, unknown>) {
290
+ const values = new Map<string, string>();
291
+ if (initial) values.set(devtoolsShellStateKey, JSON.stringify(initial));
292
+ return {
293
+ getItem: (key: string) => values.get(key) ?? null,
294
+ setItem: (key: string, value: string) => {
295
+ values.set(key, value);
296
+ },
297
+ state: () => JSON.parse(values.get(devtoolsShellStateKey) ?? "null"),
298
+ };
299
+ }
300
+
301
+ describe("openDevtoolsShell (MPO-447)", () => {
302
+ const realWindow = (globalThis as { window?: unknown }).window;
303
+
288
304
  afterEach(() => {
289
- (globalThis as { __TANSTACK_EVENT_TARGET__?: EventTarget | null }).__TANSTACK_EVENT_TARGET__ =
290
- null;
305
+ (globalThis as { window?: unknown }).window = realWindow;
291
306
  });
292
307
 
293
- it("emits the shell's trigger-toggled open event on the devtools bus", () => {
294
- const bus = new EventTarget();
295
- (globalThis as { __TANSTACK_EVENT_TARGET__?: EventTarget | null }).__TANSTACK_EVENT_TARGET__ =
296
- bus;
297
- // Play the shell's side of the connect handshake.
298
- bus.addEventListener("tanstack-connect", () => {
299
- bus.dispatchEvent(new CustomEvent("tanstack-connect-success"));
300
- });
301
- const received: Array<{ type: string; payload: { isOpen: boolean } }> = [];
302
- bus.addEventListener("tanstack-dispatch-event", (event) => {
303
- received.push((event as CustomEvent).detail);
304
- });
305
- openDevtoolsShell();
306
- expect(received).toHaveLength(1);
307
- expect(received[0]).toMatchObject({
308
- type: `${devtoolsShellPluginId}:${devtoolsTriggerToggledEvent}`,
309
- payload: { isOpen: true },
310
- });
311
- // Once connected, later shakes emit directly (open-only, never close).
312
- openDevtoolsShell();
313
- expect(received).toHaveLength(2);
314
- expect(received[1]?.payload).toEqual({ isOpen: true });
308
+ function stubWindow() {
309
+ const win = new EventTarget();
310
+ (globalThis as { window?: unknown }).window = win;
311
+ const requests = vi.fn();
312
+ win.addEventListener(devtoolsOpenRequestEvent, requests);
313
+ return requests;
314
+ }
315
+
316
+ it("marks a closed shell open for its next mount and asks for that mount", () => {
317
+ const requests = stubWindow();
318
+ const storage = memoryStorage({ persistOpen: false, layout: { tab: "theme" } });
319
+ openDevtoolsShell(storage);
320
+ expect(storage.state()).toEqual({ persistOpen: true, layout: { tab: "theme" } });
321
+ expect(isDevtoolsShellOpen(storage)).toBe(true);
322
+ expect(requests).toHaveBeenCalledTimes(1);
323
+ });
324
+
325
+ it("opens a shell that never persisted any state", () => {
326
+ const requests = stubWindow();
327
+ const storage = memoryStorage();
328
+ openDevtoolsShell(storage);
329
+ expect(storage.state()).toEqual({ persistOpen: true });
330
+ expect(requests).toHaveBeenCalledTimes(1);
331
+ });
332
+
333
+ it("leaves an open shell alone, so a continued shake never remounts or closes it", () => {
334
+ const requests = stubWindow();
335
+ const storage = memoryStorage({ persistOpen: true });
336
+ openDevtoolsShell(storage);
337
+ openDevtoolsShell(storage);
338
+ expect(storage.state()).toEqual({ persistOpen: true });
339
+ expect(requests).not.toHaveBeenCalled();
340
+ });
341
+
342
+ it("recovers from unreadable state instead of throwing", () => {
343
+ const requests = stubWindow();
344
+ const storage = memoryStorage();
345
+ storage.setItem(devtoolsShellStateKey, "{not json");
346
+ openDevtoolsShell(storage);
347
+ expect(storage.state()).toEqual({ persistOpen: true });
348
+ expect(requests).toHaveBeenCalledTimes(1);
315
349
  });
316
350
  });
317
351
 
318
352
  describe("upstream contract (installed packages)", () => {
319
- // The trigger-toggled bus event is an internal implementation detail of
320
- // @tanstack/devtools, not documented public API. Pin it against the
321
- // installed packages so an upstream rename fails tests instead of
322
- // silently breaking shake-to-open.
353
+ // The shell has no public open call. What this code relies on is how the
354
+ // installed shell decides it is open when it mounts, so pin that against
355
+ // the installed packages: an upstream change fails tests instead of
356
+ // silently breaking the shortcut and shake-to-open again.
323
357
  /**
324
358
  * Locate an installed package dir regardless of linker layout: walk
325
359
  * ancestor node_modules (hoisted puts everything at the repo root),
@@ -342,26 +376,82 @@ describe("upstream contract (installed packages)", () => {
342
376
  return null;
343
377
  }
344
378
 
345
- it("@tanstack/devtools still listens for the trigger-toggled event", () => {
379
+ function shellDir(): string {
346
380
  const reactDevtoolsDir = findInstalled("@tanstack/react-devtools");
347
381
  expect(reactDevtoolsDir).toBeTruthy();
348
382
  const devtoolsDir = findInstalled("@tanstack/devtools", reactDevtoolsDir as string);
349
383
  expect(devtoolsDir).toBeTruthy();
350
- const chunkDir = join(devtoolsDir as string, "dist", "devtools");
351
- const chunks = readdirSync(chunkDir).filter((f) => f.endsWith(".js"));
352
- const hit = chunks.some((f) =>
353
- readFileSync(join(chunkDir, f), "utf8").includes(`"${devtoolsTriggerToggledEvent}"`),
354
- );
355
- expect(hit).toBe(true);
384
+ return devtoolsDir as string;
385
+ }
386
+
387
+ function distSources(dir: string): string[] {
388
+ const out: string[] = [];
389
+ for (const sub of ["devtools", "chunk"]) {
390
+ const subDir = join(dir, "dist", sub);
391
+ if (!existsSync(subDir)) continue;
392
+ for (const file of readdirSync(subDir).filter((f) => f.endsWith(".js"))) {
393
+ out.push(readFileSync(join(subDir, file), "utf8"));
394
+ }
395
+ }
396
+ return out;
397
+ }
398
+
399
+ it("core pins the shell exactly, and the installed shell is that pin", () => {
400
+ // A range let shc resolve 0.10.10 while this spec ran against 0.10.2, and
401
+ // the difference was the whole bug. An exact pin makes an app resolve
402
+ // the version these contracts were checked on.
403
+ const pkg = JSON.parse(readFileSync(join(here, "..", "..", "package.json"), "utf8")) as {
404
+ dependencies: Record<string, string>;
405
+ };
406
+ const pinned = pkg.dependencies["@tanstack/react-devtools"];
407
+ expect(pinned).toMatch(/^\d+\.\d+\.\d+$/);
408
+ const installed = JSON.parse(
409
+ readFileSync(
410
+ join(findInstalled("@tanstack/react-devtools") as string, "package.json"),
411
+ "utf8",
412
+ ),
413
+ ) as { version: string };
414
+ expect(installed.version).toBe(pinned);
356
415
  });
357
416
 
358
- it("@tanstack/devtools-client still uses the tanstack-devtools-core plugin id", () => {
359
- const reactDevtoolsDir = findInstalled("@tanstack/react-devtools");
360
- const devtoolsDir = findInstalled("@tanstack/devtools", reactDevtoolsDir as string);
361
- const clientDir = findInstalled("@tanstack/devtools-client", devtoolsDir as string);
417
+ it("the shell stores its state under tanstack_devtools_state", () => {
418
+ const sources = distSources(shellDir());
419
+ expect(sources.some((src) => src.includes(`"${devtoolsShellStateKey}"`))).toBe(true);
420
+ });
421
+
422
+ it("a mounting shell opens from persistOpen in that state", () => {
423
+ const sources = distSources(shellDir());
424
+ expect(
425
+ sources.some((src) =>
426
+ /createSignal\(\s*settings\(\)\.defaultOpen\s*\|\|\s*persistOpen\(\)\s*\)/.test(src),
427
+ ),
428
+ ).toBe(true);
429
+ });
430
+
431
+ it("the shell's own event client is inert outside development, so an open cannot ride the bus", async () => {
432
+ // vitest runs with NODE_ENV !== "development", as a deployed bundle does.
433
+ // From @tanstack/devtools-event-client 0.5 the shell's client is the NoOp
434
+ // there: its trigger-toggled subscription registers nothing. If this
435
+ // fails, upstream dropped the gate and the bus would work again.
436
+ const clientDir = findInstalled("@tanstack/devtools-client", shellDir());
362
437
  expect(clientDir).toBeTruthy();
363
- const source = readFileSync(join(clientDir as string, "dist", "esm", "index.js"), "utf8");
364
- expect(source).toContain(`pluginId: "${devtoolsShellPluginId}"`);
438
+ const target = new EventTarget();
439
+ const added = vi.spyOn(target, "addEventListener");
440
+ const previous = (globalThis as { __TANSTACK_EVENT_TARGET__?: EventTarget | null })
441
+ .__TANSTACK_EVENT_TARGET__;
442
+ (globalThis as { __TANSTACK_EVENT_TARGET__?: EventTarget | null }).__TANSTACK_EVENT_TARGET__ =
443
+ target;
444
+ try {
445
+ const client = await import(
446
+ pathToFileURL(join(clientDir as string, "dist", "esm", "index.js")).href
447
+ );
448
+ const unsubscribe = client.devtoolsEventClient.on("trigger-toggled", () => {});
449
+ expect(added).not.toHaveBeenCalled();
450
+ unsubscribe();
451
+ } finally {
452
+ (globalThis as { __TANSTACK_EVENT_TARGET__?: EventTarget | null }).__TANSTACK_EVENT_TARGET__ =
453
+ previous;
454
+ }
365
455
  });
366
456
 
367
457
  it("@tanstack/pacer-devtools/production resolves the REAL core, not the NoOp", async () => {
@@ -502,6 +592,16 @@ describe("devtools shell wiring", () => {
502
592
  expect(shellSource).toContain("useDevtoolsKeyboardTrigger(");
503
593
  });
504
594
 
595
+ it("the shortcut and a shake open through openDevtoolsShell, and a request remounts the shell", () => {
596
+ expect(triggerSource).toMatch(
597
+ /if \(!isDevtoolsOpenHotkeyEvent\(event\)\) return;[\s\S]*?openDevtoolsShell\(\);/,
598
+ );
599
+ expect(triggerSource).toContain("onShake: () => void = openDevtoolsShell");
600
+ expect(shellSource).toContain("const openRequests = useDevtoolsOpenRequests();");
601
+ expect(shellSource).toContain("key={`${resolvedScheme}:${openRequests}`}");
602
+ expect(triggerSource).not.toContain('from "@tanstack/devtools-event-client"');
603
+ });
604
+
505
605
  it("mounts shake-to-open with the config opt-out", () => {
506
606
  expect(shellSource).toContain("useShakeToOpen(tanstackConfig.shakeToOpen !== false)");
507
607
  });
@@ -1,5 +1,4 @@
1
- import { EventClient } from "@tanstack/devtools-event-client";
2
- import { useEffect } from "react";
1
+ import { useEffect, useState } from "react";
3
2
 
4
3
  // ---------------------------------------------------------------------------
5
4
  // Keyboard trigger
@@ -41,7 +40,7 @@ export function isDevtoolsOpenHotkeyEvent(event: KeyboardEvent): boolean {
41
40
  * shortcut. Capture runs before app-level keydown handlers, so a page
42
41
  * that stopPropagation's cannot swallow it. preventDefault +
43
42
  * stopImmediatePropagation keep TanStack's own createShortcut from
44
- * also firing (which would toggle the panel shut again).
43
+ * also firing, which would toggle the shell it is about to replace.
45
44
  */
46
45
  export function useDevtoolsKeyboardTrigger(enabled: boolean) {
47
46
  useEffect(() => {
@@ -58,41 +57,86 @@ export function useDevtoolsKeyboardTrigger(enabled: boolean) {
58
57
  }
59
58
 
60
59
  // ---------------------------------------------------------------------------
61
- // Programmatic open — the same event the devtools shell's own trigger emits
60
+ // Programmatic open: persist the shell's open state, then remount it
62
61
  // ---------------------------------------------------------------------------
63
62
 
64
63
  /**
65
- * Plugin id of the TanStack Devtools shell's internal event client
66
- * (`devtoolsEventClient` in `@tanstack/devtools-client`). The shell
67
- * subscribes to `<pluginId>:trigger-toggled` and opens/closes on the
68
- * payload's `isOpen`. The spec asserts both strings
69
- * against the installed packages so an upstream rename fails the build
70
- * instead of silently breaking shake-to-open.
64
+ * localStorage key the TanStack Devtools shell keeps its own state under. A
65
+ * mounting shell opens when this state's `persistOpen` is true.
71
66
  */
72
- export const devtoolsShellPluginId = "tanstack-devtools-core";
67
+ export const devtoolsShellStateKey = "tanstack_devtools_state";
73
68
 
74
- /** Event suffix the shell listens on for open/close state changes. */
75
- export const devtoolsTriggerToggledEvent = "trigger-toggled";
69
+ /**
70
+ * Window event `openDevtoolsShell` dispatches once it has marked the shell
71
+ * open. The shell wrapper remounts on it (`useDevtoolsOpenRequests`).
72
+ */
73
+ export const devtoolsOpenRequestEvent = "mpo:devtools-open";
74
+
75
+ interface ShellStorage {
76
+ getItem(key: string): string | null;
77
+ setItem(key: string, value: string): void;
78
+ }
79
+
80
+ function shellStorage(): ShellStorage | undefined {
81
+ try {
82
+ return typeof localStorage === "undefined" ? undefined : localStorage;
83
+ } catch {
84
+ return undefined;
85
+ }
86
+ }
87
+
88
+ function readShellState(storage: ShellStorage): Record<string, unknown> {
89
+ try {
90
+ const parsed: unknown = JSON.parse(storage.getItem(devtoolsShellStateKey) ?? "null");
91
+ return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : {};
92
+ } catch {
93
+ return {};
94
+ }
95
+ }
76
96
 
77
- interface DevtoolsShellEventMap {
78
- "trigger-toggled": { isOpen: boolean };
97
+ /** True when the shell's persisted state says it is open. */
98
+ export function isDevtoolsShellOpen(storage: ShellStorage | undefined = shellStorage()): boolean {
99
+ return storage ? readShellState(storage).persistOpen === true : false;
79
100
  }
80
101
 
81
- let shellEventClient: EventClient<DevtoolsShellEventMap> | undefined;
102
+ /**
103
+ * Open the TanStack Devtools shell. Open-only by design: a continued shake
104
+ * must not re-close the panel, so an open shell is left alone and closing
105
+ * stays on the close button and Escape.
106
+ *
107
+ * The shell exposes no open call, and from `@tanstack/devtools-event-client`
108
+ * 0.5 its `trigger-toggled` listener is a no-op in every build but
109
+ * development, so an emitted open never reached a deployed app (MPO-447). What
110
+ * every shell version does read is `persistOpen` in its stored state, on
111
+ * mount. This writes it and asks the wrapper to remount the shell.
112
+ */
113
+ export function openDevtoolsShell(storage: ShellStorage | undefined = shellStorage()): void {
114
+ if (!storage || isDevtoolsShellOpen(storage)) return;
115
+ try {
116
+ storage.setItem(
117
+ devtoolsShellStateKey,
118
+ JSON.stringify({ ...readShellState(storage), persistOpen: true }),
119
+ );
120
+ } catch {
121
+ return;
122
+ }
123
+ if (typeof window !== "undefined") window.dispatchEvent(new Event(devtoolsOpenRequestEvent));
124
+ }
82
125
 
83
126
  /**
84
- * Open the TanStack Devtools shell programmatically by emitting the same
85
- * bus event the shell's own (hidden) trigger button uses. Open-only by
86
- * design: a continued shake must not immediately re-close the panel —
87
- * closing stays on the close button / Escape / the keyboard shortcut.
88
- * The event client queues emits until the bus connects, so an early shake
89
- * racing the shell mount is still delivered.
127
+ * Counts open requests since mount. The shell wrapper folds it into the
128
+ * shell's React key, so each request mounts a fresh shell that reads the
129
+ * `persistOpen` `openDevtoolsShell` just wrote.
90
130
  */
91
- export function openDevtoolsShell() {
92
- shellEventClient ??= new EventClient<DevtoolsShellEventMap>({
93
- pluginId: devtoolsShellPluginId,
94
- });
95
- shellEventClient.emit(devtoolsTriggerToggledEvent, { isOpen: true });
131
+ export function useDevtoolsOpenRequests(): number {
132
+ const [requests, setRequests] = useState(0);
133
+ useEffect(() => {
134
+ if (typeof window === "undefined") return;
135
+ const onRequest = () => setRequests((n) => n + 1);
136
+ window.addEventListener(devtoolsOpenRequestEvent, onRequest);
137
+ return () => window.removeEventListener(devtoolsOpenRequestEvent, onRequest);
138
+ }, []);
139
+ return requests;
96
140
  }
97
141
 
98
142
  // ---------------------------------------------------------------------------
@@ -49,8 +49,9 @@ export interface TanstackConfig {
49
49
  /** Options forwarded to the QueryClient constructor. */
50
50
  queryClientOptions?: Record<string, unknown>;
51
51
  /**
52
- * Pass `useUserScheme` from `one` / `@vxrn/color-scheme` so the theme
53
- * devtools panel can control the color scheme (light / dark / system).
52
+ * Pass `useUserScheme` from `one` so the theme devtools panel can control
53
+ * the color scheme (light / dark / system). It must be `one`'s: the root
54
+ * layout paints from that store.
54
55
  */
55
56
  useUserScheme?: () => {
56
57
  setting: "system" | "light" | "dark";
@@ -1 +1 @@
1
- {"version":3,"file":"CreateApp.d.ts","sourceRoot":"","sources":["../../src/app/CreateApp.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CreateApp.d.ts","sourceRoot":"","sources":["../../src/app/CreateApp.tsx"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@ export type { CreateRootLayoutConfig, HeadConfig, RootLayoutProps, RootLoaderDat
4
4
  /**
5
5
  * Creates a root layout component for **React Native** (iOS + Android).
6
6
  *
7
- * Handles i18n initialization, color-scheme detection via `@vxrn/color-scheme`,
7
+ * Handles i18n initialization, color-scheme detection via `one`'s scheme store,
8
8
  * and wraps content in `GestureHandlerRootView` → `SafeAreaProvider` →
9
9
  * `SchemeProvider` → `AppProvider` → `RootLayout` → `<Slot />`.
10
10
  *
@@ -1 +1 @@
1
- {"version":3,"file":"CreateRootLayout.native.d.ts","sourceRoot":"","sources":["../../src/app/CreateRootLayout.native.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,OAAO,CAAC;AAQtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAGjE,YAAY,EACV,sBAAsB,EACtB,UAAU,EACV,eAAe,EACf,cAAc,EACd,YAAY,GACb,MAAM,oBAAoB,CAAC;AAM5B;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,sBAAsB,GAAG,aAAa,CA+D3E"}
1
+ {"version":3,"file":"CreateRootLayout.native.d.ts","sourceRoot":"","sources":["../../src/app/CreateRootLayout.native.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,OAAO,CAAC;AAQtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAGjE,YAAY,EACV,sBAAsB,EACtB,UAAU,EACV,eAAe,EACf,cAAc,EACd,YAAY,GACb,MAAM,oBAAoB,CAAC;AAM5B;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,sBAAsB,GAAG,aAAa,CA+D3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"CreateRootLayout.web.d.ts","sourceRoot":"","sources":["../../src/app/CreateRootLayout.web.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,OAAO,CAAC;AAQtD,OAAO,KAAK,EAAE,sBAAsB,EAAkB,MAAM,oBAAoB,CAAC;AAEjF,YAAY,EACV,sBAAsB,EACtB,UAAU,EACV,eAAe,EACf,cAAc,EACd,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,sBAAsB,GAAG,aAAa,CAqL3E"}
1
+ {"version":3,"file":"CreateRootLayout.web.d.ts","sourceRoot":"","sources":["../../src/app/CreateRootLayout.web.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,OAAO,CAAC;AAQtD,OAAO,KAAK,EAAE,sBAAsB,EAAkB,MAAM,oBAAoB,CAAC;AAEjF,YAAY,EACV,sBAAsB,EACtB,UAAU,EACV,eAAe,EACf,cAAc,EACd,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,sBAAsB,GAAG,aAAa,CAqL3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"TanstackDevtools.d.ts","sourceRoot":"","sources":["../../src/app/TanstackDevtools.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAoClD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;CAChC;AAkED,MAAM,CAAC,OAAO,UAAU,uBAAuB,CAAC,EAC9C,cAAc,EACd,cAAc,GACf,EAAE,qBAAqB,2CAoEvB"}
1
+ {"version":3,"file":"TanstackDevtools.d.ts","sourceRoot":"","sources":["../../src/app/TanstackDevtools.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAyClD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;CAChC;AAkED,MAAM,CAAC,OAAO,UAAU,uBAAuB,CAAC,EAC9C,cAAc,EACd,cAAc,GACf,EAAE,qBAAqB,2CAqEvB"}
@@ -26,29 +26,43 @@ export declare function isDevtoolsOpenHotkeyEvent(event: KeyboardEvent): boolean
26
26
  * shortcut. Capture runs before app-level keydown handlers, so a page
27
27
  * that stopPropagation's cannot swallow it. preventDefault +
28
28
  * stopImmediatePropagation keep TanStack's own createShortcut from
29
- * also firing (which would toggle the panel shut again).
29
+ * also firing, which would toggle the shell it is about to replace.
30
30
  */
31
31
  export declare function useDevtoolsKeyboardTrigger(enabled: boolean): void;
32
32
  /**
33
- * Plugin id of the TanStack Devtools shell's internal event client
34
- * (`devtoolsEventClient` in `@tanstack/devtools-client`). The shell
35
- * subscribes to `<pluginId>:trigger-toggled` and opens/closes on the
36
- * payload's `isOpen`. The spec asserts both strings
37
- * against the installed packages so an upstream rename fails the build
38
- * instead of silently breaking shake-to-open.
39
- */
40
- export declare const devtoolsShellPluginId = "tanstack-devtools-core";
41
- /** Event suffix the shell listens on for open/close state changes. */
42
- export declare const devtoolsTriggerToggledEvent = "trigger-toggled";
43
- /**
44
- * Open the TanStack Devtools shell programmatically by emitting the same
45
- * bus event the shell's own (hidden) trigger button uses. Open-only by
46
- * design: a continued shake must not immediately re-close the panel —
47
- * closing stays on the close button / Escape / the keyboard shortcut.
48
- * The event client queues emits until the bus connects, so an early shake
49
- * racing the shell mount is still delivered.
50
- */
51
- export declare function openDevtoolsShell(): void;
33
+ * localStorage key the TanStack Devtools shell keeps its own state under. A
34
+ * mounting shell opens when this state's `persistOpen` is true.
35
+ */
36
+ export declare const devtoolsShellStateKey = "tanstack_devtools_state";
37
+ /**
38
+ * Window event `openDevtoolsShell` dispatches once it has marked the shell
39
+ * open. The shell wrapper remounts on it (`useDevtoolsOpenRequests`).
40
+ */
41
+ export declare const devtoolsOpenRequestEvent = "mpo:devtools-open";
42
+ interface ShellStorage {
43
+ getItem(key: string): string | null;
44
+ setItem(key: string, value: string): void;
45
+ }
46
+ /** True when the shell's persisted state says it is open. */
47
+ export declare function isDevtoolsShellOpen(storage?: ShellStorage | undefined): boolean;
48
+ /**
49
+ * Open the TanStack Devtools shell. Open-only by design: a continued shake
50
+ * must not re-close the panel, so an open shell is left alone and closing
51
+ * stays on the close button and Escape.
52
+ *
53
+ * The shell exposes no open call, and from `@tanstack/devtools-event-client`
54
+ * 0.5 its `trigger-toggled` listener is a no-op in every build but
55
+ * development, so an emitted open never reached a deployed app (MPO-447). What
56
+ * every shell version does read is `persistOpen` in its stored state, on
57
+ * mount. This writes it and asks the wrapper to remount the shell.
58
+ */
59
+ export declare function openDevtoolsShell(storage?: ShellStorage | undefined): void;
60
+ /**
61
+ * Counts open requests since mount. The shell wrapper folds it into the
62
+ * shell's React key, so each request mounts a fresh shell that reads the
63
+ * `persistOpen` `openDevtoolsShell` just wrote.
64
+ */
65
+ export declare function useDevtoolsOpenRequests(): number;
52
66
  /** Defaults: deliberate shakes peak 15–25 m/s²; walking/bumps stay under ~8. */
53
67
  export declare const shakeDefaults: {
54
68
  /** m/s² a sample must exceed to count as a shake peak. */
@@ -1 +1 @@
1
- {"version":3,"file":"devtoolsTrigger.d.ts","sourceRoot":"","sources":["../../src/app/devtoolsTrigger.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"devtoolsTrigger.d.ts","sourceRoot":"","sources":["../../src/app/devtoolsTrigger.ts"],"names":[],"mappings":"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"}