Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
06401b1
feat: media library plugin backend
olliethedev Mar 18, 2026
7b7b576
feat: add media API and AWS SDK dependencies
olliethedev Mar 18, 2026
f88d14f
fix: improve error handling for clientPayload in media backend plugin
olliethedev Mar 18, 2026
3f1c02a
refactor: optimize S3 client initialization and error handling in med…
olliethedev Mar 18, 2026
852ce18
feat: enhance folder deletion logic to cascade delete child folders a…
olliethedev Mar 18, 2026
662018a
feat: add folder management hooks and URL prefix validation to media …
olliethedev Mar 18, 2026
60585c9
feat: implement folderId management in asset updates and enhance erro…
olliethedev Mar 18, 2026
d31f6ca
feat: add getFolderById method to media backend plugin for improved f…
olliethedev Mar 18, 2026
af9d3c8
feat: enhance folder validation in media backend plugin to ensure fol…
olliethedev Mar 18, 2026
37ae5aa
feat: enhance URL validation in media backend plugin to support autom…
olliethedev Mar 18, 2026
cdb3556
refactor: streamline S3 signed URL retrieval in media adapter with im…
olliethedev Mar 18, 2026
6888241
test: enhance AI chat plugin tests with unique run ID for conversatio…
olliethedev Mar 18, 2026
6ff8640
refactor: improve S3 client and module loading with enhanced error ha…
olliethedev Mar 18, 2026
0462352
feat: add URL prefix matching function and enhance validation logic f…
olliethedev Mar 18, 2026
9cd5c7a
test: add unit tests for media asset list query keys to validate pagi…
olliethedev Mar 18, 2026
df4c3ec
test: expand mediaBackendPlugin tests with direct upload functionalit…
olliethedev Mar 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions e2e/tests/smoke.chat.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,21 +144,29 @@ test.describe("AI Chat Plugin", () => {
}) => {
// This test verifies that multiple messages in a conversation stay together
// and don't create separate history items (fixes the bug where every message
// created a new conversation)
// created a new conversation).
//
// A unique run ID is embedded in the first message so that conversations
// created by previous retry attempts don't bleed into the sidebar count
// assertion. The in-memory adapter persists state across Playwright retries
// within the same server process, so a fixed message text would cause
// multiple conversations with the same title to accumulate.
const runId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
const firstMessage = `First msg in conv ${runId}`;

await page.goto("/pages/chat");

// Send first message
const input = page.getByPlaceholder("Type a message...");
await input.fill("First message in conversation");
await input.fill(firstMessage);
await page.keyboard.press("Enter");

// Wait for first AI response
await expect(
page
.locator('[data-testid="chat-interface"]')
.locator('[aria-label="AI response"]'),
).toBeVisible({ timeout: 30000 });
).toBeVisible({ timeout: 45000 });

// Wait for navigation to new conversation URL
await page.waitForURL(/\/pages\/chat\/[a-zA-Z0-9]+/, { timeout: 10000 });
Expand All @@ -179,21 +187,19 @@ test.describe("AI Chat Plugin", () => {

// Both messages should still be visible in the same conversation
await expect(
page
.locator('[data-testid="chat-interface"]')
.getByText("First message in conversation"),
page.locator('[data-testid="chat-interface"]').getByText(firstMessage),
).toBeVisible({ timeout: 10000 });
await expect(
page
.locator('[data-testid="chat-interface"]')
.getByText("Second message in same conversation"),
).toBeVisible({ timeout: 10000 });

// There should only be ONE conversation in the sidebar with "First message"
// (the title is based on the first message)
const sidebarConversations = page.locator(
'button:has-text("First message in conversation")',
);
// There should be exactly ONE conversation in the sidebar matching this
// run's unique first message. Searching by the unique runId ensures
// conversations from previous retry attempts (which used a different runId)
// are not counted.
const sidebarConversations = page.locator(`button:has-text("${runId}")`);
await expect(sidebarConversations).toHaveCount(1, { timeout: 5000 });
});

Expand Down
7 changes: 7 additions & 0 deletions packages/stack/build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ export default defineBuildConfig({
"react/jsx-runtime",
"@tanstack/react-query",
"sonner",
// optional peerDependencies (media plugin)
"@vercel/blob",
"@vercel/blob/server",
"@aws-sdk/client-s3",
"@aws-sdk/s3-request-presigner",
// test/build-time deps kept external
"vitest",
"@vitest/runner",
Expand Down Expand Up @@ -110,6 +115,8 @@ export default defineBuildConfig({
"./src/plugins/comments/client/components/index.tsx",
"./src/plugins/comments/client/hooks/index.tsx",
"./src/plugins/comments/query-keys.ts",
// media plugin entries
"./src/plugins/media/api/index.ts",
"./src/components/auto-form/index.ts",
"./src/components/stepped-auto-form/index.ts",
"./src/components/multi-select/index.ts",
Expand Down
6 changes: 5 additions & 1 deletion packages/stack/knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"src/plugins/kanban/client/components/index.tsx",
"src/plugins/kanban/client/hooks/index.tsx",
"src/plugins/kanban/query-keys.ts",
"src/plugins/media/api/index.ts",
"build.config.ts",
"vitest.config.mts",
"scripts/build-registry.ts",
Expand All @@ -63,6 +64,9 @@
"remark-gfm",
"remark-math",
"tailwindcss",
"@tailwindcss/typography"
"@tailwindcss/typography",
"@aws-sdk/client-s3",
"@aws-sdk/s3-request-presigner",
"@vercel/blob"
]
}
36 changes: 33 additions & 3 deletions packages/stack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,16 @@
}
},
"./plugins/comments/css": "./dist/plugins/comments/style.css",
"./plugins/media/api": {
"import": {
"types": "./dist/plugins/media/api/index.d.ts",
"default": "./dist/plugins/media/api/index.mjs"
},
"require": {
"types": "./dist/plugins/media/api/index.d.cts",
"default": "./dist/plugins/media/api/index.cjs"
}
},
"./plugins/route-docs/client": {
"import": {
"types": "./dist/plugins/route-docs/client/index.d.ts",
Expand Down Expand Up @@ -610,6 +620,9 @@
"plugins/comments/query-keys": [
"./dist/plugins/comments/query-keys.d.ts"
],
"plugins/media/api": [
"./dist/plugins/media/api/index.d.ts"
],
"plugins/route-docs/client": [
"./dist/plugins/route-docs/client/index.d.ts"
],
Expand Down Expand Up @@ -646,13 +659,17 @@
},
"peerDependencies": {
"@ai-sdk/react": ">=2.0.0",
"@aws-sdk/client-s3": ">=3.0.0",
"@aws-sdk/s3-request-presigner": ">=3.0.0",
"@btst/yar": ">=1.2.0",
"@hookform/resolvers": ">=5.0.0",
"@radix-ui/react-dialog": ">=1.1.0",
"@radix-ui/react-label": ">=2.1.0",
"@radix-ui/react-slot": ">=1.1.0",
"@radix-ui/react-switch": ">=1.1.0",
"@tailwindcss/typography": ">=0.5.0",
"@tanstack/react-query": "^5.0.0",
"@vercel/blob": ">=0.14.0",
"ai": ">=5.0.0",
"better-call": ">=1.3.2",
"class-variance-authority": ">=0.7.0",
Expand All @@ -675,25 +692,38 @@
"sonner": ">=2.0.0",
"tailwind-merge": ">=2.6.0",
"tailwindcss": ">=3.0.0",
"@tailwindcss/typography": ">=0.5.0",
"zod": ">=4.2.0"
},
"peerDependenciesMeta": {
"@vercel/blob": {
"optional": true
},
"@aws-sdk/client-s3": {
"optional": true
},
"@aws-sdk/s3-request-presigner": {
"optional": true
}
},
"devDependencies": {
"tsx": "catalog:",
"@ai-sdk/react": "^2.0.94",
"@aws-sdk/client-s3": "^3.1011.0",
"@aws-sdk/s3-request-presigner": "^3.1011.0",
"@btst/adapter-memory": "2.1.1",
"@btst/yar": "1.2.0",
"@types/react": "^19.0.0",
"@types/slug": "^5.0.9",
"@vercel/blob": "^0.27.3",
"@workspace/ui": "workspace:*",
"ai": "^5.0.94",
"better-call": "catalog:",
"knip": "^5.61.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-error-boundary": "^4.1.2",
"knip": "^5.61.2",
"rollup-plugin-preserve-directives": "0.4.0",
"rollup-plugin-visualizer": "^5.12.0",
"tsx": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"vitest": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Stub for @vercel/blob/server — used in tests only.
* The real module is an optional peer dependency.
*/
export async function handleUpload(_options: unknown): Promise<unknown> {
throw new Error(
"handleUpload is not available in the installed @vercel/blob version. Use a version that exports @vercel/blob/server.",
);
}
Loading
Loading