@robosystems/core 0.3.0 → 0.4.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/README.md CHANGED
@@ -1,18 +1,17 @@
1
1
  # @robosystems/core
2
2
 
3
- Shared React components, hooks, contexts, and utilities for the RoboSystems ecosystem apps, published as an npm package.
3
+ [![npm version](https://badge.fury.io/js/@robosystems%2Fcore.svg)](https://www.npmjs.com/package/@robosystems/core)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
5
 
5
- [![npm](https://img.shields.io/npm/v/%40robosystems%2Fcore)](https://www.npmjs.com/package/@robosystems/core)
6
+ Shared React core for the RoboSystems ecosystem apps — authentication, platform contexts, task monitoring, and the common UI component set used by robosystems-app, roboledger-app, and roboinvestor-app. Built for the Next.js App Router (client/server components and Server Actions) and consumed as a versioned npm package.
6
7
 
7
- ## Overview
8
+ ## Features
8
9
 
9
- This library contains the platform-coupled frontend core shared between:
10
-
11
- - **robosystems-app** — Graph database management interface
12
- - **roboledger-app** — Accounting and bookkeeping interface
13
- - **roboinvestor-app** — Investment management interface
14
-
15
- It is Next.js-specific by design (App Router client/server components, Server Actions, `next/headers` cookie helpers) and is intended to be consumed by Next.js applications only. The compiled output targets bundler resolution — it is not loadable by Node's native ESM resolver.
10
+ - **Authentication** sign-in/sign-up forms, `AuthProvider`/`AuthGuard`, JWT and SSO token handling
11
+ - **Contexts** — graph, entity, organization, service-offerings, and sidebar state
12
+ - **Task monitoring** — SSE-based operation monitoring with a polling fallback for long-running jobs
13
+ - **UI components** — layout, chat, forms, settings, API keys, and the shared Flowbite/Tailwind theme
14
+ - **Hooks & utilities** — user, limits, toast, and media-query hooks plus cookie persistence helpers
16
15
 
17
16
  ## Installation
18
17
 
@@ -20,233 +19,33 @@ It is Next.js-specific by design (App Router client/server components, Server Ac
20
19
  npm install @robosystems/core
21
20
  ```
22
21
 
23
- ### Peer dependencies
24
-
25
- The consuming app provides:
26
-
27
- | Peer | Range |
28
- | --------------------- | ---------- |
29
- | `react` / `react-dom` | >=18 <20 |
30
- | `next` | >=15 <17 |
31
- | `flowbite-react` | ^0.12.5 |
32
- | `react-icons` | >=4 <6 |
33
- | `@robosystems/client` | >=0.3.2 <1 |
34
-
35
- ### App wiring
36
-
37
- Two integration points beyond the install:
38
-
39
- 1. **Tailwind content scan** — the package ships Tailwind utility classes in its compiled JS; add it to the app's `tailwind.config.ts`:
40
-
41
- ```ts
42
- content: [
43
- // ...existing globs
44
- 'node_modules/@robosystems/core/**/*.js',
45
- ]
46
- ```
47
-
48
- 2. **Vitest** (if the app tests components that render core) — the package is compiled ESM with directory imports and must be processed by vite, in `vitest.config.ts`:
49
-
50
- ```ts
51
- test: {
52
- server: { deps: { inline: [/@robosystems\/core/] } },
53
- }
54
- ```
22
+ `react`, `react-dom`, `next`, `flowbite-react`, `react-icons`, and `@robosystems/client` are peer dependencies provided by the consuming app. See [CLAUDE.md](CLAUDE.md) for app wiring (Tailwind content scan, vitest inlining), packaging notes, and the release flow.
55
23
 
56
24
  ## Usage
57
25
 
58
- Everything is importable from the root barrel, or from subpaths mirroring the folder structure:
26
+ Import from the root barrel or from subpaths mirroring the folder structure:
59
27
 
60
- ```typescript
61
- import {
62
- AuthProvider,
63
- useAuth,
64
- useGraphContext,
65
- customTheme,
66
- } from '@robosystems/core'
28
+ ```tsx
29
+ import { AuthProvider, useGraphContext, customTheme } from '@robosystems/core'
67
30
  import { PageHeader, Spinner } from '@robosystems/core/ui-components'
68
31
  import { useToast } from '@robosystems/core/hooks/use-toast'
69
32
  ```
70
33
 
71
- > The root barrel has an import-time side effect: it configures the `@robosystems/client` SDK singleton (base URL from `NEXT_PUBLIC_ROBOSYSTEMS_API_URL`, Bearer-token request interceptor).
72
-
73
- ## Structure
74
-
75
- ```
76
- ├── actions/ # Next.js Server Actions
77
- │ ├── entity-actions.ts # Entity creation and management
78
- │ └── graph-actions.ts # Graph creation and lifecycle
79
- ├── auth-components/ # Authentication UI components
80
- │ ├── AppSwitcher.tsx # Cross-app navigation switcher
81
- │ ├── AuthGuard.tsx # Route protection wrapper
82
- │ ├── AuthProvider.tsx # Authentication context provider
83
- │ ├── SessionWarningDialog.tsx # Session expiry warning
84
- │ ├── SignInForm.tsx # Login form
85
- │ ├── SignUpForm.tsx # Registration form
86
- │ └── TurnstileWidget.tsx # Cloudflare Turnstile CAPTCHA
87
- ├── auth-core/ # Authentication logic and types
88
- │ ├── cleanup.ts # Session cleanup utilities
89
- │ ├── client.ts # Authentication client
90
- │ ├── config.ts # Auth configuration
91
- │ ├── hooks.ts # useAuth and related hooks
92
- │ ├── sso.ts # SSO/OAuth support
93
- │ ├── token-storage.ts # JWT storage utilities
94
- │ └── types.ts # Auth TypeScript types
95
- ├── components/ # Shared UI components
96
- │ ├── console/ # Terminal-style output display
97
- │ ├── repositories/ # Repository browsing and subscriptions
98
- │ ├── search/ # Search UI
99
- │ ├── EntitySelector.tsx # Entity picker component
100
- │ ├── GraphSelectorCore.tsx # Graph picker component
101
- │ ├── PageLayout.tsx # Standard page layout
102
- │ └── RepositoryGuard.tsx # Repository access protection
103
- ├── contexts/ # React contexts
104
- │ ├── entity-context.tsx # Active entity state
105
- │ ├── graph-context.tsx # Active graph state
106
- │ ├── org-context.tsx # Organization state
107
- │ ├── service-offerings-context.tsx # Available plans and tiers
108
- │ └── sidebar-context.tsx # Sidebar collapsed/expanded state
109
- ├── hooks/ # Custom React hooks
110
- │ ├── use-api-error.ts # API error normalization
111
- │ ├── use-media-query.ts # Responsive breakpoint detection
112
- │ ├── use-streaming-query.ts # SSE streaming data hook
113
- │ ├── use-toast.tsx # Toast notification hook
114
- │ ├── use-user-limits.ts # User quota and limit checks
115
- │ └── use-user.ts # Current user data hook
116
- ├── lib/ # Utility libraries
117
- │ ├── entity-cookie.ts # Entity selection persistence
118
- │ ├── graph-cookie.ts # Graph selection persistence
119
- │ ├── graph-tiers.ts # Tier display helpers
120
- │ └── sidebar-cookie.ts # Sidebar state persistence
121
- ├── library/ # XBRL taxonomy browser
122
- ├── research/ # Research coverage components
123
- ├── task-monitoring/ # Background job and operation tracking
124
- │ ├── hooks.ts # useTaskMonitoring, useEntityCreationTask
125
- │ ├── operationErrors.ts # Operation error types
126
- │ ├── operationHooks.ts # useOperationMonitoring, useGraphCreation
127
- │ ├── operationMonitor.ts # SSE-based operation monitor
128
- │ ├── operationTypes.ts # Shared operation types
129
- │ ├── taskMonitor.ts # Polling-based task monitor (fallback)
130
- │ └── types.ts # Task and operation TypeScript types
131
- ├── theme/ # UI theming
132
- │ └── flowbite-theme.ts # Flowbite React custom theme
133
- ├── types/ # Shared TypeScript definitions
134
- │ ├── entity.d.ts # Entity type definitions
135
- │ └── user.d.ts # User type definitions
136
- ├── ui-components/ # Reusable UI components
137
- │ ├── api-keys/ # API key management
138
- │ ├── chat/ # Chat UI (header, input, messages, deep research toggle)
139
- │ ├── forms/ # Form components and validation
140
- │ ├── layout/ # Navbar, sidebar, page containers
141
- │ ├── settings/ # Settings page components
142
- │ ├── support/ # Support modal
143
- │ ├── Logo.tsx # RoboSystems logo component
144
- │ └── Spinner.tsx # Loading spinner
145
- └── utils/ # Utility functions
146
- └── turnstile-config.ts # Cloudflare Turnstile configuration
147
- ```
148
-
149
- ## Technology Stack
150
-
151
- - **React 18/19** with modern hooks and patterns
152
- - **TypeScript** for type safety
153
- - **Flowbite React** for UI components
154
- - **Tailwind CSS** for styling (classes compiled by the consuming app)
155
- - **Next.js 15/16** App Router
156
- - **Auto-generated SDK** (`@robosystems/client`) from OpenAPI specifications
157
-
158
- ## Key Patterns
159
-
160
- ### Task Monitoring
161
-
162
- Two monitors handle async operations:
163
-
164
- - **`operationMonitor`** — SSE-based, used for graph lifecycle ops (create, materialize, etc.) that return `OperationEnvelope` with an `operationId`
165
- - **`taskMonitor`** — Polling-based fallback for older task-style operations
166
-
167
- ```typescript
168
- import { useOperationMonitoring, useGraphCreation } from '@robosystems/core/task-monitoring'
169
-
170
- // Monitor a graph operation via SSE
171
- const { startMonitoring, progress, result } = useOperationMonitoring()
172
- await startMonitoring(operationId)
173
-
174
- // Full graph creation with monitoring
175
- const { createGraph, isCreating } = useGraphCreation()
176
- await createGraph({ graph_type: 'entity', graph_name: 'Acme Corp', ... })
177
- ```
178
-
179
- ### Contexts
180
-
181
- ```typescript
182
- import { useGraphContext, useOrgContext } from '@robosystems/core/contexts'
183
-
184
- function MyComponent() {
185
- const { currentGraphId, setCurrentGraphId } = useGraphContext()
186
- const { org } = useOrgContext()
187
- }
188
- ```
189
-
190
- ### Authentication
191
-
192
- ```typescript
193
- import { useAuth, AuthProvider, AuthGuard } from '@robosystems/core/auth-core'
194
- import { SignInForm, SignUpForm } from '@robosystems/core/auth-components'
195
- ```
196
-
197
- ## Development
198
-
199
- ```bash
200
- npm install # Also wires .githooks via the prepare script
201
- npm run test # Vitest suite (jsdom, app-equivalent mocks in test/__mocks__)
202
- npm run test:all # format:check + lint + typecheck + test + build
203
- npm run build # tsc → dist/ + prepare-package.mjs (publishable package root)
204
- npm run pack:local # Build + npm pack ./dist → tarball for local app testing
205
- ```
206
-
207
- To test changes in an app before releasing:
34
+ ## Resources
208
35
 
209
- ```bash
210
- npm run pack:local
211
- cd ../roboledger-app && npm install ../robosystems-core/robosystems-core-<version>.tgz
212
- ```
213
-
214
- ### Packaging notes
215
-
216
- - The package is published **from `dist/`** so compiled files sit at the package root — the apps' directory-barrel and direct-file deep imports both resolve without an `exports` map.
217
- - The build is **per-file tsc** (not a bundler) so `'use client'` / `'use server'` directives survive at the top of each emitted file.
218
- - **No CommonJS**: the output is ESM — `require()` is not available at runtime. Use static or dynamic `import`.
219
-
220
- ### Adding New Components
221
-
222
- 1. Create component in the appropriate directory
223
- 2. Add TypeScript types in `types/` if needed
224
- 3. Export from the directory's `index.ts` (and the root `index.ts` if broadly useful)
225
- 4. Add tests in the adjacent `__tests__/` directory
226
- 5. Validate in an app with a `pack:local` tarball before releasing
36
+ - [RoboSystems Platform](https://robosystems.ai)
37
+ - [GitHub Repository](https://github.com/RoboFinSystems/robosystems-core)
38
+ - [API Documentation](https://api.robosystems.ai/docs)
227
39
 
228
- ### Naming Conventions
40
+ ## Support
229
41
 
230
- - **Components**: PascalCase (`SidebarProvider`)
231
- - **Hooks**: camelCase with `use` prefix (`useMediaQuery`)
232
- - **Types**: PascalCase (`SidebarCookie`)
233
- - **Utilities**: camelCase (`sidebarCookie`)
234
-
235
- ## Releasing
236
-
237
- Releases run through GitHub Actions (same pipeline as `@robosystems/report-components`):
238
-
239
- 1. Run the **Create Release & Publish** workflow (`gh workflow run create-release.yml --field version_type=patch|minor|major`, or `npm run release:create`)
240
- 2. It bumps the version on `main`, cuts a `release/<version>` branch, tags `v<version>`, and creates a GitHub Release with an AI-generated changelog
241
- 3. The `release/**` push triggers `publish.yml`, which builds and runs `npm publish ./dist --provenance` via npm OIDC Trusted Publishing
242
-
243
- ## Security
244
-
245
- - Never commit secrets or API keys
246
- - Use environment variables for configuration
247
- - Follow authentication best practices
248
- - Validate all inputs and API responses
42
+ - [Issues](https://github.com/RoboFinSystems/robosystems-core/issues)
43
+ - [Wiki](https://github.com/RoboFinSystems/robosystems/wiki)
44
+ - [Projects](https://github.com/orgs/RoboFinSystems/projects)
45
+ - [Discussions](https://github.com/orgs/RoboFinSystems/discussions)
249
46
 
250
47
  ## License
251
48
 
252
- MIT
49
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
50
+
51
+ MIT © 2026 RFS LLC
@@ -87,6 +87,9 @@ export function ConsoleContent({ config }) {
87
87
  .join('\n');
88
88
  const builtInCommands = ` /query - Execute a Cypher query\n` +
89
89
  ` /search - Search documents\n` +
90
+ (config.enableRecall
91
+ ? ` /recall - Recall semantic memories\n`
92
+ : '') +
90
93
  ` /mcp - Show MCP connection setup\n` +
91
94
  ` /help - Show this help message\n` +
92
95
  ` /clear - Clear console history\n` +
@@ -451,7 +454,7 @@ export function ConsoleContent({ config }) {
451
454
  }
452
455
  };
453
456
  const handleCommand = async (command) => {
454
- var _a;
457
+ var _a, _b;
455
458
  if (!command.trim())
456
459
  return;
457
460
  addUserMessage(command);
@@ -515,16 +518,70 @@ export function ConsoleContent({ config }) {
515
518
  addErrorMessage('Search failed. Please try again.');
516
519
  }
517
520
  }
518
- catch (_b) {
521
+ catch (_c) {
519
522
  addErrorMessage('An error occurred while searching.');
520
523
  }
521
524
  return;
522
525
  }
526
+ // Handle /recall command — built-in only when the target supports
527
+ // semantic memory (user graphs; shared repositories reject it). When
528
+ // disabled it falls through to the unknown-command branch below.
529
+ if (config.enableRecall && command.toLowerCase().startsWith('/recall')) {
530
+ const recallQuery = command.slice(7).trim();
531
+ if (!recallQuery) {
532
+ addErrorMessage('Usage: /recall <query>\n\nExamples:\n /recall payment terms for Acme\n /recall quarter close checklist');
533
+ return;
534
+ }
535
+ if (!graphId) {
536
+ addErrorMessage(config.noSelectionError);
537
+ return;
538
+ }
539
+ addSystemMessage(`Recalling memories for "${recallQuery}"...`);
540
+ try {
541
+ const res = await SDK.recallMemory({
542
+ path: { graph_id: graphId },
543
+ body: { query: recallQuery, k: 10 },
544
+ });
545
+ if (res.data) {
546
+ const data = res.data;
547
+ if (data.hits.length === 0) {
548
+ addSystemMessage(`No memories found for "${recallQuery}".`);
549
+ }
550
+ else {
551
+ const lines = data.hits.map((hit, idx) => {
552
+ var _a;
553
+ const tags = ((_a = hit.tags) === null || _a === void 0 ? void 0 : _a.length) ? ` [${hit.tags.join(', ')}]` : '';
554
+ const text = hit.snippet
555
+ ? `\n ${hit.snippet.slice(0, 150)}${hit.snippet.length > 150 ? '...' : ''}`
556
+ : '';
557
+ return ` ${idx + 1}. [${hit.score.toFixed(2)}]${tags}${text}`;
558
+ });
559
+ addSystemMessage(`Recalled ${data.total} memor${data.total === 1 ? 'y' : 'ies'} for "${recallQuery}" (showing ${data.hits.length}):\n\n${lines.join('\n\n')}`, true);
560
+ }
561
+ }
562
+ else {
563
+ const status = (_a = res.response) === null || _a === void 0 ? void 0 : _a.status;
564
+ if (status === 403) {
565
+ addErrorMessage('Memory recall is not available for shared repositories.');
566
+ }
567
+ else if (status === 404 || status === 503) {
568
+ addErrorMessage('Semantic memory is not enabled in this environment.');
569
+ }
570
+ else {
571
+ addErrorMessage('Recall failed. Please try again.');
572
+ }
573
+ }
574
+ }
575
+ catch (_d) {
576
+ addErrorMessage('An error occurred while recalling memories.');
577
+ }
578
+ return;
579
+ }
523
580
  // Handle slash commands
524
581
  if (command.startsWith('/')) {
525
582
  const cmd = command.toLowerCase().split(' ')[0];
526
583
  // Check extra commands first
527
- const extra = (_a = config.extraCommands) === null || _a === void 0 ? void 0 : _a.find((ec) => ec.command.toLowerCase() === cmd);
584
+ const extra = (_b = config.extraCommands) === null || _b === void 0 ? void 0 : _b.find((ec) => ec.command.toLowerCase() === cmd);
528
585
  if (extra) {
529
586
  await extra.handler({ addSystemMessage, addErrorMessage, graphId });
530
587
  return;
@@ -390,6 +390,8 @@ export function buildGraphAwareConsoleConfig(graph, branding) {
390
390
  sampleQueries: set.sampleQueries,
391
391
  examplesLabel: (_b = branding.examplesLabel) !== null && _b !== void 0 ? _b : 'Example Cypher Queries:',
392
392
  noSelectionError: (_c = branding.noSelectionError) !== null && _c !== void 0 ? _c : 'No graph selected. Please select a graph first.',
393
+ // Semantic memory is per-user-graph; shared repositories reject it.
394
+ enableRecall: !isRepository,
393
395
  };
394
396
  }
395
397
  /**
@@ -56,6 +56,10 @@ export interface ConsoleConfig {
56
56
  examplesLabel: string;
57
57
  /** Error message when no graph/portfolio is selected */
58
58
  noSelectionError: string;
59
+ /** Enable the built-in /recall semantic-memory command. Off by default —
60
+ * shared repositories don't support memory, so the graph-aware builder
61
+ * enables it only for user graphs. */
62
+ enableRecall?: boolean;
59
63
  /** Extra slash commands beyond the built-in set */
60
64
  extraCommands?: ConsoleExtraCommand[];
61
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robosystems/core",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Shared RoboSystems frontend core library (auth, contexts, task monitoring, UI components) for the RoboSystems Next.js apps",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -4,10 +4,11 @@ import type { CoverageItem } from './types';
4
4
  * "Listen" card right under the video), then the brief rendered from markdown (its own
5
5
  * leading H1 is stripped — we render the title above it), and the continuing-coverage
6
6
  * history. Works in a server component (SSR'd for SEO) or a client one.
7
- * Styled for a dark background render inside a dark (`bg-black`/`.dark`) container.
8
- * The prose styling mirrors the blog article body so research reads consistently with it
9
- * (explicit `prose-invert` + element overrides rather than `dark:prose-invert`, which the
10
- * Tailwind v4 + typography-plugin setup in these apps does not resolve from a `.dark` class).
7
+ * Theme-aware: readable in both light and dark. The prose body sets an explicit
8
+ * light + `dark:` color for every element it renders (headings, p, strong, em, links,
9
+ * lists, code, blockquote, hr, tables) rather than relying on `prose-invert` /
10
+ * `dark:prose-invert` the latter doesn't resolve from a `.dark` class in the
11
+ * Tailwind v4 + typography-plugin setup these apps use.
11
12
  */
12
13
  export declare function ResearchArticle({ item, briefMarkdown, }: {
13
14
  item: CoverageItem;
@@ -8,10 +8,11 @@ import { CoverageHistory } from './CoverageHistory';
8
8
  * "Listen" card right under the video), then the brief rendered from markdown (its own
9
9
  * leading H1 is stripped — we render the title above it), and the continuing-coverage
10
10
  * history. Works in a server component (SSR'd for SEO) or a client one.
11
- * Styled for a dark background render inside a dark (`bg-black`/`.dark`) container.
12
- * The prose styling mirrors the blog article body so research reads consistently with it
13
- * (explicit `prose-invert` + element overrides rather than `dark:prose-invert`, which the
14
- * Tailwind v4 + typography-plugin setup in these apps does not resolve from a `.dark` class).
11
+ * Theme-aware: readable in both light and dark. The prose body sets an explicit
12
+ * light + `dark:` color for every element it renders (headings, p, strong, em, links,
13
+ * lists, code, blockquote, hr, tables) rather than relying on `prose-invert` /
14
+ * `dark:prose-invert` the latter doesn't resolve from a `.dark` class in the
15
+ * Tailwind v4 + typography-plugin setup these apps use.
15
16
  */
16
17
  export function ResearchArticle({ item, briefMarkdown, }) {
17
18
  var _a;
@@ -20,7 +21,7 @@ export function ResearchArticle({ item, briefMarkdown, }) {
20
21
  const podcastYtId = youtubeId(item.podcast_youtube_url);
21
22
  return (_jsxs("article", { className: "mx-auto max-w-3xl", children: [_jsxs("div", { className: "mb-3 flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-400", children: [_jsxs("span", { className: "rounded bg-cyan-500/10 px-2 py-0.5 font-semibold text-cyan-600 dark:text-cyan-400", children: [item.company, " \u00B7 ", item.ticker] }), item.coverage_label && _jsx("span", { children: item.coverage_label }), _jsx("span", { children: (_a = item.date) === null || _a === void 0 ? void 0 : _a.slice(0, 10) })] }), _jsx("h1", { className: "mb-6 text-3xl font-bold text-gray-900 dark:text-white", children: item.title }), ytId ? (_jsx("div", { className: "mb-8 aspect-video w-full overflow-hidden rounded-xl bg-black", children: _jsx("iframe", { className: "h-full w-full", src: `https://www.youtube.com/embed/${ytId}`, title: item.title, loading: "lazy", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true }) })) : (item.assets.video && (
22
23
  // eslint-disable-next-line jsx-a11y/media-has-caption
23
- _jsx("video", { controls: true, poster: item.assets.thumbnail, src: item.assets.video, className: "mb-8 aspect-video w-full rounded-xl bg-black" }))), (item.assets.podcast_mp3 || podcastYtId) && (_jsx("section", { className: "mb-8", children: item.assets.podcast_mp3 ? (_jsxs("div", { className: "rounded-xl border border-cyan-500/30 bg-gray-900/50 p-4", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-3", children: [_jsx("p", { className: "text-sm font-semibold text-cyan-600 dark:text-cyan-400", children: "\uD83C\uDF99 Listen \u2014 Q&A podcast" }), podcastYtId && (_jsx("a", { href: item.podcast_youtube_url, target: "_blank", rel: "noopener noreferrer", className: "shrink-0 text-xs text-cyan-600 hover:underline dark:text-cyan-400", children: "Watch on YouTube \u2197" }))] }), _jsx("audio", { controls: true, preload: "none", src: item.assets.podcast_mp3, className: "w-full" })] })) : (
24
+ _jsx("video", { controls: true, poster: item.assets.thumbnail, src: item.assets.video, className: "mb-8 aspect-video w-full rounded-xl bg-black" }))), (item.assets.podcast_mp3 || podcastYtId) && (_jsx("section", { className: "mb-8", children: item.assets.podcast_mp3 ? (_jsxs("div", { className: "rounded-xl border border-cyan-500/30 bg-cyan-50/60 p-4 dark:bg-gray-900/50", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-3", children: [_jsx("p", { className: "text-sm font-semibold text-cyan-600 dark:text-cyan-400", children: "\uD83C\uDF99 Listen \u2014 Q&A podcast" }), podcastYtId && (_jsx("a", { href: item.podcast_youtube_url, target: "_blank", rel: "noopener noreferrer", className: "shrink-0 text-xs text-cyan-600 hover:underline dark:text-cyan-400", children: "Watch on YouTube \u2197" }))] }), _jsx("audio", { controls: true, preload: "none", src: item.assets.podcast_mp3, className: "w-full" })] })) : (
24
25
  // no MP3 yet — fall back to the YouTube video player
25
- _jsxs(_Fragment, { children: [_jsx("h2", { className: "mb-3 text-xl font-bold text-gray-900 dark:text-white", children: "Listen \u2014 Q&A podcast" }), _jsx("div", { className: "aspect-video w-full overflow-hidden rounded-xl bg-black", children: _jsx("iframe", { className: "h-full w-full", src: `https://www.youtube.com/embed/${podcastYtId}`, title: `${item.title} — Q&A podcast`, loading: "lazy", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true }) })] })) })), body && (_jsx("div", { className: "prose prose-lg prose-invert prose-headings:font-heading prose-headings:font-bold prose-headings:text-white prose-p:text-gray-300 prose-p:leading-relaxed prose-a:text-cyan-400 prose-a:no-underline hover:prose-a:text-cyan-300 prose-strong:text-white prose-strong:font-semibold prose-code:text-cyan-400 prose-code:bg-gray-800 prose-code:px-2 prose-code:py-1 prose-code:rounded prose-pre:bg-gray-900 prose-pre:border prose-pre:border-gray-800 prose-blockquote:border-l-cyan-500 prose-blockquote:text-gray-400 prose-blockquote:italic prose-ul:text-gray-300 prose-ol:text-gray-300 prose-li:marker:text-cyan-500 prose-table:border-gray-700 prose-th:bg-gray-900 prose-th:text-white prose-td:text-gray-300 max-w-none", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: body }) })), _jsx(CoverageHistory, { history: item.history }), _jsxs("footer", { className: "mt-12 border-t border-gray-200 pt-6 dark:border-gray-800", children: [_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Audio & music produced with ElevenLabs." }), _jsxs("a", { href: "https://elevenlabs.io/startup-grants", target: "_blank", rel: "noopener noreferrer", "aria-label": "Backed by the ElevenLabs Grants program", className: "mt-3 inline-block", children: [_jsx("img", { src: "/images/logos/elevenlabs-grants.webp", alt: "Backed by the ElevenLabs Grants program", width: 200, className: "h-auto dark:hidden" }), _jsx("img", { src: "/images/logos/elevenlabs-grants-white.webp", alt: "Backed by the ElevenLabs Grants program", width: 200, className: "hidden h-auto dark:block" })] }), _jsxs("p", { className: "mt-2 text-xs text-gray-400 dark:text-gray-500", children: ["Using ElevenLabs yourself? Our", ' ', _jsx("a", { href: "https://try.elevenlabs.io/v9z3wzm97gk3", target: "_blank", rel: "noopener noreferrer", className: "hover:underline", children: "referral link" }), ' ', "costs you nothing extra and supports this research."] })] })] }));
26
+ _jsxs(_Fragment, { children: [_jsx("h2", { className: "mb-3 text-xl font-bold text-gray-900 dark:text-white", children: "Listen \u2014 Q&A podcast" }), _jsx("div", { className: "aspect-video w-full overflow-hidden rounded-xl bg-black", children: _jsx("iframe", { className: "h-full w-full", src: `https://www.youtube.com/embed/${podcastYtId}`, title: `${item.title} — Q&A podcast`, loading: "lazy", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true }) })] })) })), body && (_jsx("div", { className: "prose prose-lg prose-headings:font-heading prose-headings:font-bold prose-headings:text-gray-900 dark:prose-headings:text-white prose-p:text-gray-700 dark:prose-p:text-gray-300 prose-p:leading-relaxed prose-a:text-cyan-600 dark:prose-a:text-cyan-400 prose-a:no-underline hover:prose-a:text-cyan-500 dark:hover:prose-a:text-cyan-300 prose-strong:text-gray-900 dark:prose-strong:text-white prose-strong:font-semibold prose-em:text-gray-700 dark:prose-em:text-gray-300 prose-code:text-cyan-700 dark:prose-code:text-cyan-400 prose-code:bg-gray-100 dark:prose-code:bg-gray-800 prose-code:px-2 prose-code:py-1 prose-code:rounded prose-pre:bg-gray-900 prose-pre:border prose-pre:border-gray-800 prose-blockquote:border-l-cyan-500 prose-blockquote:text-gray-600 dark:prose-blockquote:text-gray-400 prose-blockquote:italic prose-ul:text-gray-700 dark:prose-ul:text-gray-300 prose-ol:text-gray-700 dark:prose-ol:text-gray-300 prose-li:marker:text-cyan-500 prose-hr:border-gray-200 dark:prose-hr:border-gray-800 prose-table:border-gray-300 dark:prose-table:border-gray-700 prose-th:bg-gray-100 dark:prose-th:bg-gray-900 prose-th:text-gray-900 dark:prose-th:text-white prose-td:text-gray-700 dark:prose-td:text-gray-300 max-w-none", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: body }) })), _jsx(CoverageHistory, { history: item.history }), _jsxs("footer", { className: "mt-12 border-t border-gray-200 pt-6 dark:border-gray-800", children: [_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Audio & music produced with ElevenLabs." }), _jsxs("a", { href: "https://elevenlabs.io/startup-grants", target: "_blank", rel: "noopener noreferrer", "aria-label": "Backed by the ElevenLabs Grants program", className: "mt-3 inline-block", children: [_jsx("img", { src: "/images/logos/elevenlabs-grants.webp", alt: "Backed by the ElevenLabs Grants program", width: 200, className: "h-auto dark:hidden" }), _jsx("img", { src: "/images/logos/elevenlabs-grants-white.webp", alt: "Backed by the ElevenLabs Grants program", width: 200, className: "hidden h-auto dark:block" })] }), _jsxs("p", { className: "mt-2 text-xs text-gray-400 dark:text-gray-500", children: ["Using ElevenLabs yourself? Our", ' ', _jsx("a", { href: "https://try.elevenlabs.io/v9z3wzm97gk3", target: "_blank", rel: "noopener noreferrer", className: "hover:underline", children: "referral link" }), ' ', "costs you nothing extra and supports this research."] })] })] }));
26
27
  }