jamdesk 1.0.13 → 1.0.14

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 (132) hide show
  1. package/README.md +89 -4
  2. package/dist/__tests__/unit/auth.test.d.ts +2 -0
  3. package/dist/__tests__/unit/auth.test.d.ts.map +1 -0
  4. package/dist/__tests__/unit/auth.test.js +169 -0
  5. package/dist/__tests__/unit/auth.test.js.map +1 -0
  6. package/dist/__tests__/unit/config.test.d.ts +2 -0
  7. package/dist/__tests__/unit/config.test.d.ts.map +1 -0
  8. package/dist/__tests__/unit/config.test.js +76 -0
  9. package/dist/__tests__/unit/config.test.js.map +1 -0
  10. package/dist/__tests__/unit/deploy.test.d.ts +2 -0
  11. package/dist/__tests__/unit/deploy.test.d.ts.map +1 -0
  12. package/dist/__tests__/unit/deploy.test.js +273 -0
  13. package/dist/__tests__/unit/deploy.test.js.map +1 -0
  14. package/dist/__tests__/unit/deps-sync.test.js +3 -1
  15. package/dist/__tests__/unit/deps-sync.test.js.map +1 -1
  16. package/dist/__tests__/unit/dev-loading-server.test.d.ts +2 -0
  17. package/dist/__tests__/unit/dev-loading-server.test.d.ts.map +1 -0
  18. package/dist/__tests__/unit/dev-loading-server.test.js +141 -0
  19. package/dist/__tests__/unit/dev-loading-server.test.js.map +1 -0
  20. package/dist/__tests__/unit/docs-json-writer.test.d.ts +2 -0
  21. package/dist/__tests__/unit/docs-json-writer.test.d.ts.map +1 -0
  22. package/dist/__tests__/unit/docs-json-writer.test.js +71 -0
  23. package/dist/__tests__/unit/docs-json-writer.test.js.map +1 -0
  24. package/dist/__tests__/unit/loading-page.test.d.ts +2 -0
  25. package/dist/__tests__/unit/loading-page.test.d.ts.map +1 -0
  26. package/dist/__tests__/unit/loading-page.test.js +73 -0
  27. package/dist/__tests__/unit/loading-page.test.js.map +1 -0
  28. package/dist/__tests__/unit/login.test.d.ts +2 -0
  29. package/dist/__tests__/unit/login.test.d.ts.map +1 -0
  30. package/dist/__tests__/unit/login.test.js +100 -0
  31. package/dist/__tests__/unit/login.test.js.map +1 -0
  32. package/dist/__tests__/unit/logout.test.d.ts +2 -0
  33. package/dist/__tests__/unit/logout.test.d.ts.map +1 -0
  34. package/dist/__tests__/unit/logout.test.js +39 -0
  35. package/dist/__tests__/unit/logout.test.js.map +1 -0
  36. package/dist/__tests__/unit/tarball.test.d.ts +2 -0
  37. package/dist/__tests__/unit/tarball.test.d.ts.map +1 -0
  38. package/dist/__tests__/unit/tarball.test.js +126 -0
  39. package/dist/__tests__/unit/tarball.test.js.map +1 -0
  40. package/dist/__tests__/unit/whoami.test.d.ts +2 -0
  41. package/dist/__tests__/unit/whoami.test.d.ts.map +1 -0
  42. package/dist/__tests__/unit/whoami.test.js +47 -0
  43. package/dist/__tests__/unit/whoami.test.js.map +1 -0
  44. package/dist/commands/deploy.d.ts +13 -0
  45. package/dist/commands/deploy.d.ts.map +1 -0
  46. package/dist/commands/deploy.js +265 -0
  47. package/dist/commands/deploy.js.map +1 -0
  48. package/dist/commands/dev.d.ts.map +1 -1
  49. package/dist/commands/dev.js +48 -25
  50. package/dist/commands/dev.js.map +1 -1
  51. package/dist/commands/login.d.ts +8 -0
  52. package/dist/commands/login.d.ts.map +1 -0
  53. package/dist/commands/login.js +135 -0
  54. package/dist/commands/login.js.map +1 -0
  55. package/dist/commands/logout.d.ts +5 -0
  56. package/dist/commands/logout.d.ts.map +1 -0
  57. package/dist/commands/logout.js +17 -0
  58. package/dist/commands/logout.js.map +1 -0
  59. package/dist/commands/whoami.d.ts +5 -0
  60. package/dist/commands/whoami.d.ts.map +1 -0
  61. package/dist/commands/whoami.js +24 -0
  62. package/dist/commands/whoami.js.map +1 -0
  63. package/dist/index.js +50 -7
  64. package/dist/index.js.map +1 -1
  65. package/dist/lib/auth.d.ts +34 -0
  66. package/dist/lib/auth.d.ts.map +1 -0
  67. package/dist/lib/auth.js +105 -0
  68. package/dist/lib/auth.js.map +1 -0
  69. package/dist/lib/config.d.ts +9 -0
  70. package/dist/lib/config.d.ts.map +1 -1
  71. package/dist/lib/config.js +7 -1
  72. package/dist/lib/config.js.map +1 -1
  73. package/dist/lib/dev-loading-server.d.ts +22 -0
  74. package/dist/lib/dev-loading-server.d.ts.map +1 -0
  75. package/dist/lib/dev-loading-server.js +117 -0
  76. package/dist/lib/dev-loading-server.js.map +1 -0
  77. package/dist/lib/docs-config.d.ts +1 -0
  78. package/dist/lib/docs-config.d.ts.map +1 -1
  79. package/dist/lib/docs-config.js.map +1 -1
  80. package/dist/lib/docs-json-writer.d.ts +2 -0
  81. package/dist/lib/docs-json-writer.d.ts.map +1 -0
  82. package/dist/lib/docs-json-writer.js +35 -0
  83. package/dist/lib/docs-json-writer.js.map +1 -0
  84. package/dist/lib/loading-page.d.ts +11 -0
  85. package/dist/lib/loading-page.d.ts.map +1 -0
  86. package/dist/lib/loading-page.js +222 -0
  87. package/dist/lib/loading-page.js.map +1 -0
  88. package/dist/lib/output.d.ts +13 -5
  89. package/dist/lib/output.d.ts.map +1 -1
  90. package/dist/lib/output.js +22 -5
  91. package/dist/lib/output.js.map +1 -1
  92. package/dist/lib/tarball.d.ts +28 -0
  93. package/dist/lib/tarball.d.ts.map +1 -0
  94. package/dist/lib/tarball.js +117 -0
  95. package/dist/lib/tarball.js.map +1 -0
  96. package/package.json +5 -2
  97. package/vendored/app/[[...slug]]/page.tsx +6 -20
  98. package/vendored/app/api/chat/[project]/route.ts +323 -0
  99. package/vendored/app/api/mcp/[project]/route.ts +2 -63
  100. package/vendored/components/chat/ChatCodeBlock.tsx +63 -0
  101. package/vendored/components/chat/ChatEmptyState.tsx +79 -0
  102. package/vendored/components/chat/ChatFAB.tsx +36 -0
  103. package/vendored/components/chat/ChatInput.tsx +106 -0
  104. package/vendored/components/chat/ChatMessage.tsx +176 -0
  105. package/vendored/components/chat/ChatPanel.tsx +206 -0
  106. package/vendored/components/chat/ChatResizeHandle.tsx +108 -0
  107. package/vendored/components/chat/LazyChatPanel.tsx +19 -0
  108. package/vendored/components/layout/LayoutWrapper.tsx +134 -44
  109. package/vendored/components/layout/PageColumns.tsx +40 -0
  110. package/vendored/components/navigation/Header.tsx +74 -29
  111. package/vendored/components/navigation/Sidebar.tsx +17 -2
  112. package/vendored/hooks/useChat.ts +335 -0
  113. package/vendored/hooks/useChatPanel.tsx +101 -0
  114. package/vendored/lib/anthropic-client.ts +19 -0
  115. package/vendored/lib/build/extract-tarball.ts +150 -0
  116. package/vendored/lib/chat-prompt.ts +56 -0
  117. package/vendored/lib/docs-types.ts +14 -0
  118. package/vendored/lib/docs.ts +22 -4
  119. package/vendored/lib/embedding-chunker.ts +173 -0
  120. package/vendored/lib/generate-starter-questions.ts +98 -0
  121. package/vendored/lib/isr-build-executor.ts +2 -1
  122. package/vendored/lib/middleware-helpers.ts +21 -0
  123. package/vendored/lib/route-helpers.ts +96 -0
  124. package/vendored/lib/snippet-loader-isr.ts +107 -1
  125. package/vendored/lib/static-artifacts.ts +3 -2
  126. package/vendored/lib/validate-config.ts +1 -0
  127. package/vendored/lib/vector-store.ts +213 -0
  128. package/vendored/schema/docs-schema.json +33 -0
  129. package/vendored/scripts/dev-project.cjs +6 -0
  130. package/vendored/shared/types.ts +6 -5
  131. package/vendored/tailwind.config.ts +9 -0
  132. package/vendored/themes/jam/variables.css +2 -2
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Upstash Vector Store
3
+ *
4
+ * Manages vector embeddings for AI chat. Each project gets its own namespace
5
+ * in the Upstash Vector index. Upstash handles embedding generation
6
+ * server-side — we send raw text, not pre-computed vectors.
7
+ *
8
+ * Used in two contexts:
9
+ * 1. Build time: upsertChunks() stores page chunks after a successful build
10
+ * 2. Chat API: querySimilarChunks() retrieves relevant context for user queries
11
+ */
12
+ import { Index, FusionAlgorithm, WeightingStrategy, QueryMode } from '@upstash/vector';
13
+ import type { EmbeddingChunk } from './embedding-chunker.js';
14
+
15
+ export interface ChunkMetadata {
16
+ [key: string]: unknown;
17
+ pageSlug: string;
18
+ sectionHeading: string;
19
+ pageTitle: string;
20
+ content: string;
21
+ }
22
+
23
+ /** Upstash limit per upsert call */
24
+ const BATCH_SIZE = 100;
25
+
26
+ /**
27
+ * Upstash metadata limit is 48KB per record. The content field in metadata
28
+ * is used for LLM context retrieval, while the `data` field (no limit) is
29
+ * used for embedding generation. Truncate metadata content to stay under limit.
30
+ */
31
+ const MAX_METADATA_CONTENT_CHARS = 10000;
32
+
33
+ /** Hybrid search parameters — DBSF fusion with IDF-weighted BM25 */
34
+ const HYBRID_QUERY_OPTS = {
35
+ queryMode: QueryMode.HYBRID,
36
+ fusionAlgorithm: FusionAlgorithm.DBSF,
37
+ weightingStrategy: WeightingStrategy.IDF,
38
+ } as const;
39
+
40
+ /**
41
+ * Minimum similarity score — below this threshold results are near-random
42
+ * and would give the LLM poor context. Initially set for cosine similarity;
43
+ * may need recalibration after switching to DBSF/RRF fusion scores.
44
+ */
45
+ const MIN_SCORE = 0.3;
46
+
47
+ /** Max chunks per page — ensures diverse results across pages */
48
+ const MAX_CHUNKS_PER_PAGE = 3;
49
+
50
+ /** Create a namespaced Upstash Vector index for a project. */
51
+ function getNamespace(projectId: string) {
52
+ const index = new Index({
53
+ url: process.env.UPSTASH_VECTOR_REST_URL!,
54
+ token: process.env.UPSTASH_VECTOR_REST_TOKEN!,
55
+ });
56
+ return index.namespace(projectId);
57
+ }
58
+
59
+ /**
60
+ * Replace all vectors for a project with fresh chunks.
61
+ *
62
+ * Resets the namespace first (deletes all existing vectors), then upserts
63
+ * new chunks in batches of 100. Upstash generates embeddings from the
64
+ * `data` field — no need for a separate embedding model.
65
+ *
66
+ * Note: This is NOT safe for concurrent calls on the same project — reset()
67
+ * could clear another build's partially-upserted data. The build queue system
68
+ * guarantees one build per project at a time, preventing this race condition.
69
+ */
70
+ export async function upsertChunks(
71
+ projectId: string,
72
+ chunks: EmbeddingChunk[],
73
+ ): Promise<void> {
74
+ const ns = getNamespace(projectId);
75
+
76
+ await ns.reset();
77
+
78
+ for (let i = 0; i < chunks.length; i += BATCH_SIZE) {
79
+ const batch = chunks.slice(i, i + BATCH_SIZE);
80
+ await ns.upsert(
81
+ batch.map(c => ({
82
+ id: c.id,
83
+ data: c.content,
84
+ metadata: {
85
+ pageSlug: c.pageSlug,
86
+ sectionHeading: c.sectionHeading,
87
+ pageTitle: c.pageTitle,
88
+ content: c.content.length > MAX_METADATA_CONTENT_CHARS
89
+ ? c.content.slice(0, MAX_METADATA_CONTENT_CHARS) + '...'
90
+ : c.content,
91
+ } satisfies ChunkMetadata,
92
+ })),
93
+ );
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Strip common question/request phrasing to extract the core topic.
99
+ * Long queries like "How do I make an analytics call - give me a javascript example"
100
+ * embed poorly — the request-pattern words ("give me", "example") dilute the topic signal.
101
+ * Extracting just the topic ("analytics call") produces much better vector matches.
102
+ *
103
+ * Returns the stripped topic, or null if stripping didn't meaningfully shorten the query.
104
+ */
105
+ export function extractTopicQuery(queryText: string): string | null {
106
+ let topic = queryText;
107
+
108
+ // Strip leading question patterns
109
+ topic = topic.replace(/^(how (do|can|would|should) (i|you|we)|can (i|you|we)|what('s| is| are)|where (do|can|is|are)|show me|tell me|give me|explain|describe|help me)\b/i, '');
110
+
111
+ // Strip trailing request patterns (after dash, comma, or period)
112
+ topic = topic.replace(/\s*[-–—,.]\s*(give me|show me|provide|include|with)\b.*$/i, '');
113
+
114
+ // Strip request-type suffixes
115
+ topic = topic.replace(/\b(give me|show me|provide|include)\s+(a|an|some|the)?\s*(javascript|typescript|python|code|curl)?\s*(example|sample|snippet|demo)s?\b/gi, '');
116
+
117
+ // Strip filler words
118
+ topic = topic.replace(/\b(please|just|basically|simply|actually)\b/gi, '');
119
+
120
+ // Clean up extra whitespace and trim
121
+ topic = topic.replace(/\s+/g, ' ').trim();
122
+
123
+ // Only use the topic query if it's meaningfully shorter (at least 30% shorter)
124
+ if (!topic || topic.length > queryText.length * 0.7) return null;
125
+ return topic;
126
+ }
127
+
128
+ /** Query with hybrid mode, falling back to dense-only if hybrid is not supported */
129
+ async function queryWithFallback(
130
+ ns: ReturnType<typeof getNamespace>,
131
+ params: { data: string; topK: number; includeMetadata: true },
132
+ ): Promise<Array<{ id: string | number; score: number; metadata?: ChunkMetadata }>> {
133
+ try {
134
+ return await ns.query<ChunkMetadata>({ ...params, ...HYBRID_QUERY_OPTS });
135
+ } catch (err) {
136
+ // Hybrid not supported on this index — fall back to dense-only
137
+ console.warn('[vector-store] Hybrid query failed, falling back to dense-only:', String(err));
138
+ return await ns.query<ChunkMetadata>({ ...params, queryMode: QueryMode.DENSE });
139
+ }
140
+ }
141
+
142
+ type RawResult = { id: string | number; score: number; metadata?: ChunkMetadata };
143
+
144
+ /**
145
+ * Deduplicate and filter raw vector results into scored chunks.
146
+ * Filters out low-score results and limits chunks per page for diversity.
147
+ * Accepts multiple result lists — earlier lists take priority.
148
+ */
149
+ function filterAndMerge(
150
+ resultSets: RawResult[][],
151
+ topK: number,
152
+ ): Array<ChunkMetadata & { score: number }> {
153
+ const seen = new Set<string>();
154
+ const pageCount = new Map<string, number>();
155
+ const merged: Array<ChunkMetadata & { score: number }> = [];
156
+
157
+ for (const results of resultSets) {
158
+ for (const r of results) {
159
+ if (merged.length >= topK) return merged;
160
+ if (!r.metadata || r.score < MIN_SCORE) continue;
161
+
162
+ const id = String(r.id);
163
+ if (seen.has(id)) continue;
164
+
165
+ const count = pageCount.get(r.metadata.pageSlug) ?? 0;
166
+ if (count >= MAX_CHUNKS_PER_PAGE) continue;
167
+
168
+ seen.add(id);
169
+ pageCount.set(r.metadata.pageSlug, count + 1);
170
+ merged.push({ ...r.metadata, score: r.score });
171
+ }
172
+ }
173
+
174
+ return merged;
175
+ }
176
+
177
+ /**
178
+ * Find chunks most similar to a query string.
179
+ *
180
+ * Uses a dual-query strategy for long queries: runs the full query AND an
181
+ * extracted topic-only version in parallel, then merges results. This prevents
182
+ * request-pattern words (e.g. "give me a javascript example") from diluting
183
+ * the topic signal in the embedding.
184
+ *
185
+ * Returns up to `topK` results with their similarity scores,
186
+ * filtering out any results with missing metadata.
187
+ */
188
+ export async function querySimilarChunks(
189
+ projectId: string,
190
+ queryText: string,
191
+ topK = 5,
192
+ ): Promise<Array<ChunkMetadata & { score: number }>> {
193
+ const ns = getNamespace(projectId);
194
+ const queryParams = { topK, includeMetadata: true as const };
195
+
196
+ const topicQuery = extractTopicQuery(queryText);
197
+
198
+ // Dual-query: topic query is the PRIMARY source (better topical relevance);
199
+ // the full query fills remaining slots with unique results only.
200
+ if (topicQuery) {
201
+ const [fullResults, topicResults] = await Promise.all([
202
+ queryWithFallback(ns, { data: queryText, ...queryParams }),
203
+ queryWithFallback(ns, { data: topicQuery, ...queryParams }),
204
+ ]);
205
+
206
+ // Topic results first for best topical relevance
207
+ return filterAndMerge([topicResults, fullResults], topK);
208
+ }
209
+
210
+ // Simple single-query path for short/simple queries
211
+ const results = await queryWithFallback(ns, { data: queryText, ...queryParams });
212
+ return filterAndMerge([results], topK);
213
+ }
@@ -13,6 +13,10 @@
13
13
  "default": "https://www.jamdesk.com/docs.json",
14
14
  "description": "The URL to the JSON schema file for validation"
15
15
  },
16
+ "projectId": {
17
+ "type": "string",
18
+ "description": "Jamdesk project identifier. Automatically set by jamdesk deploy."
19
+ },
16
20
  "name": {
17
21
  "type": "string",
18
22
  "minLength": 1,
@@ -1363,6 +1367,23 @@
1363
1367
  },
1364
1368
  "additionalProperties": false,
1365
1369
  "description": "Configure page metadata display settings"
1370
+ },
1371
+ "chat": {
1372
+ "type": "object",
1373
+ "properties": {
1374
+ "enabled": {
1375
+ "type": "boolean",
1376
+ "description": "Enable AI-powered chat assistant (default: true). Set to false to disable.",
1377
+ "default": true
1378
+ },
1379
+ "starterQuestions": {
1380
+ "type": "array",
1381
+ "items": { "type": "string", "minLength": 5, "maxLength": 200 },
1382
+ "maxItems": 4,
1383
+ "description": "Suggested questions shown in chat empty state. Auto-generated by AI during builds when omitted. Set to [] to disable."
1384
+ }
1385
+ },
1386
+ "additionalProperties": false
1366
1387
  }
1367
1388
  },
1368
1389
  "required": [
@@ -1383,6 +1404,9 @@
1383
1404
  "$schema": {
1384
1405
  "$ref": "#/anyOf/0/properties/$schema"
1385
1406
  },
1407
+ "projectId": {
1408
+ "$ref": "#/anyOf/0/properties/projectId"
1409
+ },
1386
1410
  "name": {
1387
1411
  "$ref": "#/anyOf/0/properties/name"
1388
1412
  },
@@ -1460,6 +1484,9 @@
1460
1484
  },
1461
1485
  "metadata": {
1462
1486
  "$ref": "#/anyOf/0/properties/metadata"
1487
+ },
1488
+ "chat": {
1489
+ "$ref": "#/anyOf/0/properties/chat"
1463
1490
  }
1464
1491
  },
1465
1492
  "required": [
@@ -1480,6 +1507,9 @@
1480
1507
  "$schema": {
1481
1508
  "$ref": "#/anyOf/0/properties/$schema"
1482
1509
  },
1510
+ "projectId": {
1511
+ "$ref": "#/anyOf/0/properties/projectId"
1512
+ },
1483
1513
  "name": {
1484
1514
  "$ref": "#/anyOf/0/properties/name"
1485
1515
  },
@@ -1557,6 +1587,9 @@
1557
1587
  },
1558
1588
  "metadata": {
1559
1589
  "$ref": "#/anyOf/0/properties/metadata"
1590
+ },
1591
+ "chat": {
1592
+ "$ref": "#/anyOf/0/properties/chat"
1560
1593
  }
1561
1594
  },
1562
1595
  "required": [
@@ -379,6 +379,7 @@ async function runDev() {
379
379
  JAMDESK_PROJECTS_DIR: projectsDir, // Pass the resolved projects directory to child scripts
380
380
  HOST_AT_DOCS: hostAtDocs ? 'true' : 'false', // Pass to Next.js config
381
381
  NEXT_PUBLIC_BASE_PATH: hostAtDocs ? '/docs' : '', // Pass to client components for asset URLs
382
+ NEXT_PUBLIC_PROJECT_SLUG: effectiveProjectName, // Pass to chat hook for local dev API routing
382
383
  };
383
384
 
384
385
  try {
@@ -451,6 +452,11 @@ async function runDev() {
451
452
  env,
452
453
  });
453
454
 
455
+ // Note: No file watcher needed for docs.json. In dev mode, getDocsConfig()
456
+ // reads directly from the project source (not public/docs.json) with mtime-based
457
+ // cache invalidation. Edits are picked up automatically on browser refresh.
458
+ // Writing to public/ during dev triggers Turbopack recompilation hangs.
459
+
454
460
  // Step 4: Run Next.js dev server
455
461
  console.log(`\nšŸš€ Starting dev server on port ${port}...`);
456
462
  // Get first page and OpenAPI config from docs.json (reuse projectDocsJson from step 1)
@@ -12,18 +12,19 @@ export interface BuildParams {
12
12
  projectId: string;
13
13
  buildId: string;
14
14
  slug: string;
15
- repoFullName: string;
16
- branch: string;
17
- installationId: number;
15
+ repoFullName?: string; // Optional for CLI builds
16
+ branch?: string; // Optional for CLI builds
17
+ installationId?: number; // Optional for CLI builds
18
18
  triggeredBy: string;
19
- commitSha: string;
20
- commitMessage: string;
19
+ commitSha?: string; // Optional for CLI builds
20
+ commitMessage?: string; // Optional for CLI builds
21
21
  fullRebuild?: boolean; // If true, forces full R2 upload (ignores manifest)
22
22
  docsPath?: string | null; // Monorepo: path to docs.json directory (e.g., "jamdesk")
23
23
  domain?: string; // Project's custom domain or subdomain (e.g., "docs.acme.com" or "acme.jamdesk.app")
24
24
  showBranding?: boolean; // Show "Powered by Jamdesk" in footer (defaults to true)
25
25
  hostAtDocs?: boolean; // URL structure: true = /docs/*, false = root /*
26
26
  autoMigrate?: boolean; // If true, auto-convert deprecated components during build
27
+ tarballKey?: string; // R2 key for CLI-uploaded tarball
27
28
  }
28
29
 
29
30
  export interface ErrorDetails {
@@ -33,6 +33,15 @@ export default {
33
33
  'theme-code-bg': 'var(--color-code-bg)',
34
34
  'theme-code-text': 'var(--color-code-text)',
35
35
  },
36
+ keyframes: {
37
+ 'fd-fade-in': {
38
+ from: { opacity: '0' },
39
+ to: { opacity: '1' },
40
+ },
41
+ },
42
+ animation: {
43
+ 'fd-fade-in': 'fd-fade-in 0.15s ease-in forwards',
44
+ },
36
45
  },
37
46
  },
38
47
  }
@@ -216,8 +216,8 @@ body[data-theme="jam"]:not(.dark) main {
216
216
  background-color: transparent !important;
217
217
  }
218
218
 
219
- /* Target wrapper divs that contain the main content */
220
- body[data-theme="jam"]:not(.dark) > div > div.flex {
219
+ /* Target wrapper divs that contain the main content (exclude chat panel) */
220
+ body[data-theme="jam"]:not(.dark) > div > div.flex:not([role="dialog"]):not(:has([data-chat-panel])) {
221
221
  background-color: transparent !important;
222
222
  }
223
223