@timber-js/app 0.1.42 → 0.1.43

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": "@timber-js/app",
3
- "version": "0.1.42",
3
+ "version": "0.1.43",
4
4
  "description": "Vite-native React framework for Cloudflare Workers — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -100,7 +100,7 @@
100
100
  "nuqs": "^2.0.0",
101
101
  "react": "^19.2.4",
102
102
  "react-dom": "^19.2.4",
103
- "vite": "^8.0.0",
103
+ "vite": "^8.0.1",
104
104
  "zod": "^3.22.0 || ^4.0.0"
105
105
  },
106
106
  "peerDependenciesMeta": {
@@ -47,16 +47,35 @@ export interface NavigationState {
47
47
  * The context is created lazily to avoid calling createContext at module
48
48
  * level. In the RSC environment, React.createContext doesn't exist —
49
49
  * calling it at import time would crash the server.
50
+ *
51
+ * IMPORTANT: Context instances are stored on globalThis, NOT in module-
52
+ * level variables. The RSC client bundler duplicates this module across
53
+ * the browser-entry chunk (index) and client-reference chunk (shared-app)
54
+ * because both entry graphs import it. Module-level variables would create
55
+ * separate singleton instances per chunk — the provider in TransitionRoot
56
+ * (index chunk) would use context A while the consumer in LinkStatusProvider
57
+ * (shared-app chunk) reads from context B. globalThis guarantees a single
58
+ * instance regardless of how many times the module is duplicated.
59
+ *
60
+ * See design/19-client-navigation.md §"Singleton Context Guarantee"
50
61
  */
51
- let _context: React.Context<NavigationState | null> | undefined;
62
+
63
+ // Symbol keys for globalThis storage — prevents collisions with user code
64
+ const NAV_CTX_KEY = Symbol.for('__timber_nav_ctx');
65
+ const PENDING_CTX_KEY = Symbol.for('__timber_pending_nav_ctx');
52
66
 
53
67
  function getOrCreateContext(): React.Context<NavigationState | null> | undefined {
54
- if (_context !== undefined) return _context;
68
+ const existing = (globalThis as Record<symbol, unknown>)[NAV_CTX_KEY] as
69
+ | React.Context<NavigationState | null>
70
+ | undefined;
71
+ if (existing !== undefined) return existing;
55
72
  // createContext may not exist in the RSC environment
56
73
  if (typeof React.createContext === 'function') {
57
- _context = React.createContext<NavigationState | null>(null);
74
+ const ctx = React.createContext<NavigationState | null>(null);
75
+ (globalThis as Record<symbol, unknown>)[NAV_CTX_KEY] = ctx;
76
+ return ctx;
58
77
  }
59
- return _context;
78
+ return undefined;
60
79
  }
61
80
 
62
81
  /**
@@ -125,23 +144,25 @@ export function getNavigationState(): NavigationState {
125
144
 
126
145
  /**
127
146
  * Separate context for the in-flight navigation URL. Provided by
128
- * TransitionRoot (useOptimistic state), consumed by LinkStatusProvider
147
+ * TransitionRoot (urgent useState), consumed by LinkStatusProvider
129
148
  * and useNavigationPending.
130
149
  *
131
- * Lives in this module (not a separate file) to guarantee singleton
132
- * identity across chunks. The `'use client'` LinkStatusProvider and
133
- * the non-directive TransitionRoot both import from this module —
134
- * if they were in separate files, the bundler could duplicate the
135
- * module-level context variable across chunks.
150
+ * Uses globalThis via Symbol.for for the same reason as NavigationContext
151
+ * above the bundler duplicates this module across chunks, and module-
152
+ * level variables would create separate context instances.
136
153
  */
137
- let _pendingContext: React.Context<string | null> | undefined;
138
154
 
139
155
  function getOrCreatePendingContext(): React.Context<string | null> | undefined {
140
- if (_pendingContext !== undefined) return _pendingContext;
156
+ const existing = (globalThis as Record<symbol, unknown>)[PENDING_CTX_KEY] as
157
+ | React.Context<string | null>
158
+ | undefined;
159
+ if (existing !== undefined) return existing;
141
160
  if (typeof React.createContext === 'function') {
142
- _pendingContext = React.createContext<string | null>(null);
161
+ const ctx = React.createContext<string | null>(null);
162
+ (globalThis as Record<symbol, unknown>)[PENDING_CTX_KEY] = ctx;
163
+ return ctx;
143
164
  }
144
- return _pendingContext;
165
+ return undefined;
145
166
  }
146
167
 
147
168
  /**