Skip to content

refactor: clean up code#43

Merged
EinfachMxrc merged 6 commits into
mainfrom
refactor/clean-up-code
Apr 14, 2026
Merged

refactor: clean up code#43
EinfachMxrc merged 6 commits into
mainfrom
refactor/clean-up-code

Conversation

@Daanieeel

@Daanieeel Daanieeel commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • New dashboard shell and UI component library (buttons, cards, dialogs, tabs, inputs) for a more consistent, polished interface.
    • Server-side session handling and demo-login endpoint for improved auth flows.
  • Improvements

    • Dark mode/theme refinements and expanded semantic theming.
    • Upgraded styling toolchain to Tailwind v4; updated styles for buttons and global theme tokens.
    • PowerPoint add-in stability and sync reliability improvements.
  • Bug Fixes

    • Safer print and global-object handling to avoid runtime errors.

Copilot AI review requested due to automatic review settings April 14, 2026 18:12
@vercel

vercel Bot commented Apr 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
handout-powerpoint-addin Ready Ready Preview, Comment Apr 14, 2026 6:40pm

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Refactors the PowerPoint add-in into separate Zustand stores, migrates styling to Tailwind v4, adds a Base UI component library, implements server-side session cookies with middleware, replaces direct window/Office references with safe global helpers, and introduces Turborepo task orchestration and related scripts.

Changes

Cohort / File(s) Summary
Monorepo & build tooling
\.gitignore, package.json, turbo.json, README.md, apps/web/scripts/dev.mjs, netlify.toml
Add Turborepo config and scripts, pin pnpm, add dev:all/dev orchestration, new dev script for web port probing; remove Netlify config; update docs.
Tailwind / PostCSS upgrade
apps/web/package.json, apps/powerpoint-addin/package.json, apps/web/postcss.config.mjs, apps/powerpoint-addin/postcss.config.mjs, apps/web/tailwind.config.ts, apps/powerpoint-addin/src/index.css, apps/web/src/app/globals.css
Migrate to Tailwind v4 and @tailwindcss/postcss, remove autoprefixer, change CSS imports and theme token additions, large theme/variable overhaul.
PowerPoint add-in stores & components
apps/powerpoint-addin/src/store/connectionStore.ts, .../simulatorStore.ts, .../uiStore.ts, .../types.ts, apps/powerpoint-addin/src/App.tsx, .../components/*, apps/powerpoint-addin/src/main.tsx, apps/powerpoint-addin/src/lib/officeBridge.ts
Split monolithic add‑in store into connection/simulator/ui stores and move types; update App and panels to use new hooks; add safe global helpers for document/root and Office context; refactor sync logic and simulator behavior.
Web app auth, sessions & middleware
apps/web/src/app/api/auth/session/route.ts, apps/web/src/app/api/auth/demo/route.ts, apps/web/src/lib/authSession.ts, apps/web/src/middleware.ts, apps/web/src/app/auth/*, apps/web/src/components/powerpoint/PowerPointAddinClient.tsx
Add server endpoints to set/clear session cookie, demo auth route that sets cookie, helpers to call them, middleware protecting /dashboard and redirecting /auth, and update client auth flows to set/clear server cookie before navigation.
Dashboard & viewer flows
apps/web/src/app/dashboard/layout.tsx, apps/web/src/app/dashboard/page.tsx, apps/web/src/app/dashboard/session/[id]/page.tsx, apps/web/src/app/dashboard/handout/[id]/print/page.tsx, apps/web/src/components/dashboard/DashboardShell.tsx
Convert dashboard layout to server component using cookie check, extract DashboardShell, replace polling with Convex query subscription for viewer count, and guard print calls via globalThis.print?.().
UI component library & UI primitives
apps/web/src/components/ui/button.tsx, .../card.tsx, .../dialog.tsx, .../input.tsx, .../select.tsx, .../tabs.tsx, .../textarea.tsx, apps/web/src/components/ui/Modal.tsx
Add new Base UI‑backed primitives with CVA styling and accessibility wrappers; migrate custom Modal to Dialog primitives.
Theming & utilities
apps/web/src/components/ThemeToggle.tsx, apps/web/src/components/ConvexClientProvider.tsx, apps/web/src/app/layout.tsx, apps/web/src/lib/utils.ts, apps/web/components.json
Switch to next-themes, add cn utility (clsx + twMerge), add Shadcn UI config, integrate Geist font, and change Convex provider wrapping behavior.
PowerPoint & Office bridge (web)
apps/web/src/lib/powerpoint/officeBridge.ts, apps/web/src/app/powerpoint/addin/page.tsx, apps/web/src/components/powerpoint/PowerPointAddinClient.tsx
Replace direct Office/window usage with getOfficeContextDoc/getClientOrigin and globalThis usages; introduce useOfficeBridgeLifecycle and auth/session sync hooks; session-scoped bridge slide tracking.
Store/API surface & visibility changes
apps/web/src/store/*, convex/_utils.ts, apps/web/src/store/authStore.ts, apps/web/src/store/dashboardStore.ts
Auth store replaces isLoading→hasHydrated; dashboardStore removed (state lifted/redistributed); reduce export visibility in convex/_utils (types and hash helpers now file-local).
Misc web fixes
apps/web/src/app/h/[token]/page.tsx, apps/web/src/components/QRCodeDialog.tsx, apps/web/src/app/globals.css, apps/web/.env.local.example
Use blockRefs instead of document queries, guard printing, use globalThis.location origin fallbacks, update env example and many CSS theme tokens.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 Hop, hop, the stores now split with glee,
Three burrows where there once was we.
Tailwind dresses pages fresh and new,
Sessions tucked in cookies, safe and true.
Turbo hums — globalThis whispers, "We grew!"

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.90% 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 pull request title 'refactor: clean up code' is vague and generic, using non-descriptive terms that don't convey meaningful information about the specific changes made. Replace with a more descriptive title that highlights the primary change, such as 'refactor: migrate from monolithic store to modular Zustand stores and upgrade Tailwind v3 to v4' or 'refactor: split store architecture and upgrade styling toolchain'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/clean-up-code

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

@greptile-apps

greptile-apps Bot commented Apr 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR is a broad refactor focused on three areas: (1) replacing client-side-only route guarding with a proper httpOnly session cookie backed by a new /api/auth/session route and Next.js middleware, (2) simplifying the dashboard shell from a client component with Convex subscriptions into a lean server component, and (3) replacing custom low-level primitives (window, manual DOM modals, polling loops) with globalThis-safe wrappers, @base-ui/react dialog/button components, and reactive useQuery subscriptions.

Key changes:

  • New auth cookie layer: POST /api/auth/session sets an httpOnly cookie on login; DELETE clears it on logout; middleware protects /dashboard/* and redirects authenticated users away from /auth/*
  • DashboardLayout simplified to server component: cookie guard moved to middleware + server layout; DashboardShell handles the client-side nav/logout
  • PowerPointAddinClient split into custom hooks: useAddinAuthSessionSync and useOfficeBridgeLifecycle extract the auth and bridge concerns
  • useQuery replaces polling: viewer count now uses a Convex subscription instead of a setInterval loop
  • globalThis throughout: window.* calls replaced with globalThis.* for SSR/Office safety
  • UI primitives upgraded: Modal now wraps @base-ui/react Dialog; dashboard uses shadcn-style Button, Tabs, Input, Textarea
  • dashboardStore deleted: local UI state inlined into page components

Notable concerns:

  • handleDownloadPDF in the public reader page lost its document.title manipulation, so PDFs will no longer be pre-named with the handout title
  • DashboardShell no longer calls api.auth.validateToken; Convex sessions invalidated server-side will not trigger an auto-redirect
  • clearServerSessionCookie silently swallows errors, unlike setServerSessionCookie which throws
  • OFFICE_CONTEXT_DOC_KEY = "doc" + "ument" is an unnecessary obfuscation of the string "document" that hurts readability

Confidence Score: 3/5

  • Safe to merge after addressing the Convex token validation gap and the handleDownloadPDF regression.
  • The new httpOnly cookie auth layer and middleware are implemented correctly, and the majority of the refactoring is clean. However two P1 issues exist: (1) the removed Convex validateToken call means invalidated server-side sessions no longer auto-redirect users out of the dashboard, and (2) handleDownloadPDF lost its document.title manipulation so PDF filenames revert to the generic page name. Both are behavioural regressions introduced by this PR.
  • apps/web/src/components/dashboard/DashboardShell.tsx (missing Convex token validation) and apps/web/src/app/h/[token]/page.tsx (handleDownloadPDF regression)

Important Files Changed

Filename Overview
apps/web/src/app/h/[token]/page.tsx Public handout reader page: handleDownloadPDF now calls globalThis.print() without setting document.title, losing the PDF filename feature from the previous implementation
apps/web/src/components/dashboard/DashboardShell.tsx New client shell for the dashboard nav/header; Convex token validation that previously triggered auto-redirect on expired sessions is absent, leaving stale sessions stuck in the dashboard
apps/web/src/app/dashboard/layout.tsx Simplified from a heavy client component to a lean async server component that delegates cookie guard to middleware and wraps children in DashboardShell
apps/web/src/middleware.ts New Next.js middleware enforcing httpOnly session cookie on /dashboard routes and redirecting authenticated users away from /auth routes
apps/web/src/app/api/auth/session/route.ts New API route for setting (POST) and clearing (DELETE) the httpOnly session cookie; secure flag tied to NODE_ENV, correct sameSite and maxAge configuration
apps/web/src/lib/authSession.ts Client-side helpers wrapping the session API route; setServerSessionCookie throws on failure but clearServerSessionCookie silently ignores errors, creating an inconsistency on logout failure
apps/web/src/lib/powerpoint/officeBridge.ts Refactored to use getOfficeContextDoc() helper and globalThis throughout; introduces OFFICE_CONTEXT_DOC_KEY = "doc" + "ument" obfuscation that reduces readability
apps/web/src/store/authStore.ts isLoading renamed to hasHydrated with onRehydrateStorage callback, providing a cleaner hydration signal for zustand-persist
apps/web/src/components/powerpoint/PowerPointAddinClient.tsx Large refactor splitting monolithic component into useAddinAuthSessionSync and useOfficeBridgeLifecycle hooks; session cookie sync added on login/logout; uses effectiveSelectedSessionId to avoid stale selection
apps/web/src/app/dashboard/session/[id]/page.tsx Viewer count polling replaced with a useQuery subscription (skip when not live), cleaner and reactive; globalThis.location?.origin used safely for URL construction
apps/web/src/app/dashboard/page.tsx Session selection pruning moved from a useEffect to a derived effectiveSelectedSessions memo; useEffect removal avoids a render cycle; type of selectedSessions tightened to Id<...>
convex/_utils.ts Internal-only types and helpers (BlockDoc, SessionDoc, sha256Hash, simpleHash) narrowed to module-private by removing export; no behaviour change

Sequence Diagram

sequenceDiagram
    participant Browser
    participant Middleware
    participant Layout as DashboardLayout
    participant AuthAPI as /api/auth/session
    participant Convex

    Note over Browser,Convex: Login flow
    Browser->>Convex: login mutation
    Convex-->>Browser: token
    Browser->>Browser: setAuth() into zustand store
    Browser->>AuthAPI: POST /api/auth/session
    AuthAPI-->>Browser: Set-Cookie slide_handout_session (httpOnly)
    Browser->>Browser: router.replace("/dashboard")

    Note over Browser,Convex: Dashboard access
    Browser->>Middleware: "GET /dashboard/*"
    Middleware->>Middleware: check slide_handout_session cookie
    alt cookie missing
        Middleware-->>Browser: redirect to /auth/login
    else cookie present
        Middleware-->>Browser: next()
        Browser->>Layout: render server component
        Layout->>Layout: cookies().get cookie
        Layout-->>Browser: render DashboardShell and children
    end

    Note over Browser,Convex: Logout
    Browser->>Convex: logout mutation
    Browser->>AuthAPI: DELETE /api/auth/session
    AuthAPI-->>Browser: clear cookie maxAge 0
    Browser->>Browser: clearAuth() then router.replace login
Loading

Fix All in Codex

Reviews (1): Last reviewed commit: "5th iteration" | Re-trigger Greptile

Comment on lines 85 to 89
const handleDownloadPDF = () => {
const previousTitle = document.title;
document.title = sessionInfo?.handoutTitle || "handout";
const restoreTitle = () => {
document.title = previousTitle;
window.removeEventListener("afterprint", restoreTitle);
};
window.addEventListener("afterprint", restoreTitle);
window.print();
if (typeof globalThis.print === "function") {
globalThis.print();
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 handleDownloadPDF now identical to handlePrint

The old handleDownloadPDF deliberately set document.title to the handout title before calling window.print(), then restored it in an afterprint listener. This caused the browser's "Save as PDF" dialog to pre-fill the filename with the handout name. The new version is now byte-for-byte identical to handlePrint, losing this feature entirely.

Suggested change
const handleDownloadPDF = () => {
const previousTitle = document.title;
document.title = sessionInfo?.handoutTitle || "handout";
const restoreTitle = () => {
document.title = previousTitle;
window.removeEventListener("afterprint", restoreTitle);
};
window.addEventListener("afterprint", restoreTitle);
window.print();
if (typeof globalThis.print === "function") {
globalThis.print();
}
};
const handleDownloadPDF = () => {
if (typeof globalThis.print === "function") {
const previousTitle = document.title;
document.title = sessionInfo?.handoutTitle || "handout";
const restoreTitle = () => {
document.title = previousTitle;
globalThis.removeEventListener?.("afterprint", restoreTitle);
};
globalThis.addEventListener?.("afterprint", restoreTitle);
globalThis.print();
}
};

Fix in Codex

Comment on lines +10 to +13
export function DashboardShell({ children }: { children: React.ReactNode }) {
const { token, clearAuth, presenterName, presenterEmail, isDemo } = useAuthStore();
const router = useRouter();
const logoutMutation = useMutation(api.auth.logout);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Convex token validation removed — expired sessions no longer auto-redirect

The old DashboardLayout called api.auth.validateToken and redirected to /auth/login when it returned null (i.e., the token had been invalidated on the server). DashboardShell no longer does this validation, so a user whose Convex session is explicitly invalidated (e.g., force-logout from another device, or server-side session cleanup) will stay on the dashboard and see failing Convex queries instead of being redirected.

The new middleware and layout server-component both check only the httpOnly cookie, which has a 30-day lifetime that may outlast the actual Convex session. Consider reinstating the validateToken call in DashboardShell (or a shared hook) and calling clearAuth() + router.replace("/auth/login") when it returns null.

Fix in Codex

Comment on lines +18 to +22
export async function clearServerSessionCookie(): Promise<void> {
await fetch("/api/auth/session", {
method: "DELETE",
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 clearServerSessionCookie silently swallows fetch errors

setServerSessionCookie throws on a non-OK response, giving callers the chance to handle failure. clearServerSessionCookie fires-and-forgets the DELETE call with no error handling. If the request fails (network hiccup, server error), the httpOnly cookie stays active on the server while the client-side store is already cleared — the user appears logged out locally but the session cookie remains valid until it expires naturally.

Suggested change
export async function clearServerSessionCookie(): Promise<void> {
await fetch("/api/auth/session", {
method: "DELETE",
});
}
export async function clearServerSessionCookie(): Promise<void> {
const response = await fetch("/api/auth/session", {
method: "DELETE",
});
if (!response.ok) {
console.warn("Session cookie could not be cleared:", response.status);
}
}

Fix in Codex

Comment on lines +28 to +32
const OFFICE_CONTEXT_DOC_KEY = "doc" + "ument";

export function isOfficeAvailable(): boolean {
function getOfficeContextDoc() {
return (Office.context as Record<string, any>)[OFFICE_CONTEXT_DOC_KEY];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Obfuscated property key reduces readability

"doc" + "ument" constructs the string "document" via concatenation for no runtime benefit — the result is identical to the literal string. This makes the code harder to read, search, and understand. The same pattern also appears in apps/powerpoint-addin/src/lib/officeBridge.ts.

If the goal is to suppress a lint rule or static-analysis warning on Office.context.document, a targeted inline eslint-disable on the specific accessor line would be clearer and self-documenting. Consider replacing the key constant with the plain string "document".

Fix in Codex

Copilot AI 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.

Pull request overview

This PR refactors the monorepo tooling and the web/add-in frontends by introducing a Turborepo task pipeline, migrating styling to Tailwind v4 + shadcn/base-ui components, and adding an HttpOnly cookie-based “session present” guard for /dashboard routing.

Changes:

  • Add Turborepo (turbo.json, root scripts) and standardize workspace build/lint/type-check/test execution via turbo run.
  • Migrate apps/web and apps/powerpoint-addin to Tailwind v4 PostCSS setup and introduce a set of base-ui/shadcn-inspired UI primitives.
  • Introduce server-side route guarding via middleware + /api/auth/session to set/clear an HttpOnly session cookie; refactor dashboard shell/layout and PowerPoint add-in state management.

Reviewed changes

Copilot reviewed 53 out of 56 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
turbo.json Adds Turborepo task definitions for build/lint/type-check/test/dev.
package.json Switches root scripts to turbo run, adds turbo, pins packageManager.
netlify.toml Removes Netlify-specific build configuration.
convex/_utils.ts Narrows exported surface area by making internal types/helpers non-exported.
apps/web/tailwind.config.ts Extends Tailwind theme tokens for CSS-variable-driven design system.
apps/web/src/store/dashboardStore.ts Removes the previous dashboard UI zustand store.
apps/web/src/store/authStore.ts Renames loading state to hydration state and sets it on rehydrate.
apps/web/src/middleware.ts Adds cookie-based redirects for /dashboard/* and /auth/*.
apps/web/src/lib/utils.ts Adds cn() helper for class merging (clsx + tailwind-merge).
apps/web/src/lib/powerpoint/officeBridge.ts Refactors Office context access and window listener guards.
apps/web/src/lib/authSession.ts Adds helpers to set/clear an HttpOnly session cookie via API route.
apps/web/src/components/ui/textarea.tsx Adds reusable textarea UI primitive.
apps/web/src/components/ui/tabs.tsx Adds reusable tabs UI primitive (Base UI).
apps/web/src/components/ui/select.tsx Adds reusable select UI primitive (Base UI).
apps/web/src/components/ui/input.tsx Adds reusable input UI primitive (Base UI).
apps/web/src/components/ui/dialog.tsx Adds reusable dialog UI primitive (Base UI).
apps/web/src/components/ui/card.tsx Adds reusable card UI primitive.
apps/web/src/components/ui/button.tsx Adds reusable button UI primitive (CVA + Base UI).
apps/web/src/components/ui/Modal.tsx Reimplements Modal using the new Dialog primitive.
apps/web/src/components/powerpoint/PowerPointAddinClient.tsx Refactors add-in client logic into hooks; adds server cookie sync on login/logout.
apps/web/src/components/dashboard/QRCodeDialog.tsx Uses globalThis.location for safer origin concatenation.
apps/web/src/components/dashboard/DashboardShell.tsx Extracts dashboard shell and adds logout cookie clearing.
apps/web/src/components/ThemeToggle.tsx Switches theme handling to next-themes.
apps/web/src/components/ConvexClientProvider.tsx Wraps app in ThemeProvider and keeps ConvexProvider.
apps/web/src/app/powerpoint/addin/page.tsx Hardens history restoration script using globalThis.
apps/web/src/app/layout.tsx Updates fonts and removes custom theme bootstrap script.
apps/web/src/app/h/[token]/page.tsx Refactors new-block scroll/flash behavior; safer print invocation.
apps/web/src/app/globals.css Migrates to Tailwind v4 @import approach and adds design tokens.
apps/web/src/app/dashboard/session/[id]/page.tsx Replaces polling viewer count with Convex useQuery subscription.
apps/web/src/app/dashboard/page.tsx Refactors dashboard UI to new primitives and improves session selection typing.
apps/web/src/app/dashboard/layout.tsx Converts dashboard layout to server-side cookie gate + DashboardShell.
apps/web/src/app/dashboard/handout/[id]/print/page.tsx Safer print() invocation via globalThis.
apps/web/src/app/auth/register/page.tsx Sets HttpOnly session cookie on register and uses router.replace.
apps/web/src/app/auth/login/page.tsx Sets HttpOnly session cookie on login and uses router.replace.
apps/web/src/app/auth/demo/page.tsx Ensures cookie is set for demo logins; fixes effect deps.
apps/web/src/app/api/auth/session/route.ts Adds API endpoint to set/clear HttpOnly session cookie.
apps/web/postcss.config.mjs Switches PostCSS plugin to @tailwindcss/postcss (Tailwind v4).
apps/web/package.json Adds Tailwind v4 + base-ui/shadcn deps.
apps/web/components.json Adds shadcn configuration for the web app.
apps/powerpoint-addin/src/store/uiStore.ts Introduces separated UI store for add-in settings panel state.
apps/powerpoint-addin/src/store/types.ts Extracts add-in connection types.
apps/powerpoint-addin/src/store/simulatorStore.ts Introduces persisted simulator configuration store.
apps/powerpoint-addin/src/store/connectionStore.ts Refactors add-in connection store and separates runtime reset.
apps/powerpoint-addin/src/main.tsx Safely accesses document via globalThis reflection.
apps/powerpoint-addin/src/lib/officeBridge.ts Refactors Office context doc access; improves callback typings.
apps/powerpoint-addin/src/index.css Migrates to Tailwind v4 import and inlines button utility styles.
apps/powerpoint-addin/src/components/SimulatorPanel.tsx Switches to the new simulator store selectors.
apps/powerpoint-addin/src/components/SettingsPanel.tsx Switches to new stores and uses globalThis.location?.reload().
apps/powerpoint-addin/src/components/ConnectionStatus.tsx Updates type import to new shared store types.
apps/powerpoint-addin/src/App.tsx Refactors add-in state usage to split stores; hardens Office bridge lifecycle.
apps/powerpoint-addin/postcss.config.mjs Switches PostCSS plugin to @tailwindcss/postcss (Tailwind v4).
apps/powerpoint-addin/package.json Upgrades Tailwind to v4 and updates PostCSS deps.
README.md Updates Turbo usage docs (but currently has broken Markdown fencing).
.gitignore Ignores .turbo/ and *.tsbuildinfo.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
Comment on lines 256 to +266
```bash
# 1. Einmalig: Convex initialisieren + Env-Dateien anlegen
npx convex dev # Anweisungen folgen, dann Ctrl+C
pnpm setup # URLs in Web-App & Add-in übernehmen

## Monorepo-Tasks (Turbo)

```bash
# Build (Turbo Pipeline)
pnpm build

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

The Markdown code fences in this section are unbalanced: a bash block is opened at “Lokale Startbefehle”, then a new “## Monorepo-Tasks (Turbo)” heading and another bash fence appear before the first block is closed. This renders the headings/content inside a code block. Close the first code fence before starting the “Monorepo-Tasks” section (and avoid nested fences).

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +17
export async function POST(request: Request) {
try {
const body = (await request.json()) as { token?: string };
const token = body.token?.trim();

if (!token) {
return NextResponse.json(
{ error: "Token fehlt" },
{ status: 400 }
);
}

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

POST /api/auth/session sets an HttpOnly auth cookie purely based on a user-supplied token in the JSON body, with no same-origin/CSRF validation. A cross-site form POST can set an arbitrary cookie value for a victim, which (combined with middleware redirects) can lock users out or force confusing auth state. Consider rejecting requests whose Origin/Sec-Fetch-Site aren’t same-origin and/or requiring a CSRF token; alternatively validate the token server-side before setting the cookie.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +21
if (isAuthPath && sessionToken) {
const url = request.nextUrl.clone();
url.pathname = "/dashboard";
return NextResponse.redirect(url);
}

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

The middleware redirects any /auth/* request to /dashboard whenever the session cookie exists. Since the cookie can be stale/invalid (or even attacker-forced via CSRF on /api/auth/session), this can prevent users from reaching the login page to recover. Consider removing the /auth-to-/dashboard redirect, or only redirecting after actual token validation (not just cookie presence).

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +13
export function DashboardShell({ children }: { children: React.ReactNode }) {
const { token, clearAuth, presenterName, presenterEmail, isDemo } = useAuthStore();
const router = useRouter();
const logoutMutation = useMutation(api.auth.logout);

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

DashboardShell assumes the client-side auth store has a valid token, but the new server/middleware gating only checks the HttpOnly cookie. If the cookie exists while localStorage has been cleared (token=null), dashboard pages will sit in a perpetual “Lädt…” state and the user may be unable to navigate back to /auth/* due to middleware redirects. Consider using hasHydrated to detect this case and proactively clear the server cookie + redirect to /auth/login when hydrated and token is missing.

Copilot uses AI. Check for mistakes.

@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: 10

Caution

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

⚠️ Outside diff range comments (2)
apps/powerpoint-addin/src/store/connectionStore.ts (1)

63-70: ⚠️ Potential issue | 🟠 Major

Version the persisted store before reusing the old key.

Line 64 keeps the existing slide-handout-addin storage key, but the new persist options don't add a version/migrate. Zustand's partialize only filters which state fields are written to storage on future saves—it does not affect rehydration. During rehydration, Zustand uses a shallow merge by default: { ...initialState, ...persistedState }. Without version/migrate, returning users will rehydrate stale runtime fields from the old, broader persisted shape (mode, syncStatus, isOfficeAvailable, etc.) into the new split store on first load after upgrade, until those fields are explicitly reset or overwritten.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/powerpoint-addin/src/store/connectionStore.ts` around lines 63 - 70, The
persisted store is reusing the old "slide-handout-addin" key while only
filtering writes with partialize, causing stale fields to rehydrate; update the
persist config used in connectionStore (the persist(...) call that contains
partialize) to include a version number and a migrate function (or bump the
storage key) so old persisted shapes are transformed/filtered on
rehydration—implement migrate to accept the incoming persistedState and return
only the expected fields (sessionPublicToken, presenterToken, sessionId,
convexUrl) or merge them safely into the store's initial state, ensuring
obsolete fields (mode, syncStatus, isOfficeAvailable, etc.) are dropped.
apps/web/src/components/powerpoint/PowerPointAddinClient.tsx (1)

392-414: ⚠️ Potential issue | 🟡 Minor

Handle potential failure of setServerSessionCookie.

If setServerSessionCookie throws after a successful login, the user will be logged in locally but the exception will propagate to the catch block, setting loginError. However, the local state (token, presenterEmail, etc.) has already been set, leaving the UI in an inconsistent state where the user appears logged in but sees an error.

Consider either awaiting and handling the cookie error separately, or setting local state only after the cookie is successfully set.

🛡️ Proposed fix
     try {
       const result = await login({
         email: loginEmail.trim(),
         password: loginPassword,
       });

+      await setServerSessionCookie(result.token);
       setToken(result.token);
       setPresenterEmail(result.email ?? loginEmail.trim());
       setPresenterName(result.name ?? "");
-      await setServerSessionCookie(result.token);
       setLoginPassword("");
     } catch (error) {
       setLoginError(getErrorMessage(error));
     } finally {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/powerpoint/PowerPointAddinClient.tsx` around lines
392 - 414, The handleLogin function sets local state (setToken,
setPresenterEmail, setPresenterName) before awaiting setServerSessionCookie,
which can throw and leave the UI inconsistent; modify handleLogin to await
setServerSessionCookie first (using the token from result) and only set local
state after it succeeds, or if you prefer to set state early, wrap
setServerSessionCookie in its own try/catch and on failure rollback the local
state (clear token/name/email) and setLoginError with getErrorMessage(error);
reference handleLogin, setServerSessionCookie, setToken, setPresenterEmail,
setPresenterName, and setLoginError when making the change.
🧹 Nitpick comments (15)
turbo.json (1)

4-7: Clarify the intent of empty build.outputs.

With "outputs": [], Turbo will not cache or restore any build artifacts (.next, dist, etc.) on cache hits—only logs are replayed. If build output caching is intended, specify the actual output globs:

Suggested adjustment
     "build": {
       "dependsOn": ["^build"],
-      "outputs": []
+      "outputs": [".next/**", "dist/**"]
     },

If skipping artifact caching is intentional (e.g., outputs are reproducible or context-specific), this configuration is fine as-is.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@turbo.json` around lines 4 - 7, The "build" pipeline currently has "outputs":
[] which prevents Turbo from caching build artifacts; update the "build"
pipeline (the "build" object) to list the actual output globs you want cached
(for example Next.js .next, static out, or package dist folders) so Turbo can
store/restore artifacts instead of only replaying logs—replace the empty array
with appropriate output patterns such as .next/**, out/**, dist/** or
packages/*/dist/** depending on your project outputs.
apps/web/src/app/h/[token]/page.tsx (1)

79-89: handlePrint and handleDownloadPDF are identical — consider deduplication.

Both functions have the exact same implementation. Either consolidate into a single handler or extract a shared utility. This reduces maintenance burden if the print logic changes.

Suggested fix
-  const handlePrint = () => {
-    if (typeof globalThis.print === "function") {
-      globalThis.print();
-    }
-  };
-
-  const handleDownloadPDF = () => {
+  const handlePrint = () => {
     if (typeof globalThis.print === "function") {
       globalThis.print();
     }
   };

Then use handlePrint for both the "Drucken" button (line 148) and the "Als PDF speichern" button (line 156).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/h/`[token]/page.tsx around lines 79 - 89, Both handlers
handlePrint and handleDownloadPDF are identical; replace them with a single
shared handler (e.g., handlePrint) and update callers to use that one function.
Locate the two functions handlePrint and handleDownloadPDF in the component,
remove the duplicate, keep a single function that calls globalThis.print() after
the typeof check, and ensure the "Drucken" and "Als PDF speichern" button click
handlers reference the consolidated handlePrint function.
apps/powerpoint-addin/src/components/SimulatorPanel.tsx (2)

90-95: Consider making the auto-advance label dynamic.

The button shows "Auto-Advance (3s)" but the actual interval is simulatorAutoAdvanceMs || 3000. If users configure a different interval via the store, the label becomes misleading.

Suggested fix
       <button
         className={isAutoRunning ? "btn-danger w-full text-xs" : "btn-secondary w-full text-xs"}
         onClick={handleAutoToggle}
       >
-        {isAutoRunning ? "Auto-Stop" : "Auto-Advance (3s)"}
+        {isAutoRunning ? "Auto-Stop" : `Auto-Advance (${(simulatorAutoAdvanceMs || 3000) / 1000}s)`}
       </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/powerpoint-addin/src/components/SimulatorPanel.tsx` around lines 90 -
95, The button label is hardcoded to "Auto-Advance (3s)" while the actual
interval uses simulatorAutoAdvanceMs || 3000; update the label generation in the
component so it displays the effective interval value (e.g.,
`${simulatorAutoAdvanceMs || 3000}ms`) instead of the fixed "3s", keeping
existing state/handlers (isAutoRunning, handleAutoToggle) unchanged so the text
reflects the configured auto-advance interval.

34-41: Add radix parameter to parseInt for consistency.

Line 36 uses parseInt(jumpInput) without a radix, while line 104 correctly uses parseInt(e.target.value, 10). For consistency and to avoid subtle parsing issues with leading zeros, add the radix.

Suggested fix
   const handleJump = (e: React.FormEvent) => {
     e.preventDefault();
-    const n = parseInt(jumpInput);
+    const n = parseInt(jumpInput, 10);
     if (!isNaN(n)) {
       simRef.current?.jumpTo(n);
       setJumpInput("");
     }
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/powerpoint-addin/src/components/SimulatorPanel.tsx` around lines 34 -
41, The parseInt call in handleJump uses parseInt(jumpInput) without a radix;
update it to parseInt(jumpInput, 10) so it matches the other usage (e.g.,
parseInt(e.target.value, 10)) and avoids octal/locale parsing issues—change the
parse in handleJump where it reads jumpInput before calling
simRef.current?.jumpTo(n) and clearing via setJumpInput("").
apps/web/src/app/api/auth/session/route.ts (1)

7-37: Consider validating the token format before storing.

The POST handler accepts any non-empty string as a token. If there's an expected format (e.g., minimum length, character set), validating it here would provide early feedback to callers and prevent storing obviously invalid tokens.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/auth/session/route.ts` around lines 7 - 37, The POST
handler accepts any non-empty token; validate the token format in POST (after
const token = body.token?.trim())—e.g., enforce minimum length and allowed
character set with a simple regex or length check, return NextResponse.json({
error: "Ungültiges Token" }, { status: 400 }) for invalid tokens, and only set
the cookie (AUTH_SESSION_COOKIE, using AUTH_SESSION_MAX_AGE_SECONDS) when the
token passes validation; keep the existing trimming and error handling flow but
add this early validation to prevent storing malformed tokens.
apps/powerpoint-addin/src/components/SettingsPanel.tsx (1)

27-32: Potential redundancy between setIsSettingsOpen(false) and onClose().

Both are called in handleClear. If onClose already updates the UI store's isSettingsOpen state (or vice versa), one call may be redundant. Otherwise, this is fine if they serve different purposes (e.g., store state vs. parent component cleanup).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/powerpoint-addin/src/components/SettingsPanel.tsx` around lines 27 - 32,
Investigate whether onClose already updates the shared UI store's isSettingsOpen
state to avoid redundant state updates: inspect handleClear, setIsSettingsOpen,
and onClose to see if onClose toggles the same store flag; if onClose does set
the UI store flag remove the setIsSettingsOpen(false) call, otherwise keep it
and add a brief comment explaining the distinction (e.g., local UI state vs.
parent cleanup). Ensure resetRuntime and setConnectionInfo remain invoked and
run existing unit/integration tests or add a small test verifying the modal
closes and cleanup runs after handleClear.
apps/web/src/lib/authSession.ts (1)

18-22: clearServerSessionCookie silently ignores errors.

Unlike setServerSessionCookie, this function doesn't check response.ok or throw on failure. While silent failure during logout might be acceptable (to avoid blocking user experience), it could mask network issues. Consider at least logging the error or returning a success indicator.

Suggested fix
-export async function clearServerSessionCookie(): Promise<void> {
-  await fetch("/api/auth/session", {
+export async function clearServerSessionCookie(): Promise<boolean> {
+  const response = await fetch("/api/auth/session", {
     method: "DELETE",
   });
+  return response.ok;
 }

Callers can then decide whether to handle failures (e.g., for analytics/logging) without blocking the UI.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/authSession.ts` around lines 18 - 22, The
clearServerSessionCookie function currently ignores fetch failures; update it to
check the fetch response (like setServerSessionCookie does) and either return a
boolean success indicator or throw on non-ok responses so callers can handle
failures; additionally log errors (e.g., via console.error or existing logger)
with context if the network call fails or response.ok is false. Ensure you
adjust the function signature/return (clearServerSessionCookie) and any callers
to consume the success/throw behavior consistently.
apps/web/src/components/ui/Modal.tsx (1)

34-37: Consider making the "Dialog" eyebrow text configurable or removing it.

The hardcoded "Dialog" label may not be appropriate for all modal contexts (e.g., confirmations, alerts, forms). Consider either making it an optional prop or removing it if it doesn't add value.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ui/Modal.tsx` around lines 34 - 37, The hardcoded
"Dialog" eyebrow inside the Modal's DialogHeader should be made configurable or
removed; update the Modal component to accept an optional prop (e.g.,
eyebrowText or eyebrow) and render that value instead of the static "Dialog"
(falling back to undefined/null to hide it), or remove the <div
className="eyebrow"> entirely and adjust consumers to pass the new prop when
needed; locate the JSX around DialogHeader / DialogTitle and modify the render
logic to conditionally show the eyebrow based on the new prop.
apps/web/src/app/globals.css (2)

165-167: Consider adding overscroll-behavior for better scroll containment.

The global styles apply smooth scrolling to html. For modal-heavy UIs, consider adding overscroll-behavior: contain to prevent scroll chaining.

📝 Optional enhancement
 * { border-color: var(--line); }
 html { scroll-behavior: smooth;
-   `@apply` font-sans; }
+   `@apply` font-sans;
+   overscroll-behavior: contain; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/globals.css` around lines 165 - 167, Add overscroll
containment to the global html rule to prevent scroll chaining for modal-heavy
UIs: update the html CSS rule (the selector "html" in globals.css) to include
overscroll-behavior: contain alongside scroll-behavior: smooth so scrolling is
contained and modals don’t propagate scroll to the viewport.

5-48: Stylelint errors are false positives for Tailwind v4 syntax.

The static analysis hints flag @custom-variant and @theme as unknown at-rules. These are valid Tailwind CSS v4 directives, not SCSS. Consider updating Stylelint configuration to handle Tailwind v4 syntax:

📝 Stylelint configuration suggestion

Update your Stylelint configuration to ignore Tailwind-specific at-rules:

{
  "rules": {
    "at-rule-no-unknown": [
      true,
      {
        "ignoreAtRules": ["tailwind", "apply", "layer", "config", "theme", "custom-variant"]
      }
    ]
  }
}

Or use the official Tailwind Stylelint plugin: stylelint-config-tailwindcss.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/globals.css` around lines 5 - 48, The Stylelint false
positives are caused by Tailwind v4 at-rules like `@custom-variant` and `@theme`
being flagged as unknown; update the Stylelint configuration to allow these
Tailwind at-rules (e.g., add "theme" and "custom-variant" to the
at-rule-no-unknown ignoreAtRules list or install and extend
stylelint-config-tailwindcss) so the CSS snippet containing `@custom-variant` and
`@theme` validates correctly.
apps/powerpoint-addin/src/App.tsx (1)

22-37: Consider consolidating store selectors to reduce re-render overhead.

Each individual useAddinConnectionStore call creates a separate subscription. While Zustand is efficient with shallow comparisons, extracting multiple values in a single selector can be more performant for related state:

📝 Alternative: consolidated selector
const {
  sessionId,
  presenterToken,
  syncStatus,
  isOfficeAvailable: isOfficeAvailableState,
  isSyncing,
  connectionError,
  lastKnownSlide,
} = useAddinConnectionStore((state) => ({
  sessionId: state.sessionId,
  presenterToken: state.presenterToken,
  syncStatus: state.syncStatus,
  isOfficeAvailable: state.isOfficeAvailable,
  isSyncing: state.isSyncing,
  connectionError: state.connectionError,
  lastKnownSlide: state.lastKnownSlide,
}), shallow);

// Setters can remain separate as they are stable references
const setOfficeAvailableState = useAddinConnectionStore((state) => state.setOfficeAvailable);
// ... etc

Note: Requires importing shallow from zustand/shallow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/powerpoint-addin/src/App.tsx` around lines 22 - 37, Consolidate the many
useAddinConnectionStore selectors into a single selector to avoid multiple
subscriptions: create one selector that returns an object with sessionId,
presenterToken, syncStatus, isOfficeAvailable (alias to isOfficeAvailableState),
isSyncing, connectionError, and lastKnownSlide, and pass shallow from
zustand/shallow as the equality fn; keep setter hooks like setOfficeAvailable,
setSyncStatus, setConnectionError, setLastKnownSlide, and setIsSyncing as
separate single-property selectors to preserve stable references (refer to the
useAddinConnectionStore selector usage and the setter identifiers).
apps/powerpoint-addin/src/index.css (1)

11-21: Consider extracting shared button utilities to reduce duplication.

The button variant classes now inline all shared utilities (inline-flex items-center justify-center rounded px-3 py-1.5 text-xs font-medium transition-colors disabled:opacity-50). While this works, it duplicates ~12 utility classes across three variants.

Consider using @apply with a shared base class or CSS custom properties to reduce maintenance burden.

📝 Alternative: shared button base
/* Shared button base */
.btn-base {
  `@apply` inline-flex items-center justify-center rounded px-3 py-1.5 text-xs font-medium transition-colors disabled:opacity-50;
}

.btn-primary {
  `@apply` btn-base bg-blue-600 text-white hover:bg-blue-700;
}

.btn-secondary {
  `@apply` btn-base bg-gray-100 text-gray-700 hover:bg-gray-200;
}

.btn-danger {
  `@apply` btn-base bg-red-600 text-white hover:bg-red-700;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/powerpoint-addin/src/index.css` around lines 11 - 21, The three button
classes (.btn-primary, .btn-secondary, .btn-danger) duplicate the same shared
utilities; extract those common utilities into a shared class (e.g., .btn-base)
using `@apply` for the shared tokens (inline-flex items-center justify-center
rounded px-3 py-1.5 text-xs font-medium transition-colors disabled:opacity-50)
and then update each variant to `@apply` the shared .btn-base plus only the unique
color classes (bg-..., text-..., hover:...), keeping the same class names
(.btn-base, .btn-primary, .btn-secondary, .btn-danger) so behavior and selectors
remain unchanged.
apps/web/src/components/ui/card.tsx (1)

1-11: Consider reordering exports after imports for readability.

The export statement is placed before the imports, which is valid ES module syntax but unconventional. Most codebases place exports after imports or at the end of the file for better readability and consistency.

📝 Suggested reordering
 import * as React from "react"
-export {
-  Card,
-  CardHeader,
-  CardFooter,
-  CardTitle,
-  CardAction,
-  CardDescription,
-  CardContent,
-}
 import { cn } from "@/lib/utils"
+
+export {
+  Card,
+  CardHeader,
+  CardFooter,
+  CardTitle,
+  CardAction,
+  CardDescription,
+  CardContent,
+}

Or use named exports at declarations:

-function Card({
+export function Card({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ui/card.tsx` around lines 1 - 11, Move the export
block so imports come first and exports follow for readability: place the
existing export { Card, CardHeader, CardFooter, CardTitle, CardAction,
CardDescription, CardContent } after the import statements (including import {
cn } from "@/lib/utils"), or alternatively convert to named exports at each
component declaration (e.g., export function/card const Card, CardHeader, etc.)
so the module follows the conventional import-then-export ordering; update any
references if you change to named exports.
apps/web/src/lib/powerpoint/officeBridge.ts (1)

28-32: Clarify the intent behind string concatenation for document key.

The use of "doc" + "ument" to construct the property key appears intentional to circumvent static analysis or security scanners. While this may be necessary for Office Add-in environments, consider adding a comment explaining why this pattern is used to help future maintainers understand the decision.

📝 Suggested documentation
+// Construct property key dynamically to avoid static analysis tools
+// flagging direct "document" access in Office Add-in context
 const OFFICE_CONTEXT_DOC_KEY = "doc" + "ument";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/powerpoint/officeBridge.ts` around lines 28 - 32, The
concatenation used to form OFFICE_CONTEXT_DOC_KEY ("doc" + "ument") is unclear
to readers; update the code around OFFICE_CONTEXT_DOC_KEY and
getOfficeContextDoc to keep the concatenation but add a concise comment
explaining that the split-string pattern is intentional to avoid triggering
static analyzers or security scanners in Office Add-in contexts and that it must
remain as-is to reliably access Office.context["document"]; include a note for
future maintainers stating that if static analysis rules change this can be
replaced with the literal "document".
apps/web/src/components/ui/dialog.tsx (1)

93-118: Consider the button ordering in DialogFooter.

With flex-col-reverse on mobile, the close button (rendered after children in DOM) will appear above the children visually. On desktop with sm:flex-row sm:justify-end, it appears at the end. This may be intentional, but if you expect the close button to appear after action buttons on mobile as well, consider adjusting the ordering.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ui/dialog.tsx` around lines 93 - 118, DialogFooter's
responsive layout uses flex-col-reverse which flips the DOM order on mobile so
the Close button (rendered after children) appears above action buttons; fix by
changing the container class in DialogFooter: replace "flex-col-reverse" with
"flex-col" (in the className string passed to the div with
data-slot="dialog-footer") so DOM order and visual order remain consistent
across breakpoints.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/postcss.config.mjs`:
- Around line 1-5: The project is missing the Tailwind `@config` directive so the
custom theme in apps/web/tailwind.config.ts (brand/emerald colors and
animations) isn't being loaded; open apps/web/src/app/globals.css and
immediately after the existing `@import` "tailwindcss" line add an `@config`
"./tailwind.config.ts" directive to explicitly point Tailwind to the JS/TS
config so utilities like bg-emerald-*, border-emerald-*, ring-emerald-*, and
custom animations are generated; you can still keep the postcss config (const
config.plugins["@tailwindcss/postcss"]) as-is.

In `@apps/web/src/app/auth/demo/page.tsx`:
- Around line 34-36: The client sets demo auth via setAuth before confirming
server-side session via await setServerSessionCookie, which can leave local
state authenticated if the POST fails; change the flow so you first await
setServerSessionCookie (wrap it in try/catch or check its success), and only
call setAuth(...) and router.replace("/dashboard") after the cookie POST
succeeds; if setServerSessionCookie fails, do not call setAuth or router.replace
and surface/log the error instead.
- Around line 21-24: The redirect is happening unconditionally in the finally
block which masks failures from setServerSessionCookie(token); change the flow
so you await or .then() the setServerSessionCookie(token) and only call
router.replace("/dashboard") on success, and add a .catch() or try/catch to
handle/log the error (and avoid leaving the rejection unhandled) — update the
code around setServerSessionCookie and router.replace to perform
router.replace("/dashboard") inside the success path and handle errors from
setServerSessionCookie(token) (e.g., log or show an error) instead of using
finally().

In `@apps/web/src/app/auth/login/page.tsx`:
- Around line 39-41: The client auth state is being set via setAuth before
ensuring the server session cookie was successfully persisted with
setServerSessionCookie, which can leave the client incorrectly authenticated if
the server call fails; change the call order so you await
setServerSessionCookie(result.token) first, only then call setAuth(result.token,
result.name ?? undefined, result.email, result.isDemo ?? false) and
router.replace("/dashboard"); also add error handling around
setServerSessionCookie to avoid mutating client state or to rollback setAuth and
prevent navigation if the server call throws.

In `@apps/web/src/app/auth/register/page.tsx`:
- Around line 45-47: Move the local authentication state update and navigation
until after the server cookie has been successfully written: call await
setServerSessionCookie(result.token as string) first, and only if that promise
resolves, call setAuth(result.token as string, name, email, result.isDemo ??
false) and then router.replace("/dashboard"); if setServerSessionCookie rejects,
do not call setAuth or router.replace — instead surface/handle the error (e.g.,
show an error toast) so the UI remains unauthenticated.

In `@apps/web/src/components/dashboard/DashboardShell.tsx`:
- Around line 26-28: The current flow calls clearServerSessionCookie() but
doesn't verify deletion succeeded before clearing local state; update
clearServerSessionCookie (in lib/authSession.ts) to check the fetch response.ok
and throw or return a failure on non-OK responses, then in DashboardShell.tsx
only call clearAuth() and router.replace("/auth/login") after
clearServerSessionCookie() resolves successfully; if the helper throws or
returns false, handle the error (log/show) and do not clear local auth or
navigate.

In `@apps/web/src/components/ThemeToggle.tsx`:
- Around line 17-22: The toggle shows two icons in system mode because
effectiveMode currently resolves system to "light" or "dark"; update the useMemo
in ThemeToggle so it preserves "system" (i.e., return mode when mode ===
"system") instead of mapping it to "light"/"dark", so icon-rendering branches
become mutually exclusive; locate the useMemo that computes effectiveMode and
change its logic to return mode ('system') when mode === "system" (keeping
[mode, resolvedTheme] deps) so only the system icon branch or the light/dark
branch renders.

In `@apps/web/src/components/ui/dialog.tsx`:
- Around line 133-147: The Tailwind descendant selectors in DialogDescription
are invalid; update the className passed to DialogPrimitive.Description inside
the DialogDescription function to replace the incorrect "*:[a]..." variants with
valid Tailwind v4 arbitrary variants targeting anchors, e.g. use
"[&_a]:underline", "[&_a]:underline-offset-3", and "[&_a:hover]:text-foreground"
(preserve the existing "text-sm text-muted-foreground" and keep concatenation
via cn and the className prop).

In `@apps/web/src/middleware.ts`:
- Around line 7-20: The middleware currently treats any non-empty
AUTH_SESSION_COOKIE value (sessionToken) as authenticated; change the logic in
middleware.ts to verify the session token before redirect decisions by replacing
the simple presence check with a server-side validation call (e.g.
verifySessionToken(sessionToken) or getSessionFromToken(sessionToken)) and use
the returned session/boolean to determine isAuthenticated; update the
isDashboardPath and isAuthPath branches to redirect only when isAuthenticated is
false (for dashboard) or true (for auth), or switch to verifying a signed/opaque
server session instead of trusting the raw cookie value.

In `@README.md`:
- Around line 261-274: Close the previously opened ```bash code fence before the
"## Monorepo-Tasks (Turbo)" heading so the heading is not rendered inside a code
block, then open a new ```bash fence immediately before the terminal commands
under that heading; locate the earlier unclosed backtick block and add the
closing ``` just above the "## Monorepo-Tasks (Turbo)" heading and ensure a
fresh ```bash fence is placed before the pnpm commands so the heading and
commands render correctly.

---

Outside diff comments:
In `@apps/powerpoint-addin/src/store/connectionStore.ts`:
- Around line 63-70: The persisted store is reusing the old
"slide-handout-addin" key while only filtering writes with partialize, causing
stale fields to rehydrate; update the persist config used in connectionStore
(the persist(...) call that contains partialize) to include a version number and
a migrate function (or bump the storage key) so old persisted shapes are
transformed/filtered on rehydration—implement migrate to accept the incoming
persistedState and return only the expected fields (sessionPublicToken,
presenterToken, sessionId, convexUrl) or merge them safely into the store's
initial state, ensuring obsolete fields (mode, syncStatus, isOfficeAvailable,
etc.) are dropped.

In `@apps/web/src/components/powerpoint/PowerPointAddinClient.tsx`:
- Around line 392-414: The handleLogin function sets local state (setToken,
setPresenterEmail, setPresenterName) before awaiting setServerSessionCookie,
which can throw and leave the UI inconsistent; modify handleLogin to await
setServerSessionCookie first (using the token from result) and only set local
state after it succeeds, or if you prefer to set state early, wrap
setServerSessionCookie in its own try/catch and on failure rollback the local
state (clear token/name/email) and setLoginError with getErrorMessage(error);
reference handleLogin, setServerSessionCookie, setToken, setPresenterEmail,
setPresenterName, and setLoginError when making the change.

---

Nitpick comments:
In `@apps/powerpoint-addin/src/App.tsx`:
- Around line 22-37: Consolidate the many useAddinConnectionStore selectors into
a single selector to avoid multiple subscriptions: create one selector that
returns an object with sessionId, presenterToken, syncStatus, isOfficeAvailable
(alias to isOfficeAvailableState), isSyncing, connectionError, and
lastKnownSlide, and pass shallow from zustand/shallow as the equality fn; keep
setter hooks like setOfficeAvailable, setSyncStatus, setConnectionError,
setLastKnownSlide, and setIsSyncing as separate single-property selectors to
preserve stable references (refer to the useAddinConnectionStore selector usage
and the setter identifiers).

In `@apps/powerpoint-addin/src/components/SettingsPanel.tsx`:
- Around line 27-32: Investigate whether onClose already updates the shared UI
store's isSettingsOpen state to avoid redundant state updates: inspect
handleClear, setIsSettingsOpen, and onClose to see if onClose toggles the same
store flag; if onClose does set the UI store flag remove the
setIsSettingsOpen(false) call, otherwise keep it and add a brief comment
explaining the distinction (e.g., local UI state vs. parent cleanup). Ensure
resetRuntime and setConnectionInfo remain invoked and run existing
unit/integration tests or add a small test verifying the modal closes and
cleanup runs after handleClear.

In `@apps/powerpoint-addin/src/components/SimulatorPanel.tsx`:
- Around line 90-95: The button label is hardcoded to "Auto-Advance (3s)" while
the actual interval uses simulatorAutoAdvanceMs || 3000; update the label
generation in the component so it displays the effective interval value (e.g.,
`${simulatorAutoAdvanceMs || 3000}ms`) instead of the fixed "3s", keeping
existing state/handlers (isAutoRunning, handleAutoToggle) unchanged so the text
reflects the configured auto-advance interval.
- Around line 34-41: The parseInt call in handleJump uses parseInt(jumpInput)
without a radix; update it to parseInt(jumpInput, 10) so it matches the other
usage (e.g., parseInt(e.target.value, 10)) and avoids octal/locale parsing
issues—change the parse in handleJump where it reads jumpInput before calling
simRef.current?.jumpTo(n) and clearing via setJumpInput("").

In `@apps/powerpoint-addin/src/index.css`:
- Around line 11-21: The three button classes (.btn-primary, .btn-secondary,
.btn-danger) duplicate the same shared utilities; extract those common utilities
into a shared class (e.g., .btn-base) using `@apply` for the shared tokens
(inline-flex items-center justify-center rounded px-3 py-1.5 text-xs font-medium
transition-colors disabled:opacity-50) and then update each variant to `@apply`
the shared .btn-base plus only the unique color classes (bg-..., text-...,
hover:...), keeping the same class names (.btn-base, .btn-primary,
.btn-secondary, .btn-danger) so behavior and selectors remain unchanged.

In `@apps/web/src/app/api/auth/session/route.ts`:
- Around line 7-37: The POST handler accepts any non-empty token; validate the
token format in POST (after const token = body.token?.trim())—e.g., enforce
minimum length and allowed character set with a simple regex or length check,
return NextResponse.json({ error: "Ungültiges Token" }, { status: 400 }) for
invalid tokens, and only set the cookie (AUTH_SESSION_COOKIE, using
AUTH_SESSION_MAX_AGE_SECONDS) when the token passes validation; keep the
existing trimming and error handling flow but add this early validation to
prevent storing malformed tokens.

In `@apps/web/src/app/globals.css`:
- Around line 165-167: Add overscroll containment to the global html rule to
prevent scroll chaining for modal-heavy UIs: update the html CSS rule (the
selector "html" in globals.css) to include overscroll-behavior: contain
alongside scroll-behavior: smooth so scrolling is contained and modals don’t
propagate scroll to the viewport.
- Around line 5-48: The Stylelint false positives are caused by Tailwind v4
at-rules like `@custom-variant` and `@theme` being flagged as unknown; update the
Stylelint configuration to allow these Tailwind at-rules (e.g., add "theme" and
"custom-variant" to the at-rule-no-unknown ignoreAtRules list or install and
extend stylelint-config-tailwindcss) so the CSS snippet containing
`@custom-variant` and `@theme` validates correctly.

In `@apps/web/src/app/h/`[token]/page.tsx:
- Around line 79-89: Both handlers handlePrint and handleDownloadPDF are
identical; replace them with a single shared handler (e.g., handlePrint) and
update callers to use that one function. Locate the two functions handlePrint
and handleDownloadPDF in the component, remove the duplicate, keep a single
function that calls globalThis.print() after the typeof check, and ensure the
"Drucken" and "Als PDF speichern" button click handlers reference the
consolidated handlePrint function.

In `@apps/web/src/components/ui/card.tsx`:
- Around line 1-11: Move the export block so imports come first and exports
follow for readability: place the existing export { Card, CardHeader,
CardFooter, CardTitle, CardAction, CardDescription, CardContent } after the
import statements (including import { cn } from "@/lib/utils"), or alternatively
convert to named exports at each component declaration (e.g., export
function/card const Card, CardHeader, etc.) so the module follows the
conventional import-then-export ordering; update any references if you change to
named exports.

In `@apps/web/src/components/ui/dialog.tsx`:
- Around line 93-118: DialogFooter's responsive layout uses flex-col-reverse
which flips the DOM order on mobile so the Close button (rendered after
children) appears above action buttons; fix by changing the container class in
DialogFooter: replace "flex-col-reverse" with "flex-col" (in the className
string passed to the div with data-slot="dialog-footer") so DOM order and visual
order remain consistent across breakpoints.

In `@apps/web/src/components/ui/Modal.tsx`:
- Around line 34-37: The hardcoded "Dialog" eyebrow inside the Modal's
DialogHeader should be made configurable or removed; update the Modal component
to accept an optional prop (e.g., eyebrowText or eyebrow) and render that value
instead of the static "Dialog" (falling back to undefined/null to hide it), or
remove the <div className="eyebrow"> entirely and adjust consumers to pass the
new prop when needed; locate the JSX around DialogHeader / DialogTitle and
modify the render logic to conditionally show the eyebrow based on the new prop.

In `@apps/web/src/lib/authSession.ts`:
- Around line 18-22: The clearServerSessionCookie function currently ignores
fetch failures; update it to check the fetch response (like
setServerSessionCookie does) and either return a boolean success indicator or
throw on non-ok responses so callers can handle failures; additionally log
errors (e.g., via console.error or existing logger) with context if the network
call fails or response.ok is false. Ensure you adjust the function
signature/return (clearServerSessionCookie) and any callers to consume the
success/throw behavior consistently.

In `@apps/web/src/lib/powerpoint/officeBridge.ts`:
- Around line 28-32: The concatenation used to form OFFICE_CONTEXT_DOC_KEY
("doc" + "ument") is unclear to readers; update the code around
OFFICE_CONTEXT_DOC_KEY and getOfficeContextDoc to keep the concatenation but add
a concise comment explaining that the split-string pattern is intentional to
avoid triggering static analyzers or security scanners in Office Add-in contexts
and that it must remain as-is to reliably access Office.context["document"];
include a note for future maintainers stating that if static analysis rules
change this can be replaced with the literal "document".

In `@turbo.json`:
- Around line 4-7: The "build" pipeline currently has "outputs": [] which
prevents Turbo from caching build artifacts; update the "build" pipeline (the
"build" object) to list the actual output globs you want cached (for example
Next.js .next, static out, or package dist folders) so Turbo can store/restore
artifacts instead of only replaying logs—replace the empty array with
appropriate output patterns such as .next/**, out/**, dist/** or
packages/*/dist/** depending on your project outputs.
🪄 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: 27a2eb0f-caa3-4826-979e-c2ab8e124ee8

📥 Commits

Reviewing files that changed from the base of the PR and between d9c45a1 and 345ca14.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (54)
  • .gitignore
  • README.md
  • apps/powerpoint-addin/package.json
  • apps/powerpoint-addin/postcss.config.mjs
  • apps/powerpoint-addin/src/App.tsx
  • apps/powerpoint-addin/src/components/ConnectionStatus.tsx
  • apps/powerpoint-addin/src/components/SettingsPanel.tsx
  • apps/powerpoint-addin/src/components/SimulatorPanel.tsx
  • apps/powerpoint-addin/src/index.css
  • apps/powerpoint-addin/src/lib/officeBridge.ts
  • apps/powerpoint-addin/src/main.tsx
  • apps/powerpoint-addin/src/store/connectionStore.ts
  • apps/powerpoint-addin/src/store/simulatorStore.ts
  • apps/powerpoint-addin/src/store/types.ts
  • apps/powerpoint-addin/src/store/uiStore.ts
  • apps/web/components.json
  • apps/web/package.json
  • apps/web/postcss.config.mjs
  • apps/web/src/app/api/auth/session/route.ts
  • apps/web/src/app/auth/demo/page.tsx
  • apps/web/src/app/auth/login/page.tsx
  • apps/web/src/app/auth/register/page.tsx
  • apps/web/src/app/dashboard/handout/[id]/print/page.tsx
  • apps/web/src/app/dashboard/layout.tsx
  • apps/web/src/app/dashboard/page.tsx
  • apps/web/src/app/dashboard/session/[id]/page.tsx
  • apps/web/src/app/globals.css
  • apps/web/src/app/h/[token]/page.tsx
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/powerpoint/addin/page.tsx
  • apps/web/src/components/ConvexClientProvider.tsx
  • apps/web/src/components/ThemeToggle.tsx
  • apps/web/src/components/dashboard/DashboardShell.tsx
  • apps/web/src/components/dashboard/QRCodeDialog.tsx
  • apps/web/src/components/powerpoint/PowerPointAddinClient.tsx
  • apps/web/src/components/ui/Modal.tsx
  • apps/web/src/components/ui/button.tsx
  • apps/web/src/components/ui/card.tsx
  • apps/web/src/components/ui/dialog.tsx
  • apps/web/src/components/ui/input.tsx
  • apps/web/src/components/ui/select.tsx
  • apps/web/src/components/ui/tabs.tsx
  • apps/web/src/components/ui/textarea.tsx
  • apps/web/src/lib/authSession.ts
  • apps/web/src/lib/powerpoint/officeBridge.ts
  • apps/web/src/lib/utils.ts
  • apps/web/src/middleware.ts
  • apps/web/src/store/authStore.ts
  • apps/web/src/store/dashboardStore.ts
  • apps/web/tailwind.config.ts
  • convex/_utils.ts
  • netlify.toml
  • package.json
  • turbo.json
💤 Files with no reviewable changes (2)
  • netlify.toml
  • apps/web/src/store/dashboardStore.ts

Comment on lines 1 to 5
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
"@tailwindcss/postcss": {},
},
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- any `@config` directives under apps/web/src ---"
rg -n '@config' apps/web/src || true

echo "--- apps/web/tailwind.config.ts ---"
sed -n '1,120p' apps/web/tailwind.config.ts

echo "--- possible usages of config-only utilities ---"
rg -nP 'animate-fade-in|animate-slide-down|\b(?:bg|text|border|ring)-(brand|emerald)-' apps/web/src || true

Repository: EinfachMxrc/handout

Length of output: 4740


🌐 Web query:

Tailwind CSS v4 PostCSS plugin auto-discovery config file same directory

💡 Result:

No, the Tailwind CSS v4 PostCSS plugin (@tailwindcss/postcss) does not automatically discover a tailwind.config.js (or similar) file in the same directory or anywhere else. JavaScript config files are no longer auto-detected for backward compatibility reasons. To use a JS config file, you must explicitly load it in your CSS using the @config directive, placed after @import "tailwindcss" and before other directives like @theme. Example: @import "tailwindcss"; @config "./tailwind.config.js"; // relative path from the CSS file @theme { // optional CSS config } The primary configuration method in v4 is now CSS-first using @theme, @plugin, etc., directly in the CSS file — no config file needed for most cases. The PostCSS plugin processes the CSS file (including directives) during the build.

Citations:


Add @config directive to load the Tailwind config in CSS.

Tailwind v4 does not auto-detect JavaScript config files. The existing apps/web/tailwind.config.ts contains custom theme extensions (brand and emerald colors, animations) that are actively used throughout the codebase, but they will silently stop generating without an explicit @config directive.

In apps/web/src/app/globals.css, add the directive immediately after @import "tailwindcss":

`@import` "tailwindcss";
`@config` "./tailwind.config.ts";

Without this, utilities like bg-emerald-*, border-emerald-*, ring-emerald-*, and custom animations will not be generated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/postcss.config.mjs` around lines 1 - 5, The project is missing the
Tailwind `@config` directive so the custom theme in apps/web/tailwind.config.ts
(brand/emerald colors and animations) isn't being loaded; open
apps/web/src/app/globals.css and immediately after the existing `@import`
"tailwindcss" line add an `@config` "./tailwind.config.ts" directive to explicitly
point Tailwind to the JS/TS config so utilities like bg-emerald-*,
border-emerald-*, ring-emerald-*, and custom animations are generated; you can
still keep the postcss config (const config.plugins["@tailwindcss/postcss"])
as-is.

Comment thread apps/web/src/app/auth/demo/page.tsx
Comment thread apps/web/src/app/auth/demo/page.tsx Outdated
Comment on lines 39 to +41
setAuth(result.token, result.name ?? undefined, result.email, result.isDemo ?? false);
router.push("/dashboard");
await setServerSessionCookie(result.token);
router.replace("/dashboard");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Persist the server session before mutating client auth state.

Line 39 commits the token locally before Line 40 proves the server session was stored. If /api/auth/session fails, the login page shows an error but the rest of the client can still think the user is authenticated.

Proposed fix
-      setAuth(result.token, result.name ?? undefined, result.email, result.isDemo ?? false);
-      await setServerSessionCookie(result.token);
+      await setServerSessionCookie(result.token);
+      setAuth(result.token, result.name ?? undefined, result.email, result.isDemo ?? false);
       router.replace("/dashboard");
📝 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
setAuth(result.token, result.name ?? undefined, result.email, result.isDemo ?? false);
router.push("/dashboard");
await setServerSessionCookie(result.token);
router.replace("/dashboard");
await setServerSessionCookie(result.token);
setAuth(result.token, result.name ?? undefined, result.email, result.isDemo ?? false);
router.replace("/dashboard");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/auth/login/page.tsx` around lines 39 - 41, The client auth
state is being set via setAuth before ensuring the server session cookie was
successfully persisted with setServerSessionCookie, which can leave the client
incorrectly authenticated if the server call fails; change the call order so you
await setServerSessionCookie(result.token) first, only then call
setAuth(result.token, result.name ?? undefined, result.email, result.isDemo ??
false) and router.replace("/dashboard"); also add error handling around
setServerSessionCookie to avoid mutating client state or to rollback setAuth and
prevent navigation if the server call throws.

Comment on lines 45 to +47
setAuth(result.token as string, name, email, result.isDemo ?? false);
router.push("/dashboard");
await setServerSessionCookie(result.token as string);
router.replace("/dashboard");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Only mark the client as authenticated after the cookie write succeeds.

Line 45 updates the local auth store before Line 46 awaits the session-cookie request. If that POST fails, this page stays locally authenticated while the next /dashboard navigation is still rejected by the missing cookie.

Suggested fix
-      setAuth(result.token as string, name, email, result.isDemo ?? false);
-      await setServerSessionCookie(result.token as string);
+      const nextToken = result.token as string;
+      await setServerSessionCookie(nextToken);
+      setAuth(nextToken, name, email, result.isDemo ?? false);
       router.replace("/dashboard");
📝 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
setAuth(result.token as string, name, email, result.isDemo ?? false);
router.push("/dashboard");
await setServerSessionCookie(result.token as string);
router.replace("/dashboard");
const nextToken = result.token as string;
await setServerSessionCookie(nextToken);
setAuth(nextToken, name, email, result.isDemo ?? false);
router.replace("/dashboard");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/auth/register/page.tsx` around lines 45 - 47, Move the local
authentication state update and navigation until after the server cookie has
been successfully written: call await setServerSessionCookie(result.token as
string) first, and only if that promise resolves, call setAuth(result.token as
string, name, email, result.isDemo ?? false) and then
router.replace("/dashboard"); if setServerSessionCookie rejects, do not call
setAuth or router.replace — instead surface/handle the error (e.g., show an
error toast) so the UI remains unauthenticated.

Comment on lines +26 to +28
await clearServerSessionCookie();
clearAuth();
router.replace("/auth/login");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't clear local auth until the server cookie delete is confirmed.

clearServerSessionCookie() only awaits fetch() and never checks response.ok in apps/web/src/lib/authSession.ts. If /api/auth/session returns 500, the HttpOnly cookie survives, but Lines 27-28 still clear local state and navigate to /auth/login; the middleware will then redirect that request straight back to /dashboard. Please make the helper fail on non-OK responses and only clearAuth()/redirect after the delete is confirmed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/dashboard/DashboardShell.tsx` around lines 26 - 28,
The current flow calls clearServerSessionCookie() but doesn't verify deletion
succeeded before clearing local state; update clearServerSessionCookie (in
lib/authSession.ts) to check the fetch response.ok and throw or return a failure
on non-OK responses, then in DashboardShell.tsx only call clearAuth() and
router.replace("/auth/login") after clearServerSessionCookie() resolves
successfully; if the helper throws or returns false, handle the error (log/show)
and do not clear local auth or navigate.

Comment thread apps/web/src/components/ThemeToggle.tsx Outdated
Comment on lines +17 to +22
const effectiveMode = useMemo<Mode>(() => {
if (mode === "system") {
return resolvedTheme === "dark" ? "dark" : "light";
}
return mode;
}, [mode, resolvedTheme]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Make the icon branches mutually exclusive.

When mode === "system", effectiveMode still becomes "light" or "dark", so one of these branches renders and the existing system branch on Line 52 renders too. The toggle ends up showing two icons at once in system mode.

🩹 Minimal fix
-      {effectiveMode === "light" && (
+      {mode !== "system" && effectiveMode === "light" && (
         <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
@@
-      {effectiveMode === "dark" && (
+      {mode !== "system" && effectiveMode === "dark" && (
         <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>

Also applies to: 34-43

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ThemeToggle.tsx` around lines 17 - 22, The toggle
shows two icons in system mode because effectiveMode currently resolves system
to "light" or "dark"; update the useMemo in ThemeToggle so it preserves "system"
(i.e., return mode when mode === "system") instead of mapping it to
"light"/"dark", so icon-rendering branches become mutually exclusive; locate the
useMemo that computes effectiveMode and change its logic to return mode
('system') when mode === "system" (keeping [mode, resolvedTheme] deps) so only
the system icon branch or the light/dark branch renders.

Comment on lines +133 to +147
function DialogDescription({
className,
...props
}: DialogPrimitive.Description.Props) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn(
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className
)}
{...props}
/>
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Tailwind CSS v4 arbitrary variant syntax for descendant selectors

💡 Result:

In Tailwind CSS v4, arbitrary variant syntax allows custom selectors using square bracket notation, such as [& *]:bg-red-500 to target direct children or [& ]:bg-blue-500 for deeper descendants. The general form is [selector]:utility, where selector uses & for the parent element. For descendant selectors specifically: - Direct children (child combinator): [& > *]:utility or simply *:utility (built-in child variant). - All descendants (descendant combinator): [& *]:utility. Examples: - [& :hover]:underline — underlines hovered descendants. - [& .child]:bg-green-500 — targets elements with class 'child' anywhere in descendants. - lg:[&:nth-child(-n+3)]:hover:underline — responsive arbitrary variant for first 3 children on hover. This syntax works in v4 as confirmed by official docs and GitHub discussions where arbitrary variants like [&:has([aria-selected])]:text-white are validated. The * built-in variant is for direct children (> ), while arbitrary enables full CSS descendant selectors (). Arbitrary variants support chaining, composability with named variants like data-, and complex pseudo-classes/selectors.

Citations:


Use proper Tailwind CSS v4 syntax for descendant selectors.

The *:[a] syntax is not valid in Tailwind CSS v4. To target anchor elements within the description, use the standard arbitrary variant syntax: [&_a]:underline, [&_a]:underline-offset-3, and [&_a:hover]:text-foreground.

Current code
className={cn(
  "text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
  className
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ui/dialog.tsx` around lines 133 - 147, The Tailwind
descendant selectors in DialogDescription are invalid; update the className
passed to DialogPrimitive.Description inside the DialogDescription function to
replace the incorrect "*:[a]..." variants with valid Tailwind v4 arbitrary
variants targeting anchors, e.g. use "[&_a]:underline",
"[&_a]:underline-offset-3", and "[&_a:hover]:text-foreground" (preserve the
existing "text-sm text-muted-foreground" and keep concatenation via cn and the
className prop).

Comment on lines +7 to +20
const sessionToken = request.cookies.get(AUTH_SESSION_COOKIE)?.value;
const isDashboardPath = pathname.startsWith("/dashboard");
const isAuthPath = pathname.startsWith("/auth");

if (isDashboardPath && !sessionToken) {
const url = request.nextUrl.clone();
url.pathname = "/auth/login";
return NextResponse.redirect(url);
}

if (isAuthPath && sessionToken) {
const url = request.nextUrl.clone();
url.pathname = "/dashboard";
return NextResponse.redirect(url);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Cookie presence alone is not a real auth check.

This middleware treats any AUTH_SESSION_COOKIE value as authenticated state, but the new session API currently stores that cookie for any non-empty token body. A client can mint an arbitrary cookie and satisfy both of these redirects without a validated session. Please validate the token before issuing the cookie, or switch to a signed/opaque server session and verify that here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/middleware.ts` around lines 7 - 20, The middleware currently
treats any non-empty AUTH_SESSION_COOKIE value (sessionToken) as authenticated;
change the logic in middleware.ts to verify the session token before redirect
decisions by replacing the simple presence check with a server-side validation
call (e.g. verifySessionToken(sessionToken) or
getSessionFromToken(sessionToken)) and use the returned session/boolean to
determine isAuthenticated; update the isDashboardPath and isAuthPath branches to
redirect only when isAuthenticated is false (for dashboard) or true (for auth),
or switch to verifying a signed/opaque server session instead of trusting the
raw cookie value.

Comment thread README.md

@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: 4

🧹 Nitpick comments (2)
apps/web/scripts/dev.mjs (1)

20-27: Fallback port is not verified for availability.

If both port 3000 and 3002 are in use, the script will attempt to start on 3002 and fail with an unclear error from Next.js. Consider checking the fallback port as well.

🛠️ Suggested improvement
 async function start() {
   const preferredPort = 3000;
   const fallbackPort = 3002;
-  const port = (await isPortFree(preferredPort)) ? preferredPort : fallbackPort;
+  let port = preferredPort;
+  if (!(await isPortFree(preferredPort))) {
+    if (!(await isPortFree(fallbackPort))) {
+      console.error(`[web] Both ports ${preferredPort} and ${fallbackPort} are in use.`);
+      process.exit(1);
+    }
+    port = fallbackPort;
+  }

   if (port !== preferredPort) {
     console.log(`[web] Port ${preferredPort} is in use, starting on ${fallbackPort}.`);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/scripts/dev.mjs` around lines 20 - 27, In start(), the fallbackPort
(3002) is not verified so if both preferredPort (3000) and fallbackPort are
occupied the script will still pick 3002 and Next.js will error; update the
logic around isPortFree, preferredPort, fallbackPort and port to check
fallbackPort as well (or iterate to find the next free port), set port to the
first free port returned by isPortFree, and when no port is available log a
clear error (including which ports were tried) and exit gracefully instead of
proceeding to start. Ensure references to start(), preferredPort, fallbackPort,
isPortFree and port are used so the change is localized to this function.
apps/web/src/app/api/auth/demo/route.ts (1)

12-31: Deduplicate Convex URL validation logic into a shared utility.

isLikelyPlaceholder/getConvexUrl here duplicates logic already present in apps/web/src/lib/convex.ts (Lines 5–20). Consolidating avoids drift and inconsistent acceptance rules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/auth/demo/route.ts` around lines 12 - 31, This
duplicates the Convex URL validation (isLikelyPlaceholder and getConvexUrl);
remove these local implementations and import the shared validation utilities
from the existing convex utility module, replacing local calls with the shared
getConvexUrl/isLikelyPlaceholder exports (or a single exported function) so all
code uses the same validation logic; ensure you update any callers in this file
to use the imported symbol(s) and remove the duplicated functions to avoid
divergence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/app/api/auth/demo/route.ts`:
- Around line 50-55: The response currently leaks result.token in the JSON body
(NextResponse.json) while also setting the same token as an httpOnly cookie;
remove the token property from the JSON payload and only return non-sensitive
fields (e.g., name, email, isDemo) so client-side JS cannot read the session
token, and ensure the existing cookie-setting logic that writes the token with
httpOnly=true continues to use result.token for the cookie (reference the
NextResponse.json call that builds response and the cookie-set code that writes
the token).
- Around line 33-65: The POST handler sets an auth cookie without validating
request origin, allowing CSRF/session overwrite; update the POST function to
validate the incoming request origin or same-site header before issuing the
cookie: retrieve the request (e.g., from NextRequest in the POST handler), check
the Origin and/or Referer against an allowlist (or enforce same-site via
checking request.headers.get("origin")/get("referer") and compare to
getConvexUrl() or your app origin), and return a 403/400 response if invalid;
only after passing this validation proceed to call ConvexHttpClient, create the
NextResponse, and set the AUTH_SESSION_COOKIE with existing options (httpOnly,
secure, sameSite, maxAge) to prevent cross-site login.

In `@apps/web/src/lib/convex.ts`:
- Around line 25-27: The code creates convexClient using ConvexReactClient and
falls back to a real deployment URL ("https://gentle-otter-123.convex.cloud")
when hasValidConvexUrl is false; replace that hardcoded external fallback with a
safe local/no-op option (e.g., an empty string, localhost dev URL, or
throw/disable client) so queries/mutations are never routed to an external
deployment; update the convexClient initialization logic around
ConvexReactClient, hasValidConvexUrl, and convexUrl to use the safe fallback and
ensure any callers handle the disabled client appropriately.

In `@package.json`:
- Around line 14-15: The build:legacy npm script uses unscoped package filters
("shared", "reveal-engine") that don't match the workspace package names,
causing filter mismatches; update the build:legacy script (the "build:legacy"
entry in package.json) to use the correct scoped package names
(`@slide-handout/shared` and `@slide-handout/reveal-engine`) so it mirrors the
"build" script's filters and runs pnpm --filter `@slide-handout/shared` build &&
pnpm --filter `@slide-handout/reveal-engine` build (keeping the existing web and
powerpoint-addin filters).

---

Nitpick comments:
In `@apps/web/scripts/dev.mjs`:
- Around line 20-27: In start(), the fallbackPort (3002) is not verified so if
both preferredPort (3000) and fallbackPort are occupied the script will still
pick 3002 and Next.js will error; update the logic around isPortFree,
preferredPort, fallbackPort and port to check fallbackPort as well (or iterate
to find the next free port), set port to the first free port returned by
isPortFree, and when no port is available log a clear error (including which
ports were tried) and exit gracefully instead of proceeding to start. Ensure
references to start(), preferredPort, fallbackPort, isPortFree and port are used
so the change is localized to this function.

In `@apps/web/src/app/api/auth/demo/route.ts`:
- Around line 12-31: This duplicates the Convex URL validation
(isLikelyPlaceholder and getConvexUrl); remove these local implementations and
import the shared validation utilities from the existing convex utility module,
replacing local calls with the shared getConvexUrl/isLikelyPlaceholder exports
(or a single exported function) so all code uses the same validation logic;
ensure you update any callers in this file to use the imported symbol(s) and
remove the duplicated functions to avoid divergence.
🪄 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: 69bcbf68-a942-4f50-8782-91c76255e89d

📥 Commits

Reviewing files that changed from the base of the PR and between 345ca14 and 5b56c7f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • README.md
  • apps/powerpoint-addin/package.json
  • apps/web/.env.local.example
  • apps/web/package.json
  • apps/web/scripts/dev.mjs
  • apps/web/src/app/api/auth/demo/route.ts
  • apps/web/src/app/auth/demo/page.tsx
  • apps/web/src/components/ConvexClientProvider.tsx
  • apps/web/src/components/ThemeToggle.tsx
  • apps/web/src/lib/convex.ts
  • convex/package.json
  • package.json
✅ Files skipped from review due to trivial changes (2)
  • apps/web/.env.local.example
  • convex/package.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/powerpoint-addin/package.json
  • apps/web/src/components/ThemeToggle.tsx
  • README.md
  • apps/web/package.json
  • apps/web/src/app/auth/demo/page.tsx

Comment on lines +33 to +65
export async function POST() {
const convexUrl = getConvexUrl();

if (!convexUrl) {
return NextResponse.json(
{ error: "NEXT_PUBLIC_CONVEX_URL fehlt oder ist ungueltig." },
{ status: 500 }
);
}

try {
const client = new ConvexHttpClient(convexUrl);
const result = await client.mutation(api.auth.login, {
email: DEMO_EMAIL,
password: DEMO_PASSWORD,
});

const response = NextResponse.json({
token: result.token,
name: result.name ?? "Demo Presenter",
email: result.email ?? DEMO_EMAIL,
isDemo: result.isDemo ?? true,
});

response.cookies.set({
name: AUTH_SESSION_COOKIE,
value: result.token,
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: AUTH_SESSION_MAX_AGE_SECONDS,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add origin validation to prevent login CSRF/session overwrite.

This endpoint sets an auth cookie but does not validate request origin. A cross-site POST can force a victim into a demo session.

Suggested fix
-export async function POST() {
+export async function POST(request: Request) {
+  const origin = request.headers.get("origin");
+  const requestOrigin = new URL(request.url).origin;
+  if (!origin || origin !== requestOrigin) {
+    return NextResponse.json({ error: "Invalid origin." }, { status: 403 });
+  }
+
   const convexUrl = getConvexUrl();
📝 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
export async function POST() {
const convexUrl = getConvexUrl();
if (!convexUrl) {
return NextResponse.json(
{ error: "NEXT_PUBLIC_CONVEX_URL fehlt oder ist ungueltig." },
{ status: 500 }
);
}
try {
const client = new ConvexHttpClient(convexUrl);
const result = await client.mutation(api.auth.login, {
email: DEMO_EMAIL,
password: DEMO_PASSWORD,
});
const response = NextResponse.json({
token: result.token,
name: result.name ?? "Demo Presenter",
email: result.email ?? DEMO_EMAIL,
isDemo: result.isDemo ?? true,
});
response.cookies.set({
name: AUTH_SESSION_COOKIE,
value: result.token,
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: AUTH_SESSION_MAX_AGE_SECONDS,
});
export async function POST(request: Request) {
const origin = request.headers.get("origin");
const requestOrigin = new URL(request.url).origin;
if (!origin || origin !== requestOrigin) {
return NextResponse.json({ error: "Invalid origin." }, { status: 403 });
}
const convexUrl = getConvexUrl();
if (!convexUrl) {
return NextResponse.json(
{ error: "NEXT_PUBLIC_CONVEX_URL fehlt oder ist ungueltig." },
{ status: 500 }
);
}
try {
const client = new ConvexHttpClient(convexUrl);
const result = await client.mutation(api.auth.login, {
email: DEMO_EMAIL,
password: DEMO_PASSWORD,
});
const response = NextResponse.json({
token: result.token,
name: result.name ?? "Demo Presenter",
email: result.email ?? DEMO_EMAIL,
isDemo: result.isDemo ?? true,
});
response.cookies.set({
name: AUTH_SESSION_COOKIE,
value: result.token,
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: AUTH_SESSION_MAX_AGE_SECONDS,
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/auth/demo/route.ts` around lines 33 - 65, The POST
handler sets an auth cookie without validating request origin, allowing
CSRF/session overwrite; update the POST function to validate the incoming
request origin or same-site header before issuing the cookie: retrieve the
request (e.g., from NextRequest in the POST handler), check the Origin and/or
Referer against an allowlist (or enforce same-site via checking
request.headers.get("origin")/get("referer") and compare to getConvexUrl() or
your app origin), and return a 403/400 response if invalid; only after passing
this validation proceed to call ConvexHttpClient, create the NextResponse, and
set the AUTH_SESSION_COOKIE with existing options (httpOnly, secure, sameSite,
maxAge) to prevent cross-site login.

Comment on lines +50 to +55
const response = NextResponse.json({
token: result.token,
name: result.name ?? "Demo Presenter",
email: result.email ?? DEMO_EMAIL,
isDemo: result.isDemo ?? true,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not return the session token in JSON when using HttpOnly cookies.

Line 51 exposes the token to client-side JS, which defeats the security benefit of storing it as httpOnly on Lines 57–60.

Suggested fix
-    const response = NextResponse.json({
-      token: result.token,
-      name: result.name ?? "Demo Presenter",
-      email: result.email ?? DEMO_EMAIL,
-      isDemo: result.isDemo ?? true,
-    });
+    const response = NextResponse.json({
+      name: result.name ?? "Demo Presenter",
+      email: result.email ?? DEMO_EMAIL,
+      isDemo: result.isDemo ?? true,
+    });
📝 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 response = NextResponse.json({
token: result.token,
name: result.name ?? "Demo Presenter",
email: result.email ?? DEMO_EMAIL,
isDemo: result.isDemo ?? true,
});
const response = NextResponse.json({
name: result.name ?? "Demo Presenter",
email: result.email ?? DEMO_EMAIL,
isDemo: result.isDemo ?? true,
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/auth/demo/route.ts` around lines 50 - 55, The response
currently leaks result.token in the JSON body (NextResponse.json) while also
setting the same token as an httpOnly cookie; remove the token property from the
JSON payload and only return non-sensitive fields (e.g., name, email, isDemo) so
client-side JS cannot read the session token, and ensure the existing
cookie-setting logic that writes the token with httpOnly=true continues to use
result.token for the cookie (reference the NextResponse.json call that builds
response and the cookie-set code that writes the token).

Comment on lines 25 to 27
export const convexClient = new ConvexReactClient(
convexUrl ?? "https://placeholder.convex.cloud"
hasValidConvexUrl ? convexUrl : "https://gentle-otter-123.convex.cloud"
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid using a real Convex deployment as fallback.

On Line 26, falling back to https://gentle-otter-123.convex.cloud can route unintended queries/mutations to an external deployment when env config is invalid. That creates avoidable data leakage risk.

Suggested fix
 export const convexClient = new ConvexReactClient(
-  hasValidConvexUrl ? convexUrl : "https://gentle-otter-123.convex.cloud"
+  hasValidConvexUrl ? convexUrl : "https://convex.invalid"
 );
📝 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
export const convexClient = new ConvexReactClient(
convexUrl ?? "https://placeholder.convex.cloud"
hasValidConvexUrl ? convexUrl : "https://gentle-otter-123.convex.cloud"
);
export const convexClient = new ConvexReactClient(
hasValidConvexUrl ? convexUrl : "https://convex.invalid"
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/convex.ts` around lines 25 - 27, The code creates
convexClient using ConvexReactClient and falls back to a real deployment URL
("https://gentle-otter-123.convex.cloud") when hasValidConvexUrl is false;
replace that hardcoded external fallback with a safe local/no-op option (e.g.,
an empty string, localhost dev URL, or throw/disable client) so
queries/mutations are never routed to an external deployment; update the
convexClient initialization logic around ConvexReactClient, hasValidConvexUrl,
and convexUrl to use the safe fallback and ensure any callers handle the
disabled client appropriately.

Comment thread package.json
Comment on lines +14 to +15
"build": "turbo run build --filter=@slide-handout/shared --filter=@slide-handout/reveal-engine --filter=web --filter=powerpoint-addin",
"build:legacy": "pnpm --filter reveal-engine build && pnpm --filter shared build && pnpm --filter web build && pnpm --filter powerpoint-addin build",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify package names in each workspace to confirm filter consistency

echo "=== Checking package names in workspaces ==="
for pkg in packages/shared packages/reveal-engine apps/web apps/powerpoint-addin; do
  if [ -f "$pkg/package.json" ]; then
    name=$(cat "$pkg/package.json" | grep -m1 '"name"' | sed 's/.*: *"\([^"]*\)".*/\1/')
    echo "$pkg -> $name"
  fi
done

Repository: EinfachMxrc/handout

Length of output: 261


Fix inconsistent package filter names in build:legacy script.

The build script correctly uses scoped package names (@slide-handout/shared, @slide-handout/reveal-engine), but build:legacy uses bare names (reveal-engine, shared) that don't match the actual package names defined in the workspace. This will likely cause build failures or unexpected behavior. Update build:legacy to use the correct scoped package names:

"build:legacy": "pnpm --filter `@slide-handout/shared` build && pnpm --filter `@slide-handout/reveal-engine` build && pnpm --filter web build && pnpm --filter powerpoint-addin build"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 14 - 15, The build:legacy npm script uses unscoped
package filters ("shared", "reveal-engine") that don't match the workspace
package names, causing filter mismatches; update the build:legacy script (the
"build:legacy" entry in package.json) to use the correct scoped package names
(`@slide-handout/shared` and `@slide-handout/reveal-engine`) so it mirrors the
"build" script's filters and runs pnpm --filter `@slide-handout/shared` build &&
pnpm --filter `@slide-handout/reveal-engine` build (keeping the existing web and
powerpoint-addin filters).

@EinfachMxrc EinfachMxrc merged commit f5d69ec into main Apr 14, 2026
3 checks passed
@EinfachMxrc EinfachMxrc deleted the refactor/clean-up-code branch April 14, 2026 22:20
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.

3 participants