Skip to content

Presentation gen - #4

Open
xoloAyush wants to merge 6 commits into
mainfrom
presentation_Gen
Open

xoloAyush wants to merge 6 commits into
mainfrom
presentation_Gen

Conversation

@xoloAyush

@xoloAyush xoloAyush commented Jun 29, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added presentation creation and management, including saving, editing, deleting, and regenerating presentations.
    • Added a presentation detail page with slide navigation, generation status, and a confirmation dialog for deletions.
    • Added support for presentation-related API actions and background job wiring.
    • Added new presentation data storage with slide records and status tracking.
  • Bug Fixes

    • Improved auth handling so protected presentation pages redirect unauthenticated users to login.
    • Updated the home flow to create a presentation before navigating to its detail view.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: af5903c7-8b00-4d4f-b732-8f410b6363ea

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an end-to-end presentation feature: Prisma schema with Presentation/Slide models and PresentationStatus enum, Zod validation schemas, server-side CRUD queries and mutations with auth ownership checks, a usePresentationDetail React Query hook with polling, a full /presentation/$presentationId detail page, AlertDialog and GenerationStatus UI components, home page mutation wiring, and an Inngest client/function/API route scaffold.

Changes

Presentation Feature End-to-End

Layer / File(s) Summary
Database schema and migration
prisma/schema.prisma, prisma/migrations/.../migration.sql, prisma.config.ts
Adds Presentation and Slide models, PresentationStatus enum, cascade FK constraints, and the migration SQL. Prisma config url is cast to string.
Zod schemas and server helpers
src/features/presentations/types/schema.ts, src/features/presentations/lib/server-helpers.ts, src/features/presentations/utils/thumbnail-url.ts, src/features/presentations/constants/presentation-options.ts
Adds Zod enum/object schemas for style, tone, layout, create, and update inputs. Adds deriveTitle and requirePresentationUserId server helpers. Adds Dicebear thumbnail URL utility.
Server queries and mutations
src/features/presentations/api/presentation-queries.ts, src/features/presentations/actions/presentation-mutations.ts, src/features/presentations/hooks/query-keys.ts
Implements listPresentations, getPresentation, getPresentationWithSlides GET functions and createPresentation, updatePresentation, deletePresentation, regeneratePresentation POST mutations with auth ownership checks. Inngest dispatch is commented out.
Auth function middleware
src/middleware/auth.ts
Adds authFnMiddleware that checks session from request headers and redirects to login when unauthenticated.
usePresentationDetail hook
src/features/presentations/hooks/usePresentation-detail.ts
React Query hook with 3-second polling while GENERATING, local SettingsForm state synced from query data, and update/regenerate/delete mutations with toast feedback and cache invalidation.
AlertDialog and GenerationStatus components
src/components/ui/alert-dialog.tsx, src/features/presentations/components/generation-status.tsx
Adds a full Radix AlertDialog wrapper set with size prop and Button integration. Adds GenerationStatus mapping each status to an icon and label.
Presentation detail route
src/routes/presentation.$presentationId.tsx
Adds /presentation/$presentationId with beforeLoad auth guard. Renders header with thumbnail/status, settings panel with AlertDialog-guarded delete, slide navigation, empty/generating states, and a slides sidebar stub.
Home page creation mutation
src/routes/index.tsx
Wires useMutation calling createPresentation on the home route, navigates to the new presentation on success, and disables the generate button while pending or prompt is empty.
Inngest integration
src/integrations/inngest/client.ts, src/integrations/inngest/functions.ts, src/routes/api/inngest.ts, package.json
Adds the inngest package, an Inngest client (id: "my-app"), a helloWorld example function, and the /api/inngest file route forwarding GET/POST/PUT to the Edge handler.
Config and temp files
.gitignore, .tanstack/tmp/...
Reduces .gitignore to minimal entries and adds src/routeTree.gen.ts to it. Includes a temporary /_auth route file.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • xoloAyush/AI_ppt_v2#1: Modifies the same src/routes/index.tsx home page form/generate flow that this PR replaces with mutation-driven creation.
  • xoloAyush/AI_ppt_v2#3: Directly overlaps with the same modules: presentation-mutations.ts, server-helpers.ts, schema.ts, inngest.ts route, and auth.ts middleware.

Poem

🐇 Hop hop, a schema grows,
Slides and presentations in rows!
The rabbit typed mutations galore,
Auth guards at every door.
Inngest sleeps, the status glows—
GENERATING until the carrot shows! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too vague to clearly describe the main change set. Use a specific title like "Add presentation generation flow" or "Introduce presentation models and routes".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch presentation_Gen

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.tanstack/tmp/2ab4fcb8-66ad50854acd56d5165e5eb0a371b193 (1)

1-13: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove generated temp file from version control.

This file in .tanstack/tmp/ is an auto-generated artifact by TanStack Router's file-based routing. It should not be committed to git. The .gitignore reduction in this PR appears to have removed the pattern that previously ignored such generated files.

Add .tanstack/tmp/ or similar to .gitignore and remove this file from the PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.tanstack/tmp/2ab4fcb8-66ad50854acd56d5165e5eb0a371b193 around lines 1 - 13,
This is an auto-generated TanStack Router artifact under .tanstack/tmp and
should not be versioned. Restore the ignore rule in .gitignore for
.tanstack/tmp/ (or an equivalent pattern) so generated routing files stay out of
git, and remove this specific temporary file from the commit/PR. Use the
existing route generation flow around createFileRoute and Route as the reference
when locating the artifact.
🧹 Nitpick comments (1)
src/routes/presentation.$presentationId.tsx (1)

63-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse AUTH_LOGIN_PATH here instead of hardcoding /login.

The middleware already redirects via the shared constant from src/lib/auth-path.ts, so keeping this route on a string literal makes the auth flow drift the next time that path changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/presentation`.$presentationId.tsx around lines 63 - 66, The
redirect in the presentation route is hardcoding the login path instead of using
the shared auth constant. Update the `session` guard in the presentation route
to reference `AUTH_LOGIN_PATH` from `src/lib/auth-path.ts` rather than the
`/login` string literal, matching the existing auth middleware behavior and
keeping the redirect path centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prisma.config.ts`:
- Line 12: The Prisma config is relying on a type assertion for DATABASE_URL,
which can still allow undefined at runtime and defer the failure to Prisma.
Update the database URL assignment in prisma.config.ts to use
env("DATABASE_URL") or add an explicit guard before constructing the config so
the process fails immediately with a clear error when the environment variable
is missing.

In `@prisma/migrations/20260627170407_presentation_slides/migration.sql`:
- Around line 22-41: The slide schema currently allows duplicate sequence
positions within the same presentation, so update the slide definition to
enforce uniqueness on the presentationId and order pair. Add the composite
unique constraint in the slide model in prisma/schema.prisma and mirror it in
the migration SQL for the slide table, using the existing slide, presentationId,
and order symbols to locate the change.

In `@prisma/schema.prisma`:
- Around line 57-67: The slide ordering in the schema is not constrained per
presentation, so duplicate values can make the sequence nondeterministic. Update
the Slide model in prisma/schema.prisma to enforce a unique ordering within each
presentation by adding a composite uniqueness constraint on presentationId and
order, and keep the existing getPresentationWithSlides ordering logic unchanged
since it will then rely on a stable contract.

In `@src/features/presentations/actions/presentation-mutations.ts`:
- Around line 26-33: The presentation mutation flow is leaving items stuck in
GENERATING because the generation job is never dispatched after the status
update. In the relevant mutation handlers in presentation-mutations.ts, restore
the queued generation step by re-enabling the inngest.send call (or otherwise
dispatching the generation work) for both the create and regenerate paths so the
status transition is followed by actual processing.

In `@src/features/presentations/api/presentation-queries.ts`:
- Around line 18-27: The getPresentation server function currently throws a
generic Error for missing records, which bypasses the router’s not-found
handling. Update the getPresentation handler to call notFound() for every
missing-record path, including stale/deleted IDs after the
prisma.presentation.findFirst lookup, and keep the user lookup via
requirePresentationUserId intact. Also make sure the presentation detail fetch
is performed through useServerFn so the client-side route can receive and react
to the not-found state correctly.

In `@src/features/presentations/hooks/usePresentation-detail.ts`:
- Around line 47-57: The sync in usePresentationDetail is overwriting local
edits whenever query.data changes, which wipes dirty form state on refetch.
Update the useEffect that calls setForm so it only seeds the form once per
presentation or only when the form is not dirty/has not been initialized, and
use a guard keyed off the current presentation/query identity to avoid resetting
user edits during polling or invalidation refetches.

In `@src/features/presentations/lib/server-helpers.ts`:
- Around line 15-19: In requirePresentationUserId, the missing-session path
currently throws a generic Error and gets surfaced as a 500; change this auth
failure to use the auth middleware flow or explicitly set a 401 response status
before failing so presentation queries/mutations return an auth response instead
of an internal server error. Keep the fix localized to requirePresentationUserId
and preserve the existing successful return of session.user.id.

In `@src/features/presentations/types/schema.ts`:
- Line 27: The schema validation for prompt/title in the presentation types
still allows whitespace-only values because z.string().min(1) is checked before
trimming; update the prompt and title validators in schema.ts so they trim input
before enforcing min/max length. Apply the same fix to both the create/update
variants referenced by the prompt and title fields, using the existing schema
definitions in that file to ensure blank-looking strings are rejected
consistently.

In `@src/integrations/inngest/functions.ts`:
- Around line 5-7: The handler in the inngest function assumes event.data.email
is always present, which can cause a crash or return an invalid greeting for
malformed events. Update the async handler for the test/hello.world event to
validate the event payload before using event.data.email, and handle
missing/invalid data with an explicit fallback or failure path instead of
dereferencing it directly. Keep the fix localized to the function body that
calls step.sleep and builds the Hello message.

In `@src/routes/index.tsx`:
- Around line 81-99: The create flow in createMut/onSuccess assumes generation
has already started, but createPresentation currently only inserts the
presentation row and returns it. Update the server action in
presentation-mutations to actually kick off the generation job by restoring the
inngest.send(...) call (or equivalent job dispatch) inside createPresentation so
the returned GENERATING record is backed by a running job. Keep the route logic
in src/routes/index.tsx as the success handler only after the mutation truly
starts generation.

In `@src/routes/presentation`.$presentationId.tsx:
- Around line 191-213: The presentation route still exposes slide-related
controls without any working slide-viewing behavior. In
presentation.$presentationId.tsx, either restore the missing slide flow by
wiring back SlidePreview, the sidebar slide list, the slideshow modal, and the
export action handlers, or remove/hide the Button controls and related UI until
they are functional. Make sure the JSX branches around slides.length,
setShowSlideshow, and the export button are consistent so users never see empty
preview areas or non-working controls.
- Around line 147-149: The detail view can lose its preview when the slide list
shrinks because presentationThumbnailUrl, activeSlideIndex, and
slides.at(activeSlideIndex) still assume the old index is valid. In
src/routes/presentation.$presentationId.tsx, update the logic around
activeSlideIndex so it is clamped or reset whenever slides.length changes, and
ensure the selected slide is always derived from a valid index before rendering
the preview.

---

Outside diff comments:
In @.tanstack/tmp/2ab4fcb8-66ad50854acd56d5165e5eb0a371b193:
- Around line 1-13: This is an auto-generated TanStack Router artifact under
.tanstack/tmp and should not be versioned. Restore the ignore rule in .gitignore
for .tanstack/tmp/ (or an equivalent pattern) so generated routing files stay
out of git, and remove this specific temporary file from the commit/PR. Use the
existing route generation flow around createFileRoute and Route as the reference
when locating the artifact.

---

Nitpick comments:
In `@src/routes/presentation`.$presentationId.tsx:
- Around line 63-66: The redirect in the presentation route is hardcoding the
login path instead of using the shared auth constant. Update the `session` guard
in the presentation route to reference `AUTH_LOGIN_PATH` from
`src/lib/auth-path.ts` rather than the `/login` string literal, matching the
existing auth middleware behavior and keeping the redirect path centralized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cc5db608-56d9-4893-9c99-248b350d3310

📥 Commits

Reviewing files that changed from the base of the PR and between 91279e5 and 57d6f67.

⛔ Files ignored due to path filters (16)
  • generated/prisma/browser.ts is excluded by !**/generated/**
  • generated/prisma/client.ts is excluded by !**/generated/**
  • generated/prisma/commonInputTypes.ts is excluded by !**/generated/**
  • generated/prisma/enums.ts is excluded by !**/generated/**
  • generated/prisma/internal/class.ts is excluded by !**/generated/**
  • generated/prisma/internal/prismaNamespace.ts is excluded by !**/generated/**
  • generated/prisma/internal/prismaNamespaceBrowser.ts is excluded by !**/generated/**
  • generated/prisma/models.ts is excluded by !**/generated/**
  • generated/prisma/models/Account.ts is excluded by !**/generated/**
  • generated/prisma/models/Presentation.ts is excluded by !**/generated/**
  • generated/prisma/models/Session.ts is excluded by !**/generated/**
  • generated/prisma/models/Slide.ts is excluded by !**/generated/**
  • generated/prisma/models/User.ts is excluded by !**/generated/**
  • generated/prisma/models/Verification.ts is excluded by !**/generated/**
  • generated/prisma/query_engine-windows.dll.node is excluded by !**/generated/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (23)
  • .gitignore
  • .tanstack/tmp/2ab4fcb8-66ad50854acd56d5165e5eb0a371b193
  • package.json
  • prisma.config.ts
  • prisma/migrations/20260627170407_presentation_slides/migration.sql
  • prisma/schema.prisma
  • src/components/ui/alert-dialog.tsx
  • src/features/presentations/actions/presentation-mutations.ts
  • src/features/presentations/api/presentation-queries.ts
  • src/features/presentations/components/generation-status.tsx
  • src/features/presentations/constants/presentation-options.ts
  • src/features/presentations/hooks/query-keys.ts
  • src/features/presentations/hooks/usePresentation-detail.ts
  • src/features/presentations/lib/server-helpers.ts
  • src/features/presentations/types/schema.ts
  • src/features/presentations/utils/thumbnail-url.ts
  • src/integrations/inngest/client.ts
  • src/integrations/inngest/functions.ts
  • src/middleware/auth.ts
  • src/routeTree.gen.ts
  • src/routes/api/inngest.ts
  • src/routes/index.tsx
  • src/routes/presentation.$presentationId.tsx
💤 Files with no reviewable changes (1)
  • src/routeTree.gen.ts

Comment thread prisma.config.ts
},
datasource: {
url: process.env["DATABASE_URL"],
url: process.env["DATABASE_URL"] as string,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and inspect the relevant area with line numbers.
git ls-files | rg '^prisma\.config\.ts$|prisma\.config\.ts$'
wc -l prisma.config.ts
cat -n prisma.config.ts | sed -n '1,120p'

# Search for any existing env validation pattern used in the repo.
rg -n "DATABASE_URL|throw new Error\\(|process\\.env\\[" -S . --glob '!node_modules' --glob '!dist' --glob '!build'

Repository: xoloAyush/AI_ppt_v2

Length of output: 2012


🌐 Web query:

Prisma config defineConfig datasource url process.env DATABASE_URL missing runtime error fail fast

💡 Result:

The Prisma env() helper function in prisma.config.ts is designed to fail fast when an environment variable is missing [1][2]. This behavior ensures that configuration errors are caught immediately rather than causing unpredictable runtime failures [3]. If your environment variable (e.g., DATABASE_URL) is not required for all Prisma commands—such as when running prisma generate in CI/CD pipelines where a database connection is not needed—this strict behavior will cause those commands to fail [1][4]. To resolve this and avoid the fail-fast behavior, you should replace the env() helper with a direct process.env access [1][2]: // Change this: datasource: { url: env("DATABASE_URL"), }, // To this: datasource: { url: process.env.DATABASE_URL, }, Alternatively, you can provide a fallback value using the nullish coalescing operator if you want to avoid undefined values while still preventing an error: datasource: { url: process.env.DATABASE_URL?? "", }, Additionally, ensure that you have initialized your environment variables correctly. Since Prisma Config does not automatically load .env files for you, you must manually import a loader like dotenv at the top of your prisma.config.ts file if you rely on a .env file for your configuration [5][6]: import "dotenv/config"; import { defineConfig } from "prisma/config"; export default defineConfig({ //... }); By using process.env instead of env(), you maintain flexibility for commands that do not require an active database connection [1][7].

Citations:


🌐 Web query:

Prisma defineConfig datasource.url process.env.DATABASE_URL as string generated prisma.config.ts template

💡 Result:

To configure the datasource URL in a prisma.config.ts file, you should use the defineConfig helper from prisma/config along with the env helper [1][2]. You must also ensure that environment variables are loaded by importing dotenv/config at the top of your configuration file [1][3]. Below is a template for a prisma.config.ts file: import "dotenv/config"; import { defineConfig, env } from "prisma/config"; export default defineConfig({ schema: "prisma/schema.prisma", datasource: { // The env helper ensures the variable is present and provides better type safety [1][3] url: env("DATABASE_URL"), }, }); Key Considerations: 1. Environment Variable Loading: Importing "dotenv/config" at the top of the file is required to load variables from your.env file into process.env so that the env helper can access them [1][3]. 2. Handling Optional Variables: If the environment variable is not guaranteed to exist (for example, in certain CI/CD environments where you only need to run type-checking), you should use process.env directly rather than the env helper [1][4]. // Alternative approach for optional environment variables import "dotenv/config"; import { defineConfig } from "prisma/config"; export default defineConfig({ schema: "prisma/schema.prisma", datasource: { // Uses process.env directly with a fallback or non-null assertion [1][4] url: process.env.DATABASE_URL?? "", }, }); 3. Prisma Version Compatibility: Note that the ability to configure the datasource URL in prisma.config.ts is a feature introduced in later versions of Prisma ORM (notably becoming standard in v7 for configuring schema location and datasource settings) [4][5]. If you are using an earlier version, these settings remain in the datasource block of your schema.prisma file [4].

Citations:


Fail fast if DATABASE_URL is missing.

as string only suppresses TypeScript’s check; at runtime it can still pass undefined, so Prisma will fail later with a less clear config error. Use env("DATABASE_URL") or an explicit guard here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prisma.config.ts` at line 12, The Prisma config is relying on a type
assertion for DATABASE_URL, which can still allow undefined at runtime and defer
the failure to Prisma. Update the database URL assignment in prisma.config.ts to
use env("DATABASE_URL") or add an explicit guard before constructing the config
so the process fails immediately with a clear error when the environment
variable is missing.

Comment on lines +22 to +41
CREATE TABLE "slide" (
"id" TEXT NOT NULL,
"presentationId" TEXT NOT NULL,
"order" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"notes" TEXT,
"imageUrl" TEXT,
"imagePrompt" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "slide_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "presentation_userId_idx" ON "presentation"("userId");

-- CreateIndex
CREATE INDEX "slide_presentationId_idx" ON "slide"("presentationId");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Enforce unique slide positions per presentation.

Every read path treats order as the slide sequence, but this schema still allows duplicate ("presentationId", "order") pairs. That makes the orderBy: { order: 'asc' } contract nondeterministic and opens the door to conflicting slide writes. Add a composite unique constraint here (and the matching @@unique in prisma/schema.prisma).

Suggested migration change
 -- CreateIndex
 CREATE INDEX "slide_presentationId_idx" ON "slide"("presentationId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "slide_presentationId_order_key" ON "slide"("presentationId", "order");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CREATE TABLE "slide" (
"id" TEXT NOT NULL,
"presentationId" TEXT NOT NULL,
"order" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"notes" TEXT,
"imageUrl" TEXT,
"imagePrompt" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "slide_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "presentation_userId_idx" ON "presentation"("userId");
-- CreateIndex
CREATE INDEX "slide_presentationId_idx" ON "slide"("presentationId");
CREATE TABLE "slide" (
"id" TEXT NOT NULL,
"presentationId" TEXT NOT NULL,
"order" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"notes" TEXT,
"imageUrl" TEXT,
"imagePrompt" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "slide_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "presentation_userId_idx" ON "presentation"("userId");
-- CreateIndex
CREATE INDEX "slide_presentationId_idx" ON "slide"("presentationId");
-- CreateIndex
CREATE UNIQUE INDEX "slide_presentationId_order_key" ON "slide"("presentationId", "order");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prisma/migrations/20260627170407_presentation_slides/migration.sql` around
lines 22 - 41, The slide schema currently allows duplicate sequence positions
within the same presentation, so update the slide definition to enforce
uniqueness on the presentationId and order pair. Add the composite unique
constraint in the slide model in prisma/schema.prisma and mirror it in the
migration SQL for the slide table, using the existing slide, presentationId, and
order symbols to locate the change.

Comment thread prisma/schema.prisma
Comment on lines +57 to +67
order Int
title String
content String @db.Text
notes String? @db.Text
imageUrl String?
imagePrompt String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@index([presentationId])
@@map("slide")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Enforce unique slide ordering per presentation.

getPresentationWithSlides sorts by order, but this schema still allows two slides in the same presentation to share the same value. That makes the slide sequence nondeterministic and weakens the data contract for any UI/navigation built on it.

Suggested schema fix
 model Slide {
   id             String       `@id` `@default`(cuid())
   presentationId String
   presentation   Presentation `@relation`(fields: [presentationId], references: [id], onDelete: Cascade)
   order          Int
   title          String
   content        String       `@db.Text`
   notes          String?      `@db.Text`
   imageUrl       String?
   imagePrompt    String?
   createdAt      DateTime     `@default`(now())
   updatedAt      DateTime     `@updatedAt`

-  @@index([presentationId])
+  @@unique([presentationId, order])
   @@map("slide")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
order Int
title String
content String @db.Text
notes String? @db.Text
imageUrl String?
imagePrompt String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([presentationId])
@@map("slide")
order Int
title String
content String `@db.Text`
notes String? `@db.Text`
imageUrl String?
imagePrompt String?
createdAt DateTime `@default`(now())
updatedAt DateTime `@updatedAt`
@@unique([presentationId, order])
@@map("slide")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prisma/schema.prisma` around lines 57 - 67, The slide ordering in the schema
is not constrained per presentation, so duplicate values can make the sequence
nondeterministic. Update the Slide model in prisma/schema.prisma to enforce a
unique ordering within each presentation by adding a composite uniqueness
constraint on presentationId and order, and keep the existing
getPresentationWithSlides ordering logic unchanged since it will then rely on a
stable contract.

Comment on lines +26 to +33
status: 'GENERATING',
},
})

// await inngest.send({
// name: 'presentation/generate',
// data: { presentationId: presentation.id },
// })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

These mutations leave presentations stuck in GENERATING.

Both paths flip the status to GENERATING, but neither one actually dispatches generation work right now. With the queue call commented out, newly created/regenerated presentations will sit in the loading state indefinitely.

Also applies to: 75-83

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/presentations/actions/presentation-mutations.ts` around lines 26
- 33, The presentation mutation flow is leaving items stuck in GENERATING
because the generation job is never dispatched after the status update. In the
relevant mutation handlers in presentation-mutations.ts, restore the queued
generation step by re-enabling the inngest.send call (or otherwise dispatching
the generation work) for both the create and regenerate paths so the status
transition is followed by actual processing.

Comment on lines +18 to +27
export const getPresentation = createServerFn({ method: 'GET' })
.inputValidator((data: unknown) => presentationIdInputSchema.parse(data))
.handler(async ({ data }) => {
const userId = await requirePresentationUserId()
const row = await prisma.presentation.findFirst({
where: { id: data.id, userId },
})
if (!row) throw new Error('Not found')
return row
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== presentation-queries.ts ==\n'
sed -n '1,220p' src/features/presentations/api/presentation-queries.ts

printf '\n== search notFound / 404 helpers ==\n'
rg -n "notFound|404|Not found|createServerFn|throw new Error\\('Not found'\\)" src . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**'

printf '\n== locate related presentation handlers ==\n'
rg -n "presentation-queries|presentation-mutations|requirePresentationUserId|presentationIdInputSchema" src --glob '!**/node_modules/**'

Repository: xoloAyush/AI_ppt_v2

Length of output: 8182


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files importing notFound ==\n'
rg -n "from ['\"][^'\"]*notFound|\\bnotFound\\(" src . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**'

printf '\n== package deps mentioning tanstack/router/start ==\n'
sed -n '1,220p' package.json 2>/dev/null || true
if [ -f pnpm-lock.yaml ]; then
  rg -n "\"`@tanstack/`(react-router|start|router)\"" pnpm-lock.yaml | head -n 40
fi
if [ -f package-lock.json ]; then
  rg -n "\"`@tanstack/`(react-router|start|router)\"" package-lock.json | head -n 40
fi
if [ -f yarn.lock ]; then
  rg -n "`@tanstack/`(react-router|start|router)" yarn.lock | head -n 40
fi

Repository: xoloAyush/AI_ppt_v2

Length of output: 189


🌐 Web query:

TanStack React Start createServerFn notFound 404 server function error handling

💡 Result:

In TanStack Start, you handle "Not Found" states in server functions by throwing the notFound() utility imported from @tanstack/react-router [1][2]. When a server function throws notFound(), the framework automatically serializes this to the client, allowing the router to handle it and trigger the appropriate notFoundComponent [1][3]. Key implementation details and best practices include: Using the notFound utility: Within your server function handler, check for the absence of a resource and throw notFound() [1][4]. import { createServerFn } from '@tanstack/react-start' import { notFound } from '@tanstack/react-router' export const getPost = createServerFn.validator((id: string) => id).handler(async ({ data }) => { const post = await db.findPost(data) if (!post) { throw notFound } return post }) Requirement for useServerFn: If a server function is capable of throwing notFound() or redirect(), it should be invoked using the useServerFn hook on the client [5]. The useServerFn hook is responsible for "wiring" these thrown router exceptions into the TanStack Router lifecycle, ensuring that the router correctly performs the navigation or renders the not-found boundary [5]. Calling the server function directly is safe for functions that only return data, but useServerFn is the recommended standard when these control-flow exceptions are involved [5]. Handling the visual result: To ensure a good user experience, you must configure your application to display the not-found state [6]. This is done by adding a notFoundComponent to your root route or specific child routes within your TanStack Router configuration [6]. If no notFoundComponent is defined, the framework will fall back to a default, basic "Not Found" message [6]. Important considerations: - Troubleshooting: If you encounter 500 errors instead of 404s, ensure you are using useServerFn if the function throws, and avoid manual HTTP response manipulation (like setResponseStatus(404)) which may bypass the router's error handling mechanisms [7][5]. - Granularity: You can optionally pass a routeId to the notFound() function if you need to target a specific route's not-found boundary rather than the nearest one [6][8].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== usePresentation-detail.ts ==\n'
sed -n '1,220p' src/features/presentations/hooks/usePresentation-detail.ts

printf '\n== route usage of detail hook ==\n'
rg -n "usePresentation-detail|getPresentationWithSlides|presentation detail|presentation not found|Not found" src/routes src/features/presentations --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**'

Repository: xoloAyush/AI_ppt_v2

Length of output: 4978


Use notFound() for both missing-record branches, and invoke the detail fetch through useServerFn. Error('Not found') turns stale or deleted IDs into generic query failures instead of the router’s not-found state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/presentations/api/presentation-queries.ts` around lines 18 - 27,
The getPresentation server function currently throws a generic Error for missing
records, which bypasses the router’s not-found handling. Update the
getPresentation handler to call notFound() for every missing-record path,
including stale/deleted IDs after the prisma.presentation.findFirst lookup, and
keep the user lookup via requirePresentationUserId intact. Also make sure the
presentation detail fetch is performed through useServerFn so the client-side
route can receive and react to the not-found state correctly.

export const presentationIdInputSchema = z.object({ id: z.string().min(1) })

export const createPresentationInputSchema = z.object({
prompt: z.string().min(1).max(50_000),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Trim prompt/title before validating length.

z.string().min(1) accepts whitespace-only input, so create/update can still persist effectively blank prompts or titles.

Suggested validation change
 export const createPresentationInputSchema = z.object({
-  prompt: z.string().min(1).max(50_000),
+  prompt: z.string().trim().min(1).max(50_000),
   slideCount: z.number().int().min(3).max(20),
   style: presentationStyleSchema,
   tone: presentationToneSchema,
   layout: presentationLayoutSchema,
 })
 
 export const updatePresentationInputSchema = z
   .object({
     id: z.string().min(1),
-    title: z.string().min(1).max(200).optional(),
-    prompt: z.string().min(1).max(50_000).optional(),
+    title: z.string().trim().min(1).max(200).optional(),
+    prompt: z.string().trim().min(1).max(50_000).optional(),
     slideCount: z.number().int().min(3).max(20).optional(),
     style: presentationStyleSchema.optional(),
     tone: presentationToneSchema.optional(),
     layout: presentationLayoutSchema.optional(),
   })

Also applies to: 37-38

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/presentations/types/schema.ts` at line 27, The schema validation
for prompt/title in the presentation types still allows whitespace-only values
because z.string().min(1) is checked before trimming; update the prompt and
title validators in schema.ts so they trim input before enforcing min/max
length. Apply the same fix to both the create/update variants referenced by the
prompt and title fields, using the existing schema definitions in that file to
ensure blank-looking strings are rejected consistently.

Comment on lines +5 to +7
async ({ event, step }) => {
await step.sleep("wait-a-moment", "1s");
return { message: `Hello ${event.data.email}!` };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate the event payload before dereferencing email.

This handler assumes every test/hello.world event includes data.email. A malformed or partial event will either throw on event.data.email or return Hello undefined!, which turns a bad input into a failed run.

Suggested fix
 export const helloWorld = inngest.createFunction(
   { id: "hello-world", triggers: [{ event: "test/hello.world" }] },
   async ({ event, step }) => {
+    const email = event.data?.email;
+    if (!email) {
+      throw new Error("Missing email in test/hello.world event");
+    }
+
     await step.sleep("wait-a-moment", "1s");
-    return { message: `Hello ${event.data.email}!` };
+    return { message: `Hello ${email}!` };
   },
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async ({ event, step }) => {
await step.sleep("wait-a-moment", "1s");
return { message: `Hello ${event.data.email}!` };
export const helloWorld = inngest.createFunction(
{ id: "hello-world", triggers: [{ event: "test/hello.world" }] },
async ({ event, step }) => {
const email = event.data?.email;
if (!email) {
throw new Error("Missing email in test/hello.world event");
}
await step.sleep("wait-a-moment", "1s");
return { message: `Hello ${email}!` };
},
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/integrations/inngest/functions.ts` around lines 5 - 7, The handler in the
inngest function assumes event.data.email is always present, which can cause a
crash or return an invalid greeting for malformed events. Update the async
handler for the test/hello.world event to validate the event payload before
using event.data.email, and handle missing/invalid data with an explicit
fallback or failure path instead of dereferencing it directly. Keep the fix
localized to the function body that calls step.sleep and builds the Hello
message.

Comment thread src/routes/index.tsx
Comment on lines +81 to +99
const createMut = useMutation({
mutationFn: ()=> createPresentation({
data:{
prompt : form.content.trim(),
slideCount: form.slideCount,
style: form.style,
tone: form.tone,
layout: form.layout,
},
}),
onSuccess: (presentation)=>{
toast.success('Presentation created')
queryClient.invalidateQueries({
queryKey: presentationQueryKeys.list()
})
navigate({
to: '/presentation/$presentationId',
params: {presentationId: presentation.id}
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Start the generation job before treating this as a successful create flow.

This success path assumes createPresentation kicks off generation, but the server action currently only inserts a row with status: 'GENERATING' and returns it. In src/features/presentations/actions/presentation-mutations.ts, Lines 27-30, the inngest.send(...) call is still commented out, so this route will navigate users to a presentation that never progresses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/index.tsx` around lines 81 - 99, The create flow in
createMut/onSuccess assumes generation has already started, but
createPresentation currently only inserts the presentation row and returns it.
Update the server action in presentation-mutations to actually kick off the
generation job by restoring the inngest.send(...) call (or equivalent job
dispatch) inside createPresentation so the returned GENERATING record is backed
by a running job. Keep the route logic in src/routes/index.tsx as the success
handler only after the mutation truly starts generation.

Comment on lines +147 to +149
const data = query.data
const thumb = presentationThumbnailUrl(data.id)
const activeSlide = slides.at(activeSlideIndex)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clamp the selected slide when the slide count shrinks.

If the presentation is regenerated or edited down to fewer slides than activeSlideIndex, slides.at(activeSlideIndex) becomes undefined and the detail view loses its preview even though slides still exist. Reset or clamp the index whenever slides.length changes.

Possible fix
-import { useCallback, useState } from 'react'
+import { useCallback, useEffect, useState } from 'react'
   const {
     query,
     slides,
@@
   } = usePresentationDetail(presentationId, {
     onDeleted: () => navigate({ to: '/' }),
   })
+
+  useEffect(() => {
+    if (slides.length === 0) {
+      setActiveSlideIndex(0)
+      return
+    }
+
+    setActiveSlideIndex((i) => Math.min(i, slides.length - 1))
+  }, [slides.length])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const data = query.data
const thumb = presentationThumbnailUrl(data.id)
const activeSlide = slides.at(activeSlideIndex)
import { useCallback, useEffect, useState } from 'react'
Suggested change
const data = query.data
const thumb = presentationThumbnailUrl(data.id)
const activeSlide = slides.at(activeSlideIndex)
const {
query,
slides,
@@
} = usePresentationDetail(presentationId, {
onDeleted: () => navigate({ to: '/' }),
})
useEffect(() => {
if (slides.length === 0) {
setActiveSlideIndex(0)
return
}
setActiveSlideIndex((i) => Math.min(i, slides.length - 1))
}, [slides.length])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/presentation`.$presentationId.tsx around lines 147 - 149, The
detail view can lose its preview when the slide list shrinks because
presentationThumbnailUrl, activeSlideIndex, and slides.at(activeSlideIndex)
still assume the old index is valid. In
src/routes/presentation.$presentationId.tsx, update the logic around
activeSlideIndex so it is clamped or reset whenever slides.length changes, and
ensure the selected slide is always derived from a valid index before rendering
the preview.

Comment on lines +191 to +213
{slides.length > 0 && (
<>
<Button
variant="outline"
size="sm"
className="rounded-xl gap-1"
onClick={() => setShowSlideshow(true)}
>
<Play className="size-4" />
<span className="hidden sm:inline">Slideshow</span>
</Button>
<Button
variant="outline"
size="sm"
className="rounded-xl gap-1"
// onClick={handleExportPptx}
// disabled={isExporting}
>
<Download className="size-4" />
{/* <span className="hidden sm:inline">
{isExporting ? 'Exporting…' : 'Export'}
</span> */}
</Button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

This route still ships without a working slide-viewing flow.

SlidePreview, the sidebar slide list, and the slideshow modal are all commented out, and the export button is rendered without an action. For presentations that already have slides, users end up with an empty preview area plus controls that don't do anything. Hide these controls or wire the rendering components back in before merge.

Also applies to: 417-430, 496-522

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/presentation`.$presentationId.tsx around lines 191 - 213, The
presentation route still exposes slide-related controls without any working
slide-viewing behavior. In presentation.$presentationId.tsx, either restore the
missing slide flow by wiring back SlidePreview, the sidebar slide list, the
slideshow modal, and the export action handlers, or remove/hide the Button
controls and related UI until they are functional. Make sure the JSX branches
around slides.length, setShowSlideshow, and the export button are consistent so
users never see empty preview areas or non-working controls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant