Skip to content

Feat: Advanced Story controls "Pro panel" with presets and presets sharing implemented along with UI/UX updated as recommended#439

Open
rohits1450 wants to merge 36 commits into
IndieHub25:mainfrom
rohits1450:feature/pro-panel
Open

Feat: Advanced Story controls "Pro panel" with presets and presets sharing implemented along with UI/UX updated as recommended#439
rohits1450 wants to merge 36 commits into
IndieHub25:mainfrom
rohits1450:feature/pro-panel

Conversation

@rohits1450
Copy link
Copy Markdown

@rohits1450 rohits1450 commented Mar 1, 2026


Description

Issue Reference (Fixes #323):

Summary of Changes (high-level):

  • Implemented the “Pro Panel” feature for advanced story configuration.
  • Grouped 70+ parameters into 9 collapsible, logically organized categories with sensible defaults.
  • Followed the exact font colouring, ui design, and colour palette as mentioned in the preview pics
  • Added full preset management: users can save, name, reuse, export, and import parameter presets as JSON.
  • Built robust state management and persistence using Zustand with local storage.
  • Enhanced UX with progressive disclosure, accessibility, and responsive design.
  • Provided clear visual feedback and error handling for all preset operations.
  • Integrated all parameter changes directly into the story generation workflow

Context:

  • Solves the need for power users to efficiently manage and share complex story configurations.
  • Improves usability and maintainability for both new and advanced users.
  • Enables sharing of presets in Discord and documentation, supporting community collaboration.

Type of Change

  • Frontend / UI
  • Testing
  • Bug Fix
  • Refactor

Technical Checklist

Frontend / UX / Accessibility

  • My changes follow the Progressive Disclosure (accordion / step‑based) UX where applicable.
  • I checked keyboard navigation and focus states for interactive elements I touched.
  • I ensured accessible labels and semantic HTML for new UI.

Backend / Database

  • I ran backend startup locally without runtime errors.

Security & Privacy

  • No API keys, private keys, secrets, or .env files are committed.
  • I avoided logging sensitive data (tokens, secrets, full payloads with PII).
  • I considered common web vulnerabilities (XSS, CSRF, SSRF, injection) in my changes.

Code Quality

  • I ran npm run lint (or equivalent) and resolved reported issues.
  • I ran available tests for the areas I changed (frontend, backend, or contracts).
  • I kept functions/components focused and avoided large “god” modules where possible.
  • I updated or added types where necessary instead of using any by default.

Testing Evidence

Environment: local

Commands:

  • npm run test
  • npm run dev

Results / Logs:

Manual test steps:

  • Navigated to /pro-panel in browser.
  • Checked accessibility and keyboard navigation.
  • Tested saving, loading, exporting, and importing presets.

Visual Proof (for UI / UX changes)

UI Updation

Screenshot 2026-03-01 103458 Screenshot 2026-03-01 103512

Dossier Style

Screenshot 2026-03-01 103525

Accordion type

Screenshot 2026-03-01 103610 Screenshot 2026-03-01 103620

Presets Save, Load and Transfer

Screenshot 2026-03-01 103628 Screenshot 2026-03-01 103635 Screenshot 2026-03-01 103644

Contributor Status

  • I am an OSCG’26 contributor.

Review & Impact

Breaking Changes

  • This PR introduces a breaking change (API / contract / DB).

Dependencies

  • I added or upgraded dependencies.

Backward Compatibility / Migrations

  • Existing users can continue using GroqTales without manual steps.

Final Acknowledgements (Mandatory or will be marked invalid)

  • I confirm that the information and code in this PR are my original work or appropriately credited, and I have the right to contribute them under this repository’s license.
  • I understand that by submitting this PR, I take full responsibility and accountability for the changes I am proposing.
  • I have read and agree to follow the project’s Code of Conduct, Security Policy, and Contribution Guidelines for all discussions and follow‑up on this PR.

Summary by CodeRabbit

  • New Features

    • Full Pro Panel editor: genre grid, dossier navigator, sidebar panels, many configurable sections and compact controls
    • Presets: built‑in presets, save/load/delete, import/export with validation and persistence
    • Story generation: server endpoint accepts Pro Panel configs with input validation, detailed errors, loading state, and toasts
  • UI/Style Updates

    • Noir/comic visual overhaul: new fonts, textures, animations, utilities and improved responsive image sizing
  • Chores

    • Updated ignore entries for demo fonts and generated public assets

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 1, 2026

@rohits1450 is attempting to deploy a commit to the Drago's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added Enhancement New feature or request in progress Feature Request or proposal for a new feature size/XL labels Mar 1, 2026
@github-actions github-actions Bot requested a review from Drago-03 March 1, 2026 05:27
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a full Pro Panel feature: new Pro Panel UI, controls, persisted Zustand store, Zod schemas and defaults, presets import/export, backend integration (API + groq-service), styling, dependency updates, and minor UI/asset ignore tweaks.

Changes

Cohort / File(s) Summary
API & Service
app/api/groq/route.ts, lib/groq-service.ts
API accepts proConfig + title, validates with ParametersSchema, and delegates to generateStoryWithProConfig; groq-service composes system/user prompts from ProParameters, maps model settings, calls Groq, and enforces output checks.
Schemas & Defaults
lib/schemas/proPanelSchemas.ts, lib/constants/proPanelDefaults.ts
Adds comprehensive Zod schemas/types and a large DEFAULT_PARAMETERS/CATEGORY_CONFIG with built-in presets and genre metadata.
State & Persistence
src/store/proPanelStore.ts
New persisted Zustand store useProPanelStore with parameters, presets, UI state, storyInput, selectors, mutation actions, import/export, and change tracking.
Pro Panel Page & Barrel
app/pro-panel/page.tsx, components/pro-panel/index.ts
New Next.js Pro Panel page and a barrel export aggregating pro-panel components.
Core UI Components
components/pro-panel/ProPanel.tsx, components/pro-panel/DossierFormPanel.tsx, components/pro-panel/MissionNavigator.tsx
Main Pro Panel orchestrator, dossier form, and category navigator with responsive modes, animations, and category switching.
Controls & Sections
components/pro-panel/controls/index.tsx, components/pro-panel/sections/*, components/pro-panel/sections/index.ts
Reusable control primitives and nine section components (StoryStructure, Character, World, ToneStyle, Theme, ModelSettings, Visual, Length, Advanced).
Genre & Presets UI
components/pro-panel/GenreCard.tsx, components/pro-panel/GenreGrid.tsx, components/pro-panel/PresetManager.tsx, components/pro-panel/ImportExportDialog.tsx
Genre grid, preset manager (save/load/delete), and import/export dialog with copy/download/import and validation.
Generation CTA
components/pro-panel/GenerateStoryButton.tsx
Client button posts prompt + proConfig to /api/groq, handles loading, errors, success, and resets input on success.
Sidebar & Story Input
components/pro-panel/SidebarPanels.tsx, components/pro-panel/StoryInputSection.tsx
Location, style, content, inspiration panels and story prompt + quick-config wired to the store.
Icon Map & Utilities
components/pro-panel/iconMap.ts
Centralized ICON_MAP mapping string IDs to Lucide icons.
UI Primitives Updates
components/ui/slider.tsx, components/ui/tooltip.tsx
Slider visuals adjusted; TooltipTrigger ref now forwards to a span, supports asChild, and TooltipContent accepts new side prop.
Styling & Theme
app/globals.css, tailwind.config.ts
Large noir/comic CSS additions (fonts, textures, animations) and Tailwind theme extensions (colors, fonts, shadows).
Deps, Config & Ignore
package.json, .gitignore
Dependency updates (Radix, zod, zustand, tooling), package manager bump, and .gitignore additions for font demo and /public/sitemap*.xml, /public/robots.txt.
Minor UI tweaks
components/header.tsx, components/splash-screen.tsx
Added sizes attributes to Next.js Image components.
Supabase client
lib/supabase/client.ts
Centralized SUPABASE_URL and SUPABASE_ANON_KEY constants for client creation.
Misc UI
components/madhava-helpbot.tsx
Added isMounted guard to avoid hydration mismatch for timestamp rendering.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Pro Panel UI
    participant Store as Zustand Store
    participant API as Server (/api/groq)
    participant Service as groq-service
    participant Groq as Groq API

    User->>UI: Configure parameters, enter prompt, click Generate
    UI->>Store: Read storyInput & proConfig
    UI->>API: POST /api/groq { action, prompt, title, proConfig }
    API->>API: Validate proConfig with ParametersSchema
    API->>Service: Call generateStoryWithProConfig(prompt, proConfig, {title})
    Service->>Service: Build system prompt & user prompt from ProParameters
    Service->>Groq: POST chat completions with mapped modelSettings
    Groq-->>Service: Return generated text
    Service->>Service: Run output safety checks
    Service-->>API: Return generation result
    API-->>UI: 200 + story content
    UI->>Store: Reset storyInput (on success)
    UI-->>User: Display story and success toast
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

UI/UX, Hard, Needs Review

Suggested reviewers

  • Drago-03

Poem

🐰
I nibbled schemas soft and neat, then hopped through sliders bright,
I stacked the presets in my pouch and tuned the panel right.
I pressed the big red button — inked dossiers took flight,
I thumped my foot, I twitched my nose, and vanished in moonlight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title directly references the main feature ('Pro panel') and core capability ('presets and presets sharing'), matching the primary objectives from issue #323.
Description check ✅ Passed The PR description follows most template sections with clear summaries, checklist completion, testing evidence, visual proof, and required acknowledgements, though some template sections lack detail.
Linked Issues check ✅ Passed The implementation comprehensively addresses issue #323 requirements: groups 70+ parameters into 9 collapsible categories [lib/schemas/proPanelSchemas.ts, lib/constants/proPanelDefaults.ts], provides preset management [src/store/proPanelStore.ts], supports export/import [components/pro-panel/ImportExportDialog.tsx], and includes state management with localStorage persistence and accessibility features.
Out of Scope Changes check ✅ Passed All code changes directly support Pro Panel feature delivery: new Pro Panel components, state management, schemas, styling, and API integration. Minor changes to existing files (image sizes, hydration fix) are reasonable collateral updates.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

❤️ Share

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

@rohits1450 rohits1450 changed the title Feature/pro panel Feat: Advanced Story controls "Pro panel" with presets and presets sharing implemented along with UI/UX updated as recommended Mar 1, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (5)
components/pro-panel/CategorySection.tsx-66-80 (1)

66-80: ⚠️ Potential issue | 🟡 Minor

Keyboard handler lacks stopPropagation, causing potential accordion toggle.

The onClick handler uses handleReset which calls both stopPropagation() and preventDefault(). However, the onKeyDown handler only calls preventDefault() and directly invokes resetCategory(id). This inconsistency may cause the accordion to toggle when the reset button is activated via keyboard.

🔧 Proposed fix
             onKeyDown={(e) => {
               if (e.key === 'Enter' || e.key === ' ') {
+                e.stopPropagation();
                 e.preventDefault();
                 resetCategory(id);
               }
             }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/CategorySection.tsx` around lines 66 - 80, The keyboard
handler on the span currently calls only e.preventDefault() and
resetCategory(id), which can let the accordion toggle; update the onKeyDown for
the span to mirror handleReset by calling e.preventDefault() and
e.stopPropagation() before invoking resetCategory(id) (or simply call
handleReset) so keyboard activation behaves identically to the click handler;
change the onKeyDown closure around resetCategory(id) in the span to include
e.stopPropagation() (or delegate to handleReset) to prevent event bubbling.
components/pro-panel/controls/index.tsx-429-435 (1)

429-435: ⚠️ Potential issue | 🟡 Minor

Give the tag remove button an accessible name.

The × icon-only button has no accessible label for assistive tech.

🧩 Suggested fix
             <button
               type="button"
               onClick={() => removeTag(index)}
+              aria-label={`Remove tag ${tag}`}
+              title={`Remove tag ${tag}`}
               className="hover:text-gray-300 ml-0.5"
             >
               ×
             </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/controls/index.tsx` around lines 429 - 435, The tag
remove button currently renders only the "×" character and lacks an accessible
name; update the button in the tag list (the element that calls
removeTag(index)) to include an accessible label (for example add
aria-label={`Remove tag ${tag}`} or aria-label="Remove tag" or include
visually-hidden text) so assistive technologies can announce its purpose; ensure
the aria-label references the tag value if available (use the same tag variable
used when rendering the tag) and keep the onClick handler removeTag(index)
unchanged.
lib/groq-service.ts-944-945 (1)

944-945: ⚠️ Potential issue | 🟡 Minor

Validate/sanitize options.title before embedding into system instructions.

prompt is sanitized/validated, but title is injected directly into the system prompt path.

🔒 Suggested fix
+    let sanitizedTitle = options.title;
+    if (options.title) {
+      const { sanitized } = sanitizeInput(options.title);
+      const titleValidation = validateInput(sanitized);
+      if (!titleValidation.isValid) {
+        logSecurityEvent({
+          type: 'injection_attempt',
+          details: { field: 'title', reason: titleValidation.reason, pattern: titleValidation.matchedPattern },
+        });
+        throw new Error(`Invalid input for title: ${titleValidation.reason}`);
+      }
+      sanitizedTitle = sanitized;
+    }
...
-    const systemPromptContent = buildProPanelSystemPrompt(proConfig, options.title);
+    const systemPromptContent = buildProPanelSystemPrompt(proConfig, sanitizedTitle);

Also applies to: 1025-1026, 1076-1076

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

In `@lib/groq-service.ts` around lines 944 - 945, The title from options
(options.title) is being injected directly into system prompts (calls to
buildProPanelSystemPrompt and subsequent buildHardenedSystemPrompt at the
occurrences near the buildProPanelSystemPrompt calls), so sanitize and validate
it before use: trim and enforce a max length, strip or escape newlines/control
characters, remove or encode any prompt-injection tokens, and reject/replace
unsafe values; reuse the existing prompt-sanitization utility (e.g.,
sanitizePrompt or similar) or add a small helper (sanitizeTitle) and pass the
sanitizedTitle into buildProPanelSystemPrompt wherever options.title is
currently used (including the other occurrences reported around the 1025–1026
and 1076 lines).
components/pro-panel/DossierFormPanel.tsx-77-83 (1)

77-83: ⚠️ Potential issue | 🟡 Minor

Hide decorative elements from assistive tech.

These nodes are decorative-only but currently exposed to screen readers.

♿ Suggested fix
-<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-48 h-48 opacity-[0.04] rotate-[-15deg] pointer-events-none select-none">
+<div aria-hidden="true" className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-48 h-48 opacity-[0.04] rotate-[-15deg] pointer-events-none select-none">

-<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 -rotate-[30deg] pointer-events-none select-none whitespace-nowrap">
+<div aria-hidden="true" className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 -rotate-[30deg] pointer-events-none select-none whitespace-nowrap">

-<div className="absolute top-8 right-16 w-16 h-16 rounded-full bg-yellow-900/[0.06] pointer-events-none" />
+<div aria-hidden="true" className="absolute top-8 right-16 w-16 h-16 rounded-full bg-yellow-900/[0.06] pointer-events-none" />

-<div className="absolute bottom-20 left-12 w-10 h-10 rounded-full bg-yellow-900/[0.05] pointer-events-none" />
+<div aria-hidden="true" className="absolute bottom-20 left-12 w-10 h-10 rounded-full bg-yellow-900/[0.05] pointer-events-none" />

-<div className="absolute top-1/3 right-8 w-6 h-6 rounded-full bg-yellow-900/[0.04] pointer-events-none" />
+<div aria-hidden="true" className="absolute top-1/3 right-8 w-6 h-6 rounded-full bg-yellow-900/[0.04] pointer-events-none" />

-<div className="absolute bottom-4 right-4 text-4xl opacity-[0.06] rotate-[20deg] pointer-events-none select-none">
+<div aria-hidden="true" className="absolute bottom-4 right-4 text-4xl opacity-[0.06] rotate-[20deg] pointer-events-none select-none">

Also applies to: 89-92, 197-199

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

In `@components/pro-panel/DossierFormPanel.tsx` around lines 77 - 83, The
decorative watermark elements (the two absolute divs containing meta.watermark
and the span with class "font-marker text-black/[0.04] text-6xl
tracking-[0.3em]") are currently exposed to assistive tech; mark them as purely
decorative by adding aria-hidden="true" (or role="presentation") to those
container elements (the divs and the inner span) and remove any focusable
attributes so screen readers ignore them; apply the same change to the other
watermark instances referenced (the nodes around the other ranges) to ensure all
decorative-only nodes are hidden from assistive technology.
components/pro-panel/ProPanel.tsx-82-107 (1)

82-107: ⚠️ Potential issue | 🟡 Minor

Add cleanup for animation timeouts to prevent memory leaks.

The nested setTimeout calls could execute after component unmount, causing potential state updates on unmounted components. Consider using useEffect cleanup or refs to track timeout IDs.

🛡️ Proposed fix with cleanup
+import React, { useState, useRef, useCallback, useEffect } from 'react';
+
 export function ProPanel() {
   // ... existing state ...
+  const timeoutRefs = useRef<NodeJS.Timeout[]>([]);
+
+  useEffect(() => {
+    return () => {
+      timeoutRefs.current.forEach(clearTimeout);
+    };
+  }, []);

   const handleCategoryChange = useCallback((newCategory: CategoryKey) => {
     if (newCategory === selectedCategory || isPageTurning) return;
     setIsPageTurning(true);
     setSelectedCategory(newCategory);

     if (pageRef.current) {
       pageRef.current.classList.remove('dossier-page-enter');
       pageRef.current.classList.add('dossier-page-exit');
     }

-    setTimeout(() => {
+    const t1 = setTimeout(() => {
       setDisplayedCategory(newCategory);
       if (pageRef.current) {
         pageRef.current.classList.remove('dossier-page-exit');
         pageRef.current.classList.add('dossier-page-enter');
       }
-      setTimeout(() => {
+      const t2 = setTimeout(() => {
         setIsPageTurning(false);
         if (pageRef.current) {
           pageRef.current.classList.remove('dossier-page-enter');
         }
       }, 700);
+      timeoutRefs.current.push(t2);
     }, 400);
+    timeoutRefs.current.push(t1);
   }, [selectedCategory, isPageTurning]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/ProPanel.tsx` around lines 82 - 107, The
handleCategoryChange function uses nested setTimeouts and can update state after
unmount; fix by tracking both timeout IDs in a ref (e.g., timeoutsRef) and
clearing them before scheduling new timeouts and in a useEffect cleanup: on
unmount clearTimeout for stored IDs and also clear any existing timeouts at top
of handleCategoryChange before setting new ones; ensure you still call
setDisplayedCategory, setSelectedCategory, and setIsPageTurning in the same
order but only after scheduling timeouts that are tracked and cancelable, and
reference pageRef as before.
🧹 Nitpick comments (20)
.gitignore (1)

63-65: Consider using more generic patterns for font demo files.

The specific entries address the immediate need to ignore accidentally committed font files. However, using more generic patterns would better prevent similar issues in the future if other font demos or temporary files are used during development.

♻️ Alternative pattern suggestions

Consider replacing with more generic patterns:

 # Font demo files
-another-danger-rohit/
-another-danger-rohit.zip
+/fonts/demo/
+/font-demos/
+*-font-demo/
+*-font-demo.zip

Or, if font-related files are commonly used temporarily:

 # Font demo files
-another-danger-rohit/
-another-danger-rohit.zip
+# Temporary font files and demos
+*.otf.zip
+*.ttf.zip
+/temp-fonts/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore around lines 63 - 65, The .gitignore currently lists two specific
entries ("another-danger-rohit/" and "another-danger-rohit.zip") which is
brittle; replace those specific entries with more generic glob patterns that
match font demo directories and font archive files (e.g., patterns for "*-demo"
or "font-demo" directories and "*.zip" archives related to fonts) so future font
demos or temporary font zip files are ignored without adding per-case entries;
update the gitignore entries that reference "another-danger-rohit/" and
"another-danger-rohit.zip" accordingly.
components/ui/tooltip.tsx (2)

36-42: Consider merging refs when asChild clones an element with an existing ref.

When the cloned child already has a ref attached, the current implementation replaces it rather than composing both refs. This could cause issues if consumers pass a child with its own ref.

For a fallback component this is likely acceptable, but worth noting if this pattern is used elsewhere.

♻️ Optional fix using a ref-merging utility
+// Helper to merge multiple refs
+function mergeRefs<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {
+  return (instance: T | null) => {
+    refs.forEach((ref) => {
+      if (typeof ref === 'function') ref(instance);
+      else if (ref) (ref as React.MutableRefObject<T | null>).current = instance;
+    });
+  };
+}

 const TooltipTrigger = React.forwardRef<HTMLSpanElement, TooltipTriggerProps>(
   ({ className, asChild = false, children, ...props }, ref) => {
     if (asChild && React.isValidElement(children)) {
+      const childRef = (children as React.ReactElement & { ref?: React.Ref<unknown> }).ref;
       return React.cloneElement(children as React.ReactElement, {
-        ref,
+        ref: mergeRefs(ref, childRef),
         className: cn((children as React.ReactElement).props?.className, className),
         ...props,
       });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/tooltip.tsx` around lines 36 - 42, The asChild branch in the
Tooltip component currently overwrites an existing child ref when calling
React.cloneElement; update this to merge refs instead: detect (children as
React.ReactElement).ref and compose it with the local ref passed into the clone
so both refs are updated (handle function refs and RefObject cases), then pass
that composed ref to React.cloneElement along with className and props; you can
implement a small utility composeRefs or use an existing ref-merge helper and
reference the symbols asChild, children, ref, React.cloneElement, and cn to
locate where to change.

58-71: The side prop is accepted but never applied to the element.

The side prop is destructured with a default value but not used. The CSS relies on data-[side=...] selectors for slide-in animations, but no data-side attribute is set on the div.

For a fallback component, this may be intentional for API compatibility. If so, consider adding a brief comment. Otherwise, apply the attribute to enable directional animations:

♻️ Option 1: Apply the data-side attribute
     return (
       <div
         ref={ref}
+        data-side={side}
         className={cn(
📝 Option 2: Document that side is non-functional in the fallback
 interface TooltipContentProps extends React.HTMLAttributes<HTMLDivElement> {
+  /** Accepted for API compatibility; positioning not implemented in fallback */
   sideOffset?: number;
+  /** Accepted for API compatibility; positioning not implemented in fallback */
   side?: 'top' | 'right' | 'bottom' | 'left';
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/tooltip.tsx` around lines 58 - 71, The TooltipContent component
destructures a side prop but never applies it, so the data-[side=...] CSS
selectors never match; update the root div inside TooltipContent to include the
data-side attribute set from the prop (e.g., data-side={side}) so the slide-in
animations work, keeping the existing default side = 'top' and preserving
className and {...props}; alternatively, if this component is intentionally a
non-directional fallback, add a short comment above TooltipContent explaining
side is accepted for API compatibility but not applied.
app/globals.css (3)

1-1: Stylelint prefers string notation for @import.

Static analysis flags import-notation — the linter expects a bare string rather than url(). While both are valid CSS, consider aligning with the project's Stylelint configuration.

💡 Use string notation
-@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@300;400;700&family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&family=Space+Grotesk:wght@300;400;500;600;700&family=Permanent+Marker&family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap');
+@import 'https://fonts.googleapis.com/css2?family=Comic+Neue:wght@300;400;700&family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&family=Space+Grotesk:wght@300;400;500;600;700&family=Permanent+Marker&family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` at line 1, The `@import` statement at the top of globals.css
uses url(...) which triggers the Stylelint import-notation rule; replace the
`@import` url('https://fonts.googleapis.com/...&display=swap') with string
notation `@import` 'https://fonts.googleapis.com/...&display=swap' so the linter
no longer flags it—update the top-level `@import` in globals.css accordingly.

724-730: Emoji filter utilities may behave inconsistently across platforms.

The filter: grayscale(1) brightness(100) technique to colorize emojis may produce different results across browsers and operating systems due to varying emoji rendering implementations. Consider testing on target platforms or using SVG icons for critical UI elements.

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

In `@app/globals.css` around lines 724 - 730, The emoji color filter classes
(.emoji-white and .emoji-dark) can render inconsistently across browsers/OSes;
test these classes on your target platforms and for any critical UI emoji
replace them with predictable assets (inline SVG or an icon font) instead of
relying on CSS filters; if you must keep the CSS approach, add cross-browser
fallbacks and vendor-prefixed properties and document the limitation in a
comment near .emoji-white and .emoji-dark so future maintainers know to prefer
SVG/icons for critical UI elements.

715-722: Fix currentColor casing per Stylelint rule.

Static analysis flags value-keyword-case — CSS keywords should be lowercase for consistency.

💡 Lowercase keyword
   .font-marker {
     font-family: var(--font-marker);
     font-weight: 700;
     transform: skewX(-4deg);
-    -webkit-text-stroke: 0.5px currentColor;
+    -webkit-text-stroke: 0.5px currentcolor;
     paint-order: stroke fill;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 715 - 722, Stylelint flags the keyword casing
in the .font-marker rule: change the CSS keyword "currentColor" to lowercase
"currentcolor" in the -webkit-text-stroke declaration (identify the .font-marker
selector and the -webkit-text-stroke: 0.5px currentColor line) so it conforms to
value-keyword-case; update that token and re-run the linter to verify the fix.
components/pro-panel/MissionNavigator.tsx (1)

130-137: Consider deriving the file count from sortedCategories.length.

The "9 ACTIVE CASE FILES" text is hardcoded. If categories are added or removed, this will become stale.

💡 Dynamic count
                     <span className="font-condensed text-[9px] text-gray-600 uppercase tracking-[0.2em]">
-                        9 ACTIVE CASE FILES
+                        {sortedCategories.length} ACTIVE CASE FILES
                     </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/MissionNavigator.tsx` around lines 130 - 137, The footer
currently hardcodes "9 ACTIVE CASE FILES"; change it to compute the count from
the component state/prop by using sortedCategories.length (the array used to
render categories) and render `${sortedCategories.length} ACTIVE CASE FILES` in
the footer stamp block inside MissionNavigator (replace the hardcoded text in
the div with className "px-5 py-4 border-t ..."). Ensure you reference the
existing sortedCategories variable so the displayed count updates whenever
categories change.
components/splash-screen.tsx (1)

53-53: Consider using responsive sizes to match the container's responsive dimensions.

The container uses w-32 h-32 md:w-48 md:h-48 (128px / 192px), but sizes="128px" is static. This may cause the browser to fetch a smaller image than needed on md+ screens, resulting in a blurry logo.

💡 Suggested responsive sizes
-              sizes="128px"
+              sizes="(min-width: 768px) 192px, 128px"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/splash-screen.tsx` at line 53, The Image's static sizes="128px"
will under-report required image width for larger screens; update the Image
sizes prop to a responsive value that matches the container classes (w-32 h-32
md:w-48 md:h-48), e.g. using a media condition so the browser requests 192px on
md+ and 128px otherwise (e.g. sizes="(min-width: 768px) 192px, 128px"); change
the sizes prop on the Image component in components/splash-screen.tsx
accordingly so the logo is fetched at the correct resolution.
app/pro-panel/page.tsx (1)

24-24: Avoid duplicating the halftone background layer.

ProPanel already renders a fixed halftone background (per components/pro-panel/ProPanel.tsx context), so this additional fixed layer stacks opacity and adds extra paint cost.

🎯 Suggested simplification
-      {/* Halftone dot background */}
-      <div className="fixed inset-0 z-0 halftone-bg pointer-events-none opacity-40" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/pro-panel/page.tsx` at line 24, Remove the duplicate fixed halftone
background layer in page.tsx: delete the <div className="fixed inset-0 z-0
halftone-bg pointer-events-none opacity-40" /> added in app/pro-panel/page.tsx
so the page relies on the existing halftone provided by the ProPanel component
(components/pro-panel/ProPanel.tsx); this prevents stacking opacity and extra
paint cost while preserving the intended background.
components/pro-panel/GenreCard.tsx (1)

15-25: Remove or render badge to keep the component API honest.

badge is exposed publicly but unused in rendering, which creates dead API surface and confusion for callers.

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

In `@components/pro-panel/GenreCard.tsx` around lines 15 - 25, The GenreCardProps
interface currently exposes a badge property that is never rendered; either
remove badge from GenreCardProps and update all call sites that pass badge, or
render it inside the GenreCard component by reading props.badge (e.g., as a
small label/chip next to title or image) and update the component's JSX to
consume badge; ensure you also update any tests/types/usages that expect the old
prop and keep the prop name consistent if you choose to render it.
src/store/proPanelStore.ts (1)

96-99: Use deep clones for parameter snapshots/resets.

Current top-level spreads are shallow and can retain nested array/object references across defaults, working state, and preset snapshots.

♻️ Suggested hardening refactor
+const cloneParameters = (params: ProParameters): ProParameters =>
+  JSON.parse(JSON.stringify(params));
...
-parameters: { ...DEFAULT_PARAMETERS },
+parameters: cloneParameters(DEFAULT_PARAMETERS),
...
-[category]: { ...DEFAULT_PARAMETERS[category] },
+[category]: cloneParameters(DEFAULT_PARAMETERS)[category],
...
-parameters: { ...DEFAULT_PARAMETERS },
+parameters: cloneParameters(DEFAULT_PARAMETERS),
...
-parameters: { ...state.parameters },
+parameters: cloneParameters(state.parameters),
...
-parameters: { ...preset.parameters },
+parameters: cloneParameters(preset.parameters),

Also applies to: 150-151, 158-158, 174-174, 194-194

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

In `@src/store/proPanelStore.ts` around lines 96 - 99, The state initialization
and any snapshot/reset assignments in proPanelStore.ts currently use shallow
spreads (e.g., parameters: { ...DEFAULT_PARAMETERS }, savedPresets:
initializeBuiltInPresets(), and other shallow copies around
currentPresetName/parameter snapshots), which can leak nested object/array
references; replace these shallow spreads with deep clones (use structuredClone
if available or a utility like lodash.cloneDeep) when assigning
DEFAULT_PARAMETERS, when creating/resetting parameters snapshots, and when
populating savedPresets returned by initializeBuiltInPresets() and any
subsequent preset save/load code (look for variables/methods named parameters,
DEFAULT_PARAMETERS, savedPresets, initializeBuiltInPresets, and any
reset/savePreset/loadPreset logic) so each state slot gets an independent deep
copy.
components/pro-panel/ProPanel.tsx (4)

256-280: Add aria-pressed for toggle button accessibility.

The view mode toggle buttons function as toggle controls. Adding aria-pressed helps screen readers communicate the current state.

♿ Proposed accessibility improvement
                   <button
                     type="button"
                     onClick={() => setViewMode('split')}
                     title="Split Panel View"
+                    aria-pressed={viewMode === 'split'}
                     className={`px-3 py-1.5 font-condensed text-[10px] font-bold uppercase tracking-wider flex items-center gap-1.5 transition-all ${
                       viewMode === 'split'
                         ? 'bg-[`#8a0000`] text-white'
                         : 'text-gray-400 hover:text-white'
                     }`}
                   >
                     <Columns className="w-3.5 h-3.5" /> Dossier
                   </button>
                   <button
                     type="button"
                     onClick={() => setViewMode('accordion')}
                     title="Accordion View"
+                    aria-pressed={viewMode === 'accordion'}
                     className={`px-3 py-1.5 font-condensed text-[10px] font-bold uppercase tracking-wider flex items-center gap-1.5 transition-all ${
                       viewMode === 'accordion'
                         ? 'bg-[`#8a0000`] text-white'
                         : 'text-gray-400 hover:text-white'
                     }`}
                   >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/ProPanel.tsx` around lines 256 - 280, The two view-mode
toggle buttons should expose their pressed state to assistive tech; update the
button elements that call setViewMode and check viewMode (the ones with
onClick={() => setViewMode('split')} and onClick={() =>
setViewMode('accordion')}) to include an aria-pressed attribute set to a boolean
expression (e.g., aria-pressed={viewMode === 'split'} and aria-pressed={viewMode
=== 'accordion'}) so screen readers can announce which toggle is active.

310-313: Simplify icon rendering with direct conditional.

The IIFE pattern is verbose. A simpler inline approach improves readability.

♻️ Simplified icon rendering
-                            {(() => {
-                              const IconComp = ICON_MAP[config.icon];
-                              return IconComp ? <IconComp className={`w-3.5 h-3.5 inline-block mr-1 ${isActive ? 'text-white' : 'text-gray-400'}`} /> : null;
-                            })()}
+                            {ICON_MAP[config.icon] && (
+                              React.createElement(ICON_MAP[config.icon], {
+                                className: `w-3.5 h-3.5 inline-block mr-1 ${isActive ? 'text-white' : 'text-gray-400'}`
+                              })
+                            )}

Or extract a small helper:

const CategoryIcon = ({ icon, isActive }: { icon: string; isActive: boolean }) => {
  const IconComp = ICON_MAP[icon];
  return IconComp ? <IconComp className={`w-3.5 h-3.5 inline-block mr-1 ${isActive ? 'text-white' : 'text-gray-400'}`} /> : null;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/ProPanel.tsx` around lines 310 - 313, Replace the IIFE
icon rendering in ProPanel with a direct conditional or small helper to improve
readability: locate the expression using ICON_MAP[config.icon] and isActive, and
either render it inline as {ICON_MAP[config.icon] ? <ICON_MAP[config.icon]
className={`w-3.5 h-3.5 inline-block mr-1 ${isActive ? 'text-white' :
'text-gray-400'}`} /> : null} or extract a CategoryIcon functional component
(props: icon, isActive) that looks up ICON_MAP[icon] and returns the icon
component with the same className logic; update the caller to use that helper
and remove the IIFE.

394-397: Convert social media icons to semantic buttons or links.

The social media icons are div elements with cursor-pointer. For accessibility, use <button> or <a> elements so they're keyboard-navigable and announced correctly by screen readers.

♿ Proposed accessibility fix
               <div className="flex gap-4">
-                <div className="w-8 h-8 border-2 border-[`#8a0000`] flex items-center justify-center text-[`#8a0000`] hover:bg-[`#8a0000`] hover:text-black cursor-pointer transition-colors font-condensed font-bold text-xs">FB</div>
-                <div className="w-8 h-8 border-2 border-[`#8a0000`] flex items-center justify-center text-[`#8a0000`] hover:bg-[`#8a0000`] hover:text-black cursor-pointer transition-colors font-condensed font-bold text-xs">TW</div>
-                <div className="w-8 h-8 border-2 border-[`#8a0000`] flex items-center justify-center text-[`#8a0000`] hover:bg-[`#8a0000`] hover:text-black cursor-pointer transition-colors font-condensed font-bold text-xs">IG</div>
+                <a href="#" aria-label="Facebook" className="w-8 h-8 border-2 border-[`#8a0000`] flex items-center justify-center text-[`#8a0000`] hover:bg-[`#8a0000`] hover:text-black transition-colors font-condensed font-bold text-xs">FB</a>
+                <a href="#" aria-label="Twitter" className="w-8 h-8 border-2 border-[`#8a0000`] flex items-center justify-center text-[`#8a0000`] hover:bg-[`#8a0000`] hover:text-black transition-colors font-condensed font-bold text-xs">TW</a>
+                <a href="#" aria-label="Instagram" className="w-8 h-8 border-2 border-[`#8a0000`] flex items-center justify-center text-[`#8a0000`] hover:bg-[`#8a0000`] hover:text-black transition-colors font-condensed font-bold text-xs">IG</a>
               </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/ProPanel.tsx` around lines 394 - 397, Replace the
non-semantic social icon <div> elements in ProPanel.tsx (the three social icon
elements currently rendering "FB", "TW", "IG") with semantic interactive
elements — use <button type="button"> when the icon triggers an in-app action or
<a href="..."> when it navigates externally — preserve the existing
classes/styles so layout/hover stays the same, add descriptive aria-labels
(e.g., aria-label="Share on Facebook" or aria-label="Open Twitter") and ensure
any onClick handlers remain attached; if using anchors for external sites,
include rel="noopener noreferrer" and target="_blank" as appropriate to match
original behavior.

66-70: STEP_TAGS steps 2-3 are always inactive.

The active property is hardcoded to false for steps 2 and 3. If this is a multi-step wizard, consider making this state-driven. If it's purely decorative for the current release, this is fine.

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

In `@components/pro-panel/ProPanel.tsx` around lines 66 - 70, STEP_TAGS currently
hardcodes active: false for steps 2 and 3; make the active state driven by the
component's current step (e.g., a prop or state like
currentStep/currentStepIndex in ProPanel) instead of hardcoding. Replace the
static array construction so each tag's active boolean is derived (for example
step === currentStep or step <= currentStep for progress-style activation) when
computing STEP_TAGS or when rendering tags in ProPanel, and ensure any
setters/props that change the wizard step update that currentStep value.
components/pro-panel/SidebarPanels.tsx (2)

353-355: Consider extracting formatLabel to a shared utility.

This helper function is useful and might be needed in other panel components. If it's duplicated elsewhere, consider moving it to a shared utilities file.

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

In `@components/pro-panel/SidebarPanels.tsx` around lines 353 - 355, Extract the
formatLabel function into a shared utility module (e.g., create or add to an
existing utils file) and export it so other components can reuse it; replace the
local formatLabel implementation in SidebarPanels.tsx with an import of the
shared function, update any other duplicated implementations across panel
components to import the shared utility instead, and run/type-check to ensure
signatures match (function name formatLabel, input string -> string) and adjust
imports where necessary.

31-42: Move GENRE_LOCATIONS outside the component to avoid recreation on each render.

The GENRE_LOCATIONS object is static and doesn't depend on props or state. Defining it inside the component causes unnecessary object recreation on every render.

♻️ Move constant outside component
+// Move outside component, before LocationDocksPanel function
+const GENRE_LOCATIONS: Record<string, { name: string; icon: React.ReactNode; locationImage: string }> = {
+    'fantasy': { name: 'ENCHANTED REALM', icon: <Castle className="w-8 h-8 text-white" />, locationImage: '/images/presets/fantasylocation.jpg' },
+    // ... rest of entries
+};

 export function LocationDocksPanel() {
     const parameters = useProPanelStore(selectParameters);
     const updateParameter = useProPanelStore((s) => s.updateParameter);
     const ui = useProPanelStore(selectUI);
     const world = parameters.world;

-    // Genre-specific location data
-    const GENRE_LOCATIONS: Record<string, { name: string; icon: React.ReactNode; locationImage: string }> = {
-        'fantasy': { name: 'ENCHANTED REALM', icon: <Castle className="w-8 h-8 text-white" />, locationImage: '/images/presets/fantasylocation.jpg' },
-        // ...
-    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/SidebarPanels.tsx` around lines 31 - 42, GENRE_LOCATIONS
is a static constant currently declared inside the component which causes it to
be recreated on every render; move the const GENRE_LOCATIONS: Record<string, {
name: string; icon: React.ReactNode; locationImage: string }> out of the
component body and place it at module top-level (above the component
declaration), preserving its shape and icon imports; if other modules need it,
consider exporting it as export const GENRE_LOCATIONS, otherwise keep it as a
top-level const so it’s created once.
lib/schemas/proPanelSchemas.ts (1)

501-511: Consider deriving CATEGORY_KEYS from schema for single source of truth.

The CATEGORY_KEYS array duplicates information already present in ParametersSchema. Deriving it programmatically prevents drift if schema keys change.

♻️ Derive from schema
 export type CategoryKey = keyof ProParameters;
-export const CATEGORY_KEYS: CategoryKey[] = [
-  'storyStructure',
-  'characters',
-  'world',
-  'toneStyle',
-  'theme',
-  'modelSettings',
-  'visual',
-  'length',
-  'advanced',
-] as const;
+export const CATEGORY_KEYS = Object.keys(ParametersSchema.shape) as CategoryKey[];

Note: This changes the order to be schema-definition order. If explicit ordering is needed, the current approach is fine.

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

In `@lib/schemas/proPanelSchemas.ts` around lines 501 - 511, CATEGORY_KEYS
currently duplicates keys from ParametersSchema; instead derive it
programmatically from the schema to keep a single source of truth. Replace the
hardcoded CATEGORY_KEYS with code that reads the keys from ParametersSchema
(e.g., using Object.keys or schema-specific utility on
ParametersSchema.properties) and casts them to CategoryKey[] (or as const) to
preserve typing; keep an explicit ordering step only if you still need a
different order than the schema provides. Ensure you update references to
CATEGORY_KEYS to accept the derived array and maintain type safety when casting
to CategoryKey[].
lib/constants/proPanelDefaults.ts (2)

676-688: Simplify the return type annotation.

The return type Omit<ProPreset, 'timestamp'> & { timestamp: number } is equivalent to ProPreset. The internal result variable already uses ProPreset correctly.

♻️ Proposed simplification
-export function initializeBuiltInPresets(): Record<string, Omit<ProPreset, 'timestamp'> & { timestamp: number }> {
+export function initializeBuiltInPresets(): Record<string, ProPreset> {
   const timestamp = Date.now();
   const result: Record<string, ProPreset> = {};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/constants/proPanelDefaults.ts` around lines 676 - 688, The function
initializeBuiltInPresets uses a return type union that is equivalent to
ProPreset; change its signature to return Record<string, ProPreset> instead of
Record<string, Omit<ProPreset, 'timestamp'> & { timestamp: number }>, leaving
the body (timestamp, result: Record<string, ProPreset>, loop over
BUILT_IN_PRESETS, and returned result) unchanged so the declared types align and
the signature is simplified.

110-115: Consider exporting CategoryConfig from schemas for consistency.

The CategoryConfig interface is defined here but CategoryKey is imported from schemas. For better cohesion, consider co-locating this interface with the schema types or explicitly documenting why it lives in defaults.

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

In `@lib/constants/proPanelDefaults.ts` around lines 110 - 115, CategoryConfig
currently lives alongside defaults while CategoryKey comes from the schemas
module; to keep types cohesive, move or re-export CategoryConfig from the
schemas barrel so both types come from the same place: add CategoryConfig to the
schemas type definitions (or export it from the schemas index), then update
usages to import CategoryConfig (and keep CategoryKey) from schemas; ensure
exported name matches "CategoryConfig" and update any imports that reference the
in-file interface to use the schemas export instead.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08cb1dc and 42911b4.

⛔ Files ignored due to path filters (38)
  • package-lock.json is excluded by !**/package-lock.json
  • public/comedy.jpg is excluded by !**/*.jpg
  • public/fantasy.png is excluded by !**/*.png
  • public/fonts/Another Danger - Demo.otf is excluded by !**/*.otf
  • public/fonts/Another Danger Slanted - Demo.otf is excluded by !**/*.otf
  • public/fonts/SignLanguage-Regular.ttf is excluded by !**/*.ttf
  • public/images/presets/adventurelocation.jpg is excluded by !**/*.jpg
  • public/images/presets/comedylocation.jpg is excluded by !**/*.jpg
  • public/images/presets/cozy.svg is excluded by !**/*.svg
  • public/images/presets/cyberpunklocation.jpg is excluded by !**/*.jpg
  • public/images/presets/dramalocation.jpg is excluded by !**/*.jpg
  • public/images/presets/fantasy.svg is excluded by !**/*.svg
  • public/images/presets/fantasylocation.jpg is excluded by !**/*.jpg
  • public/images/presets/genre-adventure.jpg is excluded by !**/*.jpg
  • public/images/presets/genre-comedy.jpg is excluded by !**/*.jpg
  • public/images/presets/genre-cyberpunk.jpg is excluded by !**/*.jpg
  • public/images/presets/genre-drama.jpg is excluded by !**/*.jpg
  • public/images/presets/genre-fantasy.jpg is excluded by !**/*.jpg
  • public/images/presets/genre-horror.jpg is excluded by !**/*.jpg
  • public/images/presets/genre-mystery.jpg is excluded by !**/*.jpg
  • public/images/presets/genre-romance.jpg is excluded by !**/*.jpg
  • public/images/presets/genre-sci-fi.jpg is excluded by !**/*.jpg
  • public/images/presets/genre-thriller.jpg is excluded by !**/*.jpg
  • public/images/presets/horrorlocation.jpg is excluded by !**/*.jpg
  • public/images/presets/literary.svg is excluded by !**/*.svg
  • public/images/presets/mysterylocation.jpg is excluded by !**/*.jpg
  • public/images/presets/noir-boss.png is excluded by !**/*.png
  • public/images/presets/noir-detective.png is excluded by !**/*.png
  • public/images/presets/noir-femme-fatale.png is excluded by !**/*.png
  • public/images/presets/noir-grifter.png is excluded by !**/*.png
  • public/images/presets/noir.svg is excluded by !**/*.svg
  • public/images/presets/romanticlocation.jpg is excluded by !**/*.jpg
  • public/images/presets/scifi.svg is excluded by !**/*.svg
  • public/images/presets/scifilocation.jpg is excluded by !**/*.jpg
  • public/images/presets/thriller.svg is excluded by !**/*.svg
  • public/images/presets/thrillerlocation.jpg is excluded by !**/*.jpg
  • public/mystery.png is excluded by !**/*.png
  • public/sci-fi.png is excluded by !**/*.png
📒 Files selected for processing (39)
  • .gitignore
  • app/api/groq/route.ts
  • app/globals.css
  • app/pro-panel/page.tsx
  • components/header.tsx
  • components/pro-panel/CategorySection.tsx
  • components/pro-panel/DossierFormPanel.tsx
  • components/pro-panel/GenerateStoryButton.tsx
  • components/pro-panel/GenreCard.tsx
  • components/pro-panel/GenreGrid.tsx
  • components/pro-panel/ImportExportDialog.tsx
  • components/pro-panel/MissionNavigator.tsx
  • components/pro-panel/PresetManager.tsx
  • components/pro-panel/ProPanel.tsx
  • components/pro-panel/SidebarPanels.tsx
  • components/pro-panel/StoryInputSection.tsx
  • components/pro-panel/controls/index.tsx
  • components/pro-panel/iconMap.ts
  • components/pro-panel/index.ts
  • components/pro-panel/sections/AdvancedSection.tsx
  • components/pro-panel/sections/CharacterSection.tsx
  • components/pro-panel/sections/LengthSection.tsx
  • components/pro-panel/sections/ModelSettingsSection.tsx
  • components/pro-panel/sections/StoryStructureSection.tsx
  • components/pro-panel/sections/ThemeSection.tsx
  • components/pro-panel/sections/ToneStyleSection.tsx
  • components/pro-panel/sections/VisualSection.tsx
  • components/pro-panel/sections/WorldSection.tsx
  • components/pro-panel/sections/index.ts
  • components/splash-screen.tsx
  • components/ui/slider.tsx
  • components/ui/tooltip.tsx
  • lib/constants/proPanelDefaults.ts
  • lib/groq-service.ts
  • lib/schemas/proPanelSchemas.ts
  • package.json
  • public/comed.jfif
  • src/store/proPanelStore.ts
  • tailwind.config.ts

Comment thread components/pro-panel/controls/index.tsx Outdated
Comment on lines +53 to +69
<span className="font-condensed uppercase tracking-wider text-xs text-white leading-tight max-w-[55%] block">
{label}
</span>
{showValue && (
<span className="text-base font-mono font-bold text-white bg-[#8a0000] px-3 py-1 rounded shrink-0 min-w-[3rem] text-center tabular-nums leading-none">
{value}{suffix}
</span>
)}
</div>
<Slider
value={[value]}
min={min}
max={max}
step={step}
onValueChange={(values) => onChange(values[0] ?? value)}
className="w-full [&_[role=slider]]:bg-noir-primary [&_[role=slider]]:border-2 [&_[role=slider]]:border-white/40 [&_[role=slider]]:w-4 [&_[role=slider]]:h-4"
/>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Mar 1, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Programmatically associate labels with interactive controls.

These controls render visible labels, but the interactive elements are not linked via htmlFor/id or aria-labelledby. Screen readers will treat several fields as unlabeled.

♿ Suggested pattern (apply across Slider/Select/Input/NumberInput/Textarea/Switch)
 export function SelectControl<T extends string>({
   label,
   description,
   value,
   options,
   onChange,
   placeholder,
 }: SelectControlProps<T>) {
+  const selectId = React.useId();
   const normalizedOptions = options.map((opt) =>
     typeof opt === 'string'
       ? { value: opt as T, label: formatOptionLabel(opt) }
       : opt
   );

   return (
     <div className="space-y-1.5">
-      <Label className="font-condensed uppercase tracking-wider text-xs text-white">{label}</Label>
+      <Label id={`${selectId}-label`} className="font-condensed uppercase tracking-wider text-xs text-white">
+        {label}
+      </Label>
       {description && (
         <p className="text-[10px] font-condensed uppercase tracking-wider text-gray-500">
           {description}
         </p>
       )}
       <Select value={value} onValueChange={onChange}>
-        <SelectTrigger className="w-full bg-black border-2 border-noir-primary p-3 font-condensed font-bold uppercase text-sm text-white focus:ring-0 hover:border-white transition-colors overflow-hidden">
+        <SelectTrigger
+          aria-labelledby={`${selectId}-label`}
+          className="w-full bg-black border-2 border-noir-primary p-3 font-condensed font-bold uppercase text-sm text-white focus:ring-0 hover:border-white transition-colors overflow-hidden"
+        >
           <SelectValue placeholder={placeholder || 'Select...'} className="truncate" />
         </SelectTrigger>
 export function InputControl({
   label,
   description,
   value,
   onChange,
   placeholder,
   type = 'text',
   min,
   max,
 }: InputControlProps) {
+  const inputId = React.useId();
   return (
     <div className="space-y-1.5">
-      <Label className="font-condensed uppercase tracking-wider text-xs text-white">{label}</Label>
+      <Label htmlFor={inputId} className="font-condensed uppercase tracking-wider text-xs text-white">
+        {label}
+      </Label>
       {description && (
         <p className="text-[10px] font-condensed uppercase tracking-wider text-gray-500">
           {description}
         </p>
       )}
       <Input
+        id={inputId}
         type={type}
         value={value}
         onChange={(e) => onChange(e.target.value)}

Also applies to: 107-115, 159-173, 202-216, 246-265, 287-295

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

In `@components/pro-panel/controls/index.tsx` around lines 53 - 69, Label text
spans are not programmatically associated with their interactive controls
(Slider here), so add an accessible id/label linkage: generate a stable id (use
useId() or accept an id prop) and set it on the label span (e.g.,
id={`${id}-label`}) then pass that id to the Slider via aria-labelledby (or
htmlFor on a <label> if Slider exposes an input id) so screen readers can
connect the label to the control; apply the same pattern to the other controls
(Select/Input/NumberInput/Textarea/Switch) referenced in this file by
adding/propagating a control id and using aria-labelledby or htmlFor+id where
appropriate (use symbols like the label span and Slider, and props like
value/onChange/min/max/step to locate the code).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@rohits1450 implement this recommended fix.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +209 to +212
type="number"
value={value}
onChange={(e) => onChange(Number(e.target.value))}
min={min}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Mar 1, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard numeric parsing before writing to state.

Line 211 uses Number(e.target.value), which turns empty input into 0 and transient input states into NaN. That can persist invalid values.

🔢 Suggested fix
       <Input
         type="number"
         value={value}
-        onChange={(e) => onChange(Number(e.target.value))}
+        onChange={(e) => {
+          const next = e.currentTarget.valueAsNumber;
+          if (!Number.isFinite(next)) return;
+          onChange(next);
+        }}
         min={min}
         max={max}
         step={step}
         className="bg-black border-2 border-noir-primary p-3 font-mono text-sm text-white focus:ring-0 focus:border-white transition-colors"
       />
📝 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
type="number"
value={value}
onChange={(e) => onChange(Number(e.target.value))}
min={min}
type="number"
value={value}
onChange={(e) => {
const next = e.currentTarget.valueAsNumber;
if (!Number.isFinite(next)) return;
onChange(next);
}}
min={min}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/controls/index.tsx` around lines 209 - 212, The numeric
input currently calls onChange(Number(e.target.value)) which converts empty
strings to 0 and can write NaN into state; update the onChange handler in the
component (the JSX using value and onChange) to guard parsing: read
e.target.value as a string, if it's empty call onChange with a cleared value
(e.g., undefined or null) to preserve the empty transient state, otherwise parse
to a number and only call onChange when Number(value) is a valid number (use
Number.isNaN to check); keep the input's value prop and min prop as-is but
ensure the handler avoids writing 0/NaN for intermediate empty or invalid input.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@rohits1450 fix this with recommended one

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +470 to +494
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className="w-full flex items-center justify-between px-4 py-2.5 hover:bg-white/[0.03] transition-colors group"
>
<h4 className="text-[11px] font-condensed font-bold uppercase tracking-[0.15em] text-gray-400 group-hover:text-gray-300 transition-colors">
{title}
</h4>
<span
className={`text-gray-500 text-xs font-condensed uppercase tracking-wider transition-transform duration-200 ${
isOpen ? 'rotate-0' : '-rotate-90'
}`}
>
</span>
</button>
<div
className={`overflow-hidden transition-all duration-300 ease-in-out ${
isOpen ? 'max-h-[2000px] opacity-100' : 'max-h-0 opacity-0'
}`}
>
<div className="px-4 pb-4 pt-2 space-y-4">
{children}
</div>
</div>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Mar 1, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Collapsed content remains focusable; add proper disclosure semantics.

The panel is visually hidden only (max-h-0/opacity-0) but children stay mounted and can still receive keyboard focus. Also add aria-expanded/aria-controls on the toggle button.

🧭 Suggested fix
 export function CollapsibleGroup({
   title,
   defaultOpen = false,
   children,
 }: CollapsibleGroupProps) {
   const [isOpen, setIsOpen] = React.useState(defaultOpen);
+  const contentId = React.useId();

   return (
     <div className="border border-white/10 bg-white/[0.02]">
       <button
         type="button"
         onClick={() => setIsOpen(!isOpen)}
+        aria-expanded={isOpen}
+        aria-controls={contentId}
         className="w-full flex items-center justify-between px-4 py-2.5 hover:bg-white/[0.03] transition-colors group"
       >
         <h4 className="text-[11px] font-condensed font-bold uppercase tracking-[0.15em] text-gray-400 group-hover:text-gray-300 transition-colors">
           {title}
         </h4>
@@
-      <div
-        className={`overflow-hidden transition-all duration-300 ease-in-out ${
-          isOpen ? 'max-h-[2000px] opacity-100' : 'max-h-0 opacity-0'
-        }`}
-      >
-        <div className="px-4 pb-4 pt-2 space-y-4">
-          {children}
-        </div>
-      </div>
+      {isOpen && (
+        <div id={contentId} className="px-4 pb-4 pt-2 space-y-4">
+          {children}
+        </div>
+      )}
     </div>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/controls/index.tsx` around lines 470 - 494, The toggle
button and hidden panel lack proper disclosure semantics and closed content
remains keyboard-focusable; update the toggle button (the element using
setIsOpen and isOpen) to include aria-expanded={isOpen} and aria-controls
pointing to a stable id, generate that id with React's useId or a stable string;
on the panel div (the container rendering {children}) set id={contentId} and
aria-hidden={!isOpen} and either unmount the children when closed (render
children only when isOpen) or ensure all interactive descendants are
inert/untabbable when closed (e.g., add tabIndex={-1} or use an inert polyfill)
so hidden content cannot receive focus. Ensure the button still toggles
setIsOpen and keep the existing visual classes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@rohits1450 add the disclosure semantics

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +59 to +66
await response.json();

toast({
title: 'Story Generated!',
description: 'Your story has been created successfully.',
});

resetStoryInput();
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Mar 1, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find the correct path for the API route
find . -name "route.ts" -o -name "route.js" | grep -E "api.*groq" | head -20

Repository: IndieHub25/GroqTales

Length of output: 87


🏁 Script executed:

# Also check the exact location of the component file
find . -name "GenerateStoryButton.tsx" -o -name "GenerateStoryButton.ts" -o -name "GenerateStoryButton.jsx"

Repository: IndieHub25/GroqTales

Length of output: 110


🏁 Script executed:

# Look at the directory structure to understand the project layout
ls -la | head -20

Repository: IndieHub25/GroqTales

Length of output: 1284


🏁 Script executed:

# Examine the API route to see what it returns
cat -n ./app/api/groq/route.ts

Repository: IndieHub25/GroqTales

Length of output: 5784


🏁 Script executed:

# Examine GenerateStoryButton.tsx to see the complete context around lines 59-66
cat -n ./components/pro-panel/GenerateStoryButton.tsx

Repository: IndieHub25/GroqTales

Length of output: 4631


🏁 Script executed:

# Check what generateStoryWithProConfig returns and if it saves to database
grep -n "generateStoryWithProConfig" ./lib/groq-service.ts | head -5

Repository: IndieHub25/GroqTales

Length of output: 117


🏁 Script executed:

# Look for story-related routes or pages that might handle viewing
find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) -path "*/app/*" | xargs grep -l "story.*\[.*id" 2>/dev/null | head -10

Repository: IndieHub25/GroqTales

Length of output: 46


🏁 Script executed:

# Check if there's a stories collection/table or database operations
rg -n "stories|Story" lib/ --type ts --type tsx | grep -E "(db|save|create|insert)" | head -20

Repository: IndieHub25/GroqTales

Length of output: 91


🏁 Script executed:

# Examine generateStoryWithProConfig function
sed -n '918,980p' ./lib/groq-service.ts

Repository: IndieHub25/GroqTales

Length of output: 2236


🏁 Script executed:

# Check for database operations in the codebase
rg "db\.|database|create.*story|save.*story" lib/ --type ts -A 3 | head -40

Repository: IndieHub25/GroqTales

Length of output: 2032


🏁 Script executed:

# Look for any story storage or API endpoints that might handle persistence
find ./app -type f \( -name "*.ts" -o -name "*.js" \) | xargs grep -l "story" 2>/dev/null | head -15

Repository: IndieHub25/GroqTales

Length of output: 608


🏁 Script executed:

# Check generate-and-mint endpoint to see if it handles story persistence
head -100 ./app/api/generate-and-mint/route.ts

Repository: IndieHub25/GroqTales

Length of output: 1198


🏁 Script executed:

# Check story/publish endpoint
head -80 ./app/api/story/publish/route.ts

Repository: IndieHub25/GroqTales

Length of output: 2408


🏁 Script executed:

# Look for any localStorage or session storage of stories in the component
rg "localStorage|sessionStorage" components/ app/ --type ts --type js | head -20

Repository: IndieHub25/GroqTales

Length of output: 1905


Generated story content is discarded — provide a way for users to access their result.

The API response containing the generated story is awaited on line 59 but never stored or used. After successful generation, the user receives a toast confirmation and the form resets, but the story content is lost. Consider storing the response in localStorage for temporary access, saving it to the database to generate a story ID for persistence, or including the story content in the toast with a copy-to-clipboard action.

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

In `@components/pro-panel/GenerateStoryButton.tsx` around lines 59 - 66, The
awaited API result from await response.json() in GenerateStoryButton.tsx is
never captured or used, so update the handler to assign that result to a
variable (e.g., const generated = await response.json()), then persist or
surface it: store generated content in localStorage under a key like
'generatedStory' for temporary access and/or call your saveStory API to persist
and return a storyId, update any component state so the UI can display the
generated story, and modify the toast to include either a brief preview and a
copy-to-clipboard action or the returned storyId; keep the existing
resetStoryInput() call but only after persisting/surfacing the generated story.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@rohits1450 provide this way for users to access the results.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread components/pro-panel/GenreCard.tsx Outdated
Comment on lines +39 to +45
<button
type="button"
onClick={() => onSelect(id)}
className={`group relative w-full text-left overflow-hidden transition-all shadow-comic group-hover:-translate-y-1 ${isSelected
? 'border-4 border-noir-primary bg-noir-primary/20'
: 'border-4 border-black bg-white'
}`}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Expose selected-card semantics on the button.

This control is selectable but does not communicate selected state to assistive tech.

♿ Suggested fix
 <button
   type="button"
   onClick={() => onSelect(id)}
+  aria-pressed={isSelected}
+  aria-label={`${title} (${caseId})`}
   className={`group relative w-full text-left overflow-hidden transition-all shadow-comic group-hover:-translate-y-1 ${isSelected
📝 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
<button
type="button"
onClick={() => onSelect(id)}
className={`group relative w-full text-left overflow-hidden transition-all shadow-comic group-hover:-translate-y-1 ${isSelected
? 'border-4 border-noir-primary bg-noir-primary/20'
: 'border-4 border-black bg-white'
}`}
<button
type="button"
onClick={() => onSelect(id)}
aria-pressed={isSelected}
aria-label={`${title} (${caseId})`}
className={`group relative w-full text-left overflow-hidden transition-all shadow-comic group-hover:-translate-y-1 ${isSelected
? 'border-4 border-noir-primary bg-noir-primary/20'
: 'border-4 border-black bg-white'
}`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/GenreCard.tsx` around lines 39 - 45, The selectable card
button in GenreCard doesn't expose its selected state to assistive tech; update
the <button> in the GenreCard component to include aria-pressed={isSelected}
(and keep the existing onSelect(id) handler and class logic) so screen readers
receive the selected semantics; ensure the boolean isSelected prop is passed
into that attribute on the button element.

Comment on lines +38 to +47
<label className="font-marker text-3xl text-black mb-4 block">
WHAT&apos;S THE STORY, HERO?*
</label>
<textarea
value={storyInput.storyPrompt}
onChange={(e) => updateStoryInput('storyPrompt', e.target.value)}
placeholder="Enter your prompt here... (e.g., A cyberpunk detective hunting a ghost in the machine)"
rows={6}
className="w-full bg-white border-4 border-black p-6 font-display text-xl text-black focus:ring-0 focus:outline-none shadow-[inset_4px_4px_0_0_#ccc] resize-none placeholder:text-gray-400"
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Associate every label with its form control.

The labels are visible but not programmatically bound to <textarea>/<select>, so accessible names are unreliable.

♿ Suggested fix
-<label className="font-marker text-3xl text-black mb-4 block">
+<label htmlFor="story-prompt" className="font-marker text-3xl text-black mb-4 block">
...
-<textarea
+<textarea
+  id="story-prompt"
   value={storyInput.storyPrompt}
   ...

-<label className="flex items-center gap-2 font-condensed text-black font-bold uppercase">
+<label htmlFor="plot-type" className="flex items-center gap-2 font-condensed text-black font-bold uppercase">
...
-<select
+<select
+  id="plot-type"
   value={parameters.storyStructure.narrativeStyle}
   ...

-<label className="flex items-center gap-2 font-condensed text-black font-bold uppercase mt-6">
+<label htmlFor="conflict-type" className="flex items-center gap-2 font-condensed text-black font-bold uppercase mt-6">
...
-<select
+<select
+  id="conflict-type"
   value={parameters.storyStructure.actStructure}
   ...

-<label className="flex items-center gap-2 font-condensed text-black font-bold uppercase">
+<label htmlFor="story-arc" className="flex items-center gap-2 font-condensed text-black font-bold uppercase">
...
-<select
+<select
+  id="story-arc"
   value={parameters.storyStructure.resolutionStyle}
   ...

Also applies to: 57-82, 85-97

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

In `@components/pro-panel/StoryInputSection.tsx` around lines 38 - 47, The label
in StoryInputSection is not programmatically bound to the textarea, so add an id
on the textarea (e.g., id="storyPrompt") and set the corresponding label's
htmlFor to that id; update the updateStoryInput-bound textarea element and its
label in StoryInputSection, and apply the same pattern to the other
label/textarea or label/select pairs referenced (lines 57-82 and 85-97) so every
<label> uses htmlFor and each form control has a matching id.

Comment on lines +98 to +115
<label className="flex items-center gap-2 font-condensed text-black font-bold uppercase mt-6">
<Zap className="w-4 h-4 text-[#8a0000]" /> Pacing
</label>
<div className="flex flex-wrap gap-2">
{PACING_OPTIONS.map(p => (
<button
key={p}
type="button"
onClick={() => updateParameter('storyStructure', 'pacing', p)}
className={`border-2 border-black px-3 py-2 font-condensed font-bold uppercase text-[10px] leading-tight text-center transition-colors whitespace-nowrap ${parameters.storyStructure.pacing === p
? 'bg-black text-white'
: 'bg-white text-black hover:bg-black hover:text-white'
}`}
>
{formatLabel(p)}
</button>
))}
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Expose pacing selection state to assistive tech.

These buttons behave like a segmented toggle, but selected state is not announced.

♿ Suggested fix
-<div className="flex flex-wrap gap-2">
+<div className="flex flex-wrap gap-2" role="group" aria-label="Pacing">
   {PACING_OPTIONS.map(p => (
     <button
       key={p}
       type="button"
       onClick={() => updateParameter('storyStructure', 'pacing', p)}
+      aria-pressed={parameters.storyStructure.pacing === p}
       className={`border-2 border-black px-3 py-2 ...`}
     >
📝 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
<label className="flex items-center gap-2 font-condensed text-black font-bold uppercase mt-6">
<Zap className="w-4 h-4 text-[#8a0000]" /> Pacing
</label>
<div className="flex flex-wrap gap-2">
{PACING_OPTIONS.map(p => (
<button
key={p}
type="button"
onClick={() => updateParameter('storyStructure', 'pacing', p)}
className={`border-2 border-black px-3 py-2 font-condensed font-bold uppercase text-[10px] leading-tight text-center transition-colors whitespace-nowrap ${parameters.storyStructure.pacing === p
? 'bg-black text-white'
: 'bg-white text-black hover:bg-black hover:text-white'
}`}
>
{formatLabel(p)}
</button>
))}
</div>
<label className="flex items-center gap-2 font-condensed text-black font-bold uppercase mt-6">
<Zap className="w-4 h-4 text-[`#8a0000`]" /> Pacing
</label>
<div className="flex flex-wrap gap-2" role="group" aria-label="Pacing">
{PACING_OPTIONS.map(p => (
<button
key={p}
type="button"
onClick={() => updateParameter('storyStructure', 'pacing', p)}
aria-pressed={parameters.storyStructure.pacing === p}
className={`border-2 border-black px-3 py-2 font-condensed font-bold uppercase text-[10px] leading-tight text-center transition-colors whitespace-nowrap ${parameters.storyStructure.pacing === p
? 'bg-black text-white'
: 'bg-white text-black hover:bg-black hover:text-white'
}`}
>
{formatLabel(p)}
</button>
))}
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/pro-panel/StoryInputSection.tsx` around lines 98 - 115, The pacing
option buttons in StoryInputSection act like a segmented toggle but don't
announce selection to assistive tech; update each rendered button (the map over
PACING_OPTIONS in StoryInputSection) to include an accessible toggle state by
adding aria-pressed={parameters.storyStructure.pacing === p} (and a descriptive
aria-label if needed, e.g., using formatLabel(p)), and keep the existing onClick
(updateParameter) and visual classes unchanged so screen readers will announce
which option is selected.

Comment thread lib/groq-service.ts Outdated
Comment on lines +981 to +991
const response = await fetch(
'https://api.groq.com/openai/v1/chat/completions',
{
method: 'POST',
headers: {
Authorization: `Bearer ${groqApiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
}
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Node.js fetch default timeout AbortController best practices server-side

💡 Result:

Key facts (Node server-side fetch)

  • Node’s built-in fetch() (Node 18+) is implemented on top of undici. (nodejs.org)
  • fetch() does not expose undici’s low-level timeout knobs directly (because the Fetch spec doesn’t define them). If you need those, you must pass a custom dispatcher (undici Agent). (github.com)
  • You should assume there is no “safe” application-level timeout by default; you must set your own overall deadline with aborting. (Node/undici also have internal stage timeouts—e.g., connect— that can still fire unless configured.) (github.com)

Best-practice pattern: always set an overall deadline with AbortSignal.timeout

If you’re on a Node version that supports it, prefer AbortSignal.timeout(ms) over hand-rolled setTimeout() + AbortController (cleaner, less error-prone). (developer.mozilla.org)

const res = await fetch(url, {
  signal: AbortSignal.timeout(10_000), // hard deadline for the whole operation
});

If you also need manual cancellation (e.g., request aborted by the caller), combine signals:

const controller = new AbortController();

const res = await fetch(url, {
  signal: AbortSignal.any([
    controller.signal,              // manual cancel
    AbortSignal.timeout(10_000),    // overall deadline
  ]),
});

(Using AbortSignal.any() with AbortSignal.timeout() is explicitly supported by the platform API.) (developer.mozilla.org)


When AbortSignal.timeout() is not enough: configure undici timeouts via a dispatcher

Some failures are governed by undici’s internal stage timeouts (not your overall deadline), e.g. connect timeout defaults (commonly observed as 10s). To change these, pass a custom undici Agent as dispatcher. (github.com)

import { Agent } from "undici";

const dispatcher = new Agent({
  connect: { timeout: 20_000 }, // connect timeout
  headersTimeout: 30_000,       // time to receive response headers
  bodyTimeout: 120_000,         // inactivity / body receive window (use-case dependent)
});

const res = await fetch(url, {
  dispatcher,
  signal: AbortSignal.timeout(130_000), // still keep an overall deadline
});

Notes:

  • Use the abort signal for your end-to-end SLA, and undici timeouts for phase-level protection (connect/headers/body).
  • If you need this app-wide, you can set a global dispatcher (undici), but per-request dispatchers are safer in multi-tenant / library code. (github.com)

Server-side operational best practices (quick checklist)

  • Always set an overall deadline (AbortSignal.timeout) for outbound calls. (developer.mozilla.org)
  • For long-running/streaming responses, tune undici headers/body/connect timeouts via a dispatcher (don’t rely on defaults). (github.com)
  • Propagate cancellation: if the inbound client disconnects, abort downstream fetches (combine signals).
  • Consume or cancel response bodies (especially if you’re doing many requests) to avoid tying up connections in the pool (undici/Fetch behavior differs from “fire and forget” expectations). (github.com)

Citations:


🏁 Script executed:

cat -n lib/groq-service.ts | sed -n '970,1010p'

Repository: IndieHub25/GroqTales

Length of output: 1656


🏁 Script executed:

cat -n package.json | head -30

Repository: IndieHub25/GroqTales

Length of output: 1062


Add timeout protection to the Groq API request.

This fetch() call has no timeout. Node.js fetch does not have a safe default timeout, so upstream stalls will block request handling indefinitely.

Use AbortSignal.timeout() for a clean, built-in deadline (Node 20+ already supports it):

Suggested fix
     const response = await fetch(
       'https://api.groq.com/openai/v1/chat/completions',
       {
         method: 'POST',
         headers: {
           Authorization: `Bearer ${groqApiKey}`,
           'Content-Type': 'application/json',
         },
+        signal: AbortSignal.timeout(15000),
         body: JSON.stringify(requestBody),
       }
     );

If you also need manual cancellation (e.g., from an inbound request abort), combine signals with AbortSignal.any().

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

In `@lib/groq-service.ts` around lines 981 - 991, The fetch call in
lib/groq-service.ts that posts to
'https://api.groq.com/openai/v1/chat/completions' needs a timeout to avoid
indefinite stalls: create an AbortSignal via AbortSignal.timeout(desiredMs) and
pass it as the signal option to fetch (merge with any existing inbound request
signal using AbortSignal.any() if needed); update the call that constructs
requestBody and invokes fetch so it uses that signal and cleanly handles
AbortError timeouts in the surrounding function (e.g., catch and treat as a
timeout error).

Comment on lines +274 to +302
export const ModelSettingsSchema = z.object({
modelSelection: z.enum([
'llama3-70b-8192',
'llama3-8b-8192',
'mixtral-8x7b-32768',
'gemma-7b-it',
'gemma2-9b-it',
'llama-3.1-70b-versatile',
'llama-3.1-8b-instant',
'llama-3.2-90b-vision-preview',
'llama-3.3-70b-versatile',
]).describe('Groq AI model to use'),

temperature: z.number().min(0).max(2).describe('Randomness of output (0-2)'),

maxTokens: z.number().min(256).max(32768).describe('Maximum tokens to generate (256-32768)'),

topP: z.number().min(0).max(1).describe('Nucleus sampling threshold (0-1)'),

topK: z.number().min(1).max(100).int().describe('Top-K sampling (1-100)'),

frequencyPenalty: z.number().min(-2).max(2).describe('Frequency penalty (-2 to 2)'),

presencePenalty: z.number().min(-2).max(2).describe('Presence penalty (-2 to 2)'),

repetitionPenalty: z.number().min(0).max(2).describe('Repetition penalty (0-2)'),

stopSequences: z.array(z.string().max(50)).max(4).describe('Stop sequences (max 4)'),
});
Copy link
Copy Markdown
Contributor

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:

Groq API models 2025 2026 available list documentation

💡 Result:

Groq API (GroqCloud) model list (2025–2026) — official docs

1) Canonical “what models are available right now”

  • Docs page: “Supported Models” (includes production + preview model IDs, plus “Compound” systems). [1]
  • API endpoint to list all active models (recommended): GET https://api.groq.com/openai/v1/models (returns JSON of active model IDs). [1]

2) Models shown in Groq’s “Supported Models” docs (current as of the latest docs page)

Production models (examples shown):

  • llama-3.1-8b-instant
  • llama-3.3-70b-versatile
  • openai/gpt-oss-120b
  • openai/gpt-oss-20b
  • whisper-large-v3
  • whisper-large-v3-turbo [1]

Production systems:

  • groq/compound
  • groq/compound-mini [1]

Preview models (examples shown):

  • canopylabs/orpheus-arabic-saudi
  • canopylabs/orpheus-v1-english
  • meta-llama/llama-4-scout-17b-16e-instruct
  • meta-llama/llama-prompt-guard-2-22m
  • meta-llama/llama-prompt-guard-2-86m
  • moonshotai/kimi-k2-instruct-0905
  • openai/gpt-oss-safeguard-20b
  • qwen/qwen3-32b [1]

3) Batch API model availability (subset list)

If you’re specifically using the Batch API, Groq documents the supported model IDs for batch chat/audio workloads (includes, e.g., meta-llama/llama-guard-4-12b). [2]

4) Deprecations you should be aware of (2025–2026)

Groq maintains an official Model Deprecation page with shutdown dates and replacement model IDs (e.g., deprecations for older Llama 3.x IDs, Kimi K2 original ID, PlayAI TTS models, etc.). [3]

Sources: [1] [2] [3]


🌐 Web query:

Groq API deprecated models 2025 llama3-70b-8192 mixtral gemma

💡 Result:

GroqCloud API model deprecations relevant to llama3-70b-8192 / Mixtral / Gemma (with 2025 shutdown dates):

Deprecated model ID Shutdown date Recommended replacement(s)
mixtral-8x7b-32768 2025-03-20 mistral-saba-24b, llama-3.3-70b-versatile [1]
llama3-70b-8192 2025-08-30 llama-3.3-70b-versatile [2]
llama3-8b-8192 2025-08-30 llama-3.1-8b-instant [2]
gemma2-9b-it 2025-10-08 llama-3.1-8b-instant [2]

Related (not 2025, but often mentioned alongside these “classic” Groq IDs):

  • gemma-7b-it shutdown 2024-12-18, replacement gemma2-9b-it [1].
  • Groq also removed gemma-7b-it and mixtral-8x7b-32768 from the docs’ chat-completions model list in an April 21, 2025 changelog update [3].

Sources: GroqDocs deprecations page [1][2] and GroqDocs changelog [3].

[1] (console.groq.com)
[2] (console.groq.com)
[3] (console.groq.com)

Citations:


Remove deprecated Groq models from the modelSelection enum immediately.

The following models have already been shut down by Groq and will fail at runtime:

  • mixtral-8x7b-32768 (deprecated 2025-03-20)
  • llama3-70b-8192 (deprecated 2025-08-30)
  • llama3-8b-8192 (deprecated 2025-08-30)
  • gemma-7b-it (deprecated 2024-12-18)
  • gemma2-9b-it (deprecated 2025-10-08)

Replace them with Groq's recommended current models: llama-3.1-8b-instant, llama-3.1-70b-versatile, and llama-3.3-70b-versatile. Additionally, verify that llama-3.2-90b-vision-preview is still supported by Groq API, as it is not listed in their current models documentation.

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

In `@lib/schemas/proPanelSchemas.ts` around lines 274 - 302, Update the
ModelSettingsSchema's modelSelection enum to remove the deprecated Groq models
and replace them with the recommended current models: remove
"mixtral-8x7b-32768", "llama3-70b-8192", "llama3-8b-8192", "gemma-7b-it", and
"gemma2-9b-it" from the z.enum in ModelSettingsSchema and add
"llama-3.1-8b-instant", "llama-3.1-70b-versatile", and "llama-3.3-70b-versatile"
instead; also verify whether "llama-3.2-90b-vision-preview" remains supported by
the Groq API and remove or keep it accordingly in the same modelSelection enum.

Comment on lines +167 to +181
savePreset: (name, description) => {
const state = get();
const newPreset: ProPreset = {
name,
description,
timestamp: Date.now(),
version: 1,
parameters: { ...state.parameters },
};

set({
savedPresets: {
...state.savedPresets,
[name.toLowerCase().replace(/\s+/g, '-')]: newPreset,
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent silent preset overwrite on slug collisions.

Saving "My Preset" and then "my preset" currently targets the same normalized key and overwrites the earlier preset without warning.

💾 Suggested fix (non-breaking: auto-suffix unique key)
       savePreset: (name, description) => {
         const state = get();
+        const baseKey = name.toLowerCase().trim().replace(/\s+/g, '-');
+        let presetKey = baseKey;
+        let suffix = 2;
+        while (state.savedPresets[presetKey]) {
+          presetKey = `${baseKey}-${suffix++}`;
+        }
+
         const newPreset: ProPreset = {
           name,
           description,
           timestamp: Date.now(),
           version: 1,
           parameters: { ...state.parameters },
         };

         set({
           savedPresets: {
             ...state.savedPresets,
-            [name.toLowerCase().replace(/\s+/g, '-')]: newPreset,
+            [presetKey]: newPreset,
           },
           currentPresetName: name,
           isModified: false,
           lastSaved: Date.now(),
         });
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store/proPanelStore.ts` around lines 167 - 181, savePreset currently
normalizes the preset name to a slug and will silently overwrite an existing
entry in savedPresets on collisions; update savePreset to compute a baseSlug
(using the existing .toLowerCase().replace(/\s+/g, '-') logic) then, before
setting savedPresets, check savedPresets for that key and, if it already exists,
append a numeric suffix (-1, -2, ...) to the slug until an unused key is found;
keep ProPreset.name and description unchanged, store the preset under the unique
slug, and use get()/set() as before to update savedPresets so collisions no
longer overwrite existing presets.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
.gitignore (1)

69-70: Reconsider ignoring public/robots.txt by default.

On Line 70, ignoring /public/robots.txt can mask intended SEO/source updates, since files in public/ are usually authored assets. Prefer keeping it tracked unless it is definitely generated in this repo.

Suggested adjustment
 # Generated files
 /public/sitemap*.xml
-/public/robots.txt
+# /public/robots.txt
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore around lines 69 - 70, Remove the /public/robots.txt ignore entry
from .gitignore so the repository tracks the authored robots file (leave
/public/sitemap*.xml as-is); only re-add an ignore or generate rule for
/public/robots.txt if you also add a clear generator step that produces it
(refer to the literal pattern "/public/robots.txt" in the .gitignore to locate
and remove or comment out).
app/globals.css (1)

978-978: Minor: Stylelint prefers lowercase currentcolor.

This is a stylistic consistency issue flagged by Stylelint's value-keyword-case rule.

✏️ Optional fix for lint compliance
   .font-marker {
     font-family: var(--font-marker);
     font-weight: 700;
     transform: skewX(-4deg);
-    -webkit-text-stroke: 0.5px currentColor;
+    -webkit-text-stroke: 0.5px currentcolor;
     paint-order: stroke fill;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` at line 978, Update the -webkit-text-stroke declaration in
globals.css to use the lowercase keyword "currentcolor" (replace any occurrences
of "currentColor" with "currentcolor") to satisfy Stylelint's value-keyword-case
rule; locate the -webkit-text-stroke property in the file and make the value
lowercase, then re-run linting to confirm the warning is resolved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/globals.css`:
- Line 931: The .noir-input rule uses var(--font-condensed) but the CSS variable
--font-condensed is not defined; define the variable (e.g., in :root) mapping it
to the imported "Roboto Condensed" font-family with safe fallbacks and ensure
the font import exists (the unique symbols to update are the CSS variable
--font-condensed and the .noir-input class, and confirm the `@import` for "Roboto
Condensed" or equivalent font-face is present); add the variable declaration so
var(--font-condensed) resolves to something like "Roboto Condensed", Arial,
sans-serif.
- Line 917: The .noir-section-label uses var(--font-marker) but that CSS
variable is never defined; add a definition for --font-marker (pointing to
"Permanent Marker", fallback fonts) inside the :root block and mirror it in
.dark if you maintain separate theme variables so the Permanent Marker import is
actually applied; update :root (and .dark if present) to set --font-marker:
'Permanent Marker', cursive (and adjust any other font variables similarly) so
.noir-section-label no longer falls back to the browser default.

---

Nitpick comments:
In @.gitignore:
- Around line 69-70: Remove the /public/robots.txt ignore entry from .gitignore
so the repository tracks the authored robots file (leave /public/sitemap*.xml
as-is); only re-add an ignore or generate rule for /public/robots.txt if you
also add a clear generator step that produces it (refer to the literal pattern
"/public/robots.txt" in the .gitignore to locate and remove or comment out).

In `@app/globals.css`:
- Line 978: Update the -webkit-text-stroke declaration in globals.css to use the
lowercase keyword "currentcolor" (replace any occurrences of "currentColor" with
"currentcolor") to satisfy Stylelint's value-keyword-case rule; locate the
-webkit-text-stroke property in the file and make the value lowercase, then
re-run linting to confirm the warning is resolved.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42911b4 and e8b12c2.

📒 Files selected for processing (2)
  • .gitignore
  • app/globals.css

Comment thread app/globals.css
Comment thread app/globals.css
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 (1)
package.json (1)

100-103: Move @types/* packages to devDependencies to avoid runtime bloat.

@types/node, @types/react, and @types/react-dom are build-time typings and are usually not needed in production dependency installs. Other @types packages in this project (@types/bcryptjs, @types/jest, @types/uuid) are already correctly placed in devDependencies; these three should follow the same pattern.

♻️ Proposed manifest adjustment
   "dependencies": {
-    "@types/node": "^20.17.30",
-    "@types/react": "^18.2.57",
-    "@types/react-dom": "^18.2.19",
     ...
   },
   "devDependencies": {
+    "@types/node": "^20.17.30",
+    "@types/react": "^18.2.57",
+    "@types/react-dom": "^18.2.19",
     ...
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 100 - 103, The listed `@types` packages
(`@types/node`, `@types/react`, `@types/react-dom`) are build-time only and should be
moved from dependencies to devDependencies in package.json: remove these three
entries from the top-level "dependencies" and add them under "devDependencies"
with the same versions, then reinstall (npm/yarn/pnpm) and update the lockfile
so the runtime install no longer includes these type packages; verify no runtime
code imports require them and update CI/install scripts if they explicitly rely
on types being present in dependencies.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/globals.css`:
- Line 981: The CSS declaration using the vendor property -webkit-text-stroke
currently uses the camelCase keyword "currentColor" which violates the Stylelint
value-keyword-case rule; update the value to the lowercase keyword
"currentcolor" (i.e., change -webkit-text-stroke: 0.5px currentColor; to
-webkit-text-stroke: 0.5px currentcolor;) so the property value matches the
configured linting rule.
- Line 1: The `@import` uses url(...) which violates the stylelint import-notation
rule; replace the url(...) usage in the globals.css `@import` statement with
string notation so the import reads as a plain quoted string (i.e., use the same
font URL but wrapped in quotes) to satisfy import-notation and keep the same
font families and query parameters.
- Around line 941-944: The CSS rule .noir-input:focus currently removes the
focus outline (outline: none) which hides keyboard focus; locate the
.noir-input:focus selector and remove the outline: none declaration and replace
it with an accessible visible focus style (e.g., a clearly visible outline or
focus ring/box-shadow matching the border color and appropriate outline-offset)
so keyboard users retain focus visibility while preserving the intended styling.

In `@lib/supabase/client.ts`:
- Around line 3-9: The code currently unconditionally falls back to placeholder
Supabase credentials (SUPABASE_URL and SUPABASE_ANON_KEY) which can hide missing
environment configuration; update the constant resolution used by createClient()
to not default to placeholders — read process.env.NEXT_PUBLIC_SUPABASE_URL and
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY directly and, if either is missing,
fail fast (throw an error or return a clear failure) so runtime paths reflect
the same fallback behavior as the server side; ensure the change targets the
SUPABASE_URL, SUPABASE_ANON_KEY constants and the createClient() function so
client/server mismatch and silent credential masking are avoided.

---

Nitpick comments:
In `@package.json`:
- Around line 100-103: The listed `@types` packages (`@types/node`, `@types/react`,
`@types/react-dom`) are build-time only and should be moved from dependencies to
devDependencies in package.json: remove these three entries from the top-level
"dependencies" and add them under "devDependencies" with the same versions, then
reinstall (npm/yarn/pnpm) and update the lockfile so the runtime install no
longer includes these type packages; verify no runtime code imports require them
and update CI/install scripts if they explicitly rely on types being present in
dependencies.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8b12c2 and 987a4a8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • app/globals.css
  • lib/supabase/client.ts
  • package.json

Comment thread app/globals.css Outdated
Comment thread app/globals.css Outdated
Comment thread app/globals.css Outdated
Comment thread lib/supabase/client.ts Outdated
Comment on lines +3 to +9
const SUPABASE_URL =
process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://placeholder.supabase.co';
const SUPABASE_ANON_KEY =
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'placeholder_anon_key';

export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL ||
(process.env.NEXT_PUBLIC_BUILD_MODE === 'true' ? 'https://dummy.supabase.co' : ''),
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ||
(process.env.NEXT_PUBLIC_BUILD_MODE === 'true' ? 'dummy_key' : '')
);
return createBrowserClient(SUPABASE_URL, SUPABASE_ANON_KEY);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid unconditional placeholder Supabase credentials in runtime paths.

Line 4 and Line 6 always fall back to placeholder values, which can mask missing env configuration and diverge from lib/supabase/server.ts fallback behavior. This can cause subtle client/server mismatch issues (e.g., auth/session instability) instead of failing fast.

🔧 Proposed fix
 const SUPABASE_URL =
-    process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://placeholder.supabase.co';
+    process.env.NEXT_PUBLIC_SUPABASE_URL ||
+    (process.env.NEXT_PUBLIC_BUILD_MODE === 'true'
+        ? 'https://dummy.supabase.co'
+        : '');
 const SUPABASE_ANON_KEY =
-    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'placeholder_anon_key';
+    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ||
+    (process.env.NEXT_PUBLIC_BUILD_MODE === 'true' ? 'dummy_key' : '');
 
 export function createClient() {
+    if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
+        throw new Error(
+            'Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY'
+        );
+    }
     return createBrowserClient(SUPABASE_URL, SUPABASE_ANON_KEY);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/supabase/client.ts` around lines 3 - 9, The code currently
unconditionally falls back to placeholder Supabase credentials (SUPABASE_URL and
SUPABASE_ANON_KEY) which can hide missing environment configuration; update the
constant resolution used by createClient() to not default to placeholders — read
process.env.NEXT_PUBLIC_SUPABASE_URL and
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY directly and, if either is missing,
fail fast (throw an error or return a clear failure) so runtime paths reflect
the same fallback behavior as the server side; ensure the change targets the
SUPABASE_URL, SUPABASE_ANON_KEY constants and the createClient() function so
client/server mismatch and silent credential masking are avoided.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
app/globals.css (5)

956-958: Add Firefox support for range slider thumb styling.

The ::-webkit-slider-thumb pseudo-element only applies to WebKit/Blink browsers. Firefox uses ::-moz-range-thumb.

🔧 Proposed fix for cross-browser support
 input[type="range"]::-webkit-slider-thumb {
   cursor: pointer;
 }
+
+input[type="range"]::-moz-range-thumb {
+  cursor: pointer;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 956 - 958, The rule only targets WebKit/Blink
via input[type="range"]::-webkit-slider-thumb; add a matching Firefox rule using
input[type="range"]::-moz-range-thumb with the same declarations (cursor:
pointer) so the range slider thumb is styled in Firefox as well; update any
related vendor-specific properties if present to keep behavior consistent across
browsers.

908-912: Add -webkit-backdrop-filter for Safari compatibility.

Safari requires the -webkit- prefix for backdrop-filter. Without it, the blur effect won't render on Safari/iOS.

🔧 Proposed fix
 .noir-sidebar-panel {
   background: rgba(255, 255, 255, 0.02);
+  -webkit-backdrop-filter: blur(8px);
   backdrop-filter: blur(8px);
   border: 1px solid rgba(255, 255, 255, 0.05);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 908 - 912, The .noir-sidebar-panel rule lacks
the Safari-prefixed backdrop filter; add a -webkit-backdrop-filter: blur(8px)
declaration alongside the existing backdrop-filter: blur(8px) in the
.noir-sidebar-panel CSS block so Safari/iOS renders the blur (place the -webkit-
prefixed property before or alongside the unprefixed property for
compatibility).

987-989: Verify emoji filter produces expected results.

brightness(100) applies 100x brightness multiplier, which is unusually high. This may produce washed-out or unexpected results depending on the emoji renderer. Consider testing across browsers.

A more common approach for forcing white:

💡 Alternative approach
 .emoji-white {
-  filter: grayscale(1) brightness(100);
+  filter: grayscale(1) brightness(0) invert(1);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 987 - 989, The .emoji-white rule uses filter:
grayscale(1) brightness(100) which applies a 100x multiplier; change the
brightness token to a valid, non-extreme value (e.g., brightness(100%) or
brightness(1)) in the .emoji-white filter to avoid washed-out results and then
test the .emoji-white appearance across target browsers/emoji renderers to
confirm the visual outcome.

284-313: Consider respecting prefers-reduced-motion for dossier animations.

The page-turn animations use 3D transforms which may cause discomfort for users with motion sensitivity. While there's a prefers-reduced-motion block at lines 791-808, it doesn't cover .dossier-page-enter and .dossier-page-exit.

♿ Proposed fix to add reduced motion support

Add the dossier animation classes to the reduced motion media query:

 `@media` (prefers-reduced-motion: reduce) {
 
   .animate-twinkle,
   .animate-pulse,
   .animate-spin,
   .animate-float,
-  .animate-wiggle {
+  .animate-wiggle,
+  .dossier-page-enter,
+  .dossier-page-exit {
     animation: none !important;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 284 - 313, Add reduced-motion support by
overriding the page-turn animations inside the existing prefers-reduced-motion
media query: target .dossier-page-enter, .dossier-page-exit and the `@keyframes`
page-turn-in/ page-turn-out effects so they do not animate for users who prefer
reduced motion; set animation: none (or animation-duration: 0s and
animation-fill-mode: none) and ensure transform/opacity are reset (transform:
none; opacity: 1) to prevent 3D motion while keeping the final visual state
consistent for .dossier-page-enter and .dossier-page-exit.

961-967: Use Tailwind utility classes instead of direct CSS animation properties.

The accordion-down and accordion-up keyframes are defined in tailwind.config.js and tailwind.config.ts, but the CSS in app/globals.css uses raw animation properties that reference them. Instead of:

.pro-panel-accordion [data-state="open"] > [role="region"] {
  animation: accordion-down 0.2s ease-out;
}

Use the Tailwind utility classes that are already configured (animate-accordion-down and animate-accordion-up), similar to how components/ui/accordion.tsx applies them. This is cleaner and ensures consistency across the codebase.

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

In `@app/globals.css` around lines 961 - 967, Replace the raw animation
declarations in the .pro-panel-accordion selectors with Tailwind utility
classes: remove `animation: accordion-down 0.2s ease-out;` and `animation:
accordion-up 0.2s ease-out;` and instead apply the configured utilities (e.g.
use `@apply animate-accordion-down` for `.pro-panel-accordion
[data-state="open"] > [role="region"]` and `@apply animate-accordion-up` for
`.pro-panel-accordion [data-state="closed"] > [role="region"]`) so the selectors
use the `animate-accordion-down` / `animate-accordion-up` utilities defined in
tailwind config, matching how `components/ui/accordion.tsx` does it.
package.json (1)

160-160: Remove yarn from dependencies and optionally add packageManager field for npm pinning.

Line 160 lists yarn as a production dependency, but the project uses npm exclusively (all scripts reference npm/next/nodemon, no yarn commands exist, and no yarn.lock file is present). The CHANGELOG explicitly notes this was incorrectly included. Remove the dependency safely, and optionally add a top-level packageManager field to document npm as the required package manager if toolchain pinning is desired.

Suggested cleanup
 {
   "name": "groqtales",
   "version": "1.3.9",
+  "packageManager": "npm@10",
   ...
   "dependencies": {
-    "yarn": "^1.22.22",
     ...
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 160, Remove the erroneous production dependency "yarn"
from package.json's dependencies object (search for the "yarn": "^1.22.22"
entry) and save the file; optionally add a top-level packageManager field (for
example "packageManager": "npm@8" or another pinned npm version) to document the
intended package manager if you want toolchain pinning. Ensure package.json
remains valid JSON (commas adjusted) after removal and run npm install to
confirm no changes are required in lockfiles or build scripts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 59: The listed packages are build/deploy tools and should be moved out of
runtime dependencies into devDependencies: remove "@cloudflare/next-on-pages",
"autoprefixer", "postcss", "tailwindcss", "eslint-config-next", "typescript",
and "yarn" from the dependencies section and add them under devDependencies
instead; also set the packageManager field (e.g., "packageManager":
"yarn@<version>") in package.json so yarn isn’t shipped as a runtime dependency.
Ensure package names exactly match the current entries to avoid duplication and
update any CI/build scripts if they referenced these as runtime deps.

---

Nitpick comments:
In `@app/globals.css`:
- Around line 956-958: The rule only targets WebKit/Blink via
input[type="range"]::-webkit-slider-thumb; add a matching Firefox rule using
input[type="range"]::-moz-range-thumb with the same declarations (cursor:
pointer) so the range slider thumb is styled in Firefox as well; update any
related vendor-specific properties if present to keep behavior consistent across
browsers.
- Around line 908-912: The .noir-sidebar-panel rule lacks the Safari-prefixed
backdrop filter; add a -webkit-backdrop-filter: blur(8px) declaration alongside
the existing backdrop-filter: blur(8px) in the .noir-sidebar-panel CSS block so
Safari/iOS renders the blur (place the -webkit- prefixed property before or
alongside the unprefixed property for compatibility).
- Around line 987-989: The .emoji-white rule uses filter: grayscale(1)
brightness(100) which applies a 100x multiplier; change the brightness token to
a valid, non-extreme value (e.g., brightness(100%) or brightness(1)) in the
.emoji-white filter to avoid washed-out results and then test the .emoji-white
appearance across target browsers/emoji renderers to confirm the visual outcome.
- Around line 284-313: Add reduced-motion support by overriding the page-turn
animations inside the existing prefers-reduced-motion media query: target
.dossier-page-enter, .dossier-page-exit and the `@keyframes` page-turn-in/
page-turn-out effects so they do not animate for users who prefer reduced
motion; set animation: none (or animation-duration: 0s and animation-fill-mode:
none) and ensure transform/opacity are reset (transform: none; opacity: 1) to
prevent 3D motion while keeping the final visual state consistent for
.dossier-page-enter and .dossier-page-exit.
- Around line 961-967: Replace the raw animation declarations in the
.pro-panel-accordion selectors with Tailwind utility classes: remove `animation:
accordion-down 0.2s ease-out;` and `animation: accordion-up 0.2s ease-out;` and
instead apply the configured utilities (e.g. use `@apply animate-accordion-down`
for `.pro-panel-accordion [data-state="open"] > [role="region"]` and `@apply
animate-accordion-up` for `.pro-panel-accordion [data-state="closed"] >
[role="region"]`) so the selectors use the `animate-accordion-down` /
`animate-accordion-up` utilities defined in tailwind config, matching how
`components/ui/accordion.tsx` does it.

In `@package.json`:
- Line 160: Remove the erroneous production dependency "yarn" from
package.json's dependencies object (search for the "yarn": "^1.22.22" entry) and
save the file; optionally add a top-level packageManager field (for example
"packageManager": "npm@8" or another pinned npm version) to document the
intended package manager if you want toolchain pinning. Ensure package.json
remains valid JSON (commas adjusted) after removal and run npm install to
confirm no changes are required in lockfiles or build scripts.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 987a4a8 and e3d9d5f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • app/globals.css
  • components/madhava-helpbot.tsx
  • lib/supabase/client.ts
  • package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/supabase/client.ts

Comment thread package.json
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/globals.css`:
- Around line 970-976: biome is rejecting Tailwind directives like `@apply` used
in globals.css (e.g., selectors such as .pro-panel-accordion [data-state="open"]
> [role="region"]), so update your Biome config by enabling the CSS parser
option "tailwindDirectives": set css.parser.tailwindDirectives to true in
biome.json so Tailwind-specific syntax is recognized project-wide; this change
ensures `@apply` and other directives no longer produce parse errors.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3d9d5f and 57a9327.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • app/globals.css
  • package.json

Comment thread app/globals.css Outdated
@Drago-03 Drago-03 added OSCG26 Open Source Connect Global 2026 Contributions Expert labels Mar 1, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 1, 2026

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

Project Deployment Actions Updated (UTC)
groq-tales Error Error Mar 7, 2026 5:59am

@Drago-03
Copy link
Copy Markdown
Member

Drago-03 commented Mar 1, 2026

  1. Fix all red CI checks (Code Quality, Lint, Lighthouse, Dependency Review, Cloudflare).

  2. Raise docstring coverage to required 80% for new/changed logic, especially in ‎⁠lib/groq-service.ts⁠, ‎⁠src/store/proPanelStore.ts⁠, and any other touched modules.

  3. In ‎⁠ModelSettingsSchema⁠ (‎⁠lib/schemas/proPanelSchemas.ts⁠), re‑verify the ‎⁠modelSelection⁠ enum against Groq’s current ‎⁠/models⁠ response; remove any deprecated/invalid IDs and ensure defaults only use live models.

  4. Do one focused accessibility pass on the Pro Panel in the preview:
    ▫ Keyboard navigation through sliders, accordions, pacing buttons, and tags.
    ▫ Confirm labels/‎⁠aria-*⁠ wiring are correct.
    ▫ Ensure collapsed content isn’t focusable.

Copy link
Copy Markdown
Member

@Drago-03 Drago-03 left a comment

Choose a reason for hiding this comment

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

update with the requested changes as per coderabbit

@Drago-03
Copy link
Copy Markdown
Member

Drago-03 commented Mar 1, 2026

  • remove this extra footer
  • update this from /pro-panel to create/ai-story
image

- Fix prettier formatting and import order across all Pro Panel files
- Replace error: any with error: unknown in API route
- Add JSDoc to 93/93 exported symbols (100% coverage)
- Update Groq model IDs: default llama-3.3-70b-versatile, remove deprecated
- Remove duplicate noir footer from ProPanel component
- Rename route /pro-panel → /create/ai-story-pro
Copy link
Copy Markdown
Member

@Drago-03 Drago-03 left a comment

Choose a reason for hiding this comment

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

update with requested changes and resolve conflicts

@rohits1450
Copy link
Copy Markdown
Author

@Drago-03, sure will work on the requested changes and conflicts.

@Drago-03
Copy link
Copy Markdown
Member

Drago-03 commented Mar 5, 2026

@rohits1450 kindly update as we need to move forward with more changes.

@rohits1450
Copy link
Copy Markdown
Author

rohits1450 commented Mar 5, 2026

@Drago-03 Good afternoon sir, I'm currently out of station, will complete the updates and report it to you soon.

- Add htmlFor/id, aria-labelledby, aria-pressed to Pro Panel controls
- Add Info icon tooltips with descriptions on all control types
- Add rate limiting to /api/groq route (10 req/60s via Upstash Redis)
- Add 70 unit tests for Zod schemas and preset save/load logic
- Enhance ModelSettingsSection with power/cost/quality guide
# Conflicts:
#	.github/workflows/deployment.yml
#	README.md
#	SECURITY.md
#	app/community/creators/page.tsx
#	app/create/ai-story/page.tsx
#	app/create/page.tsx
#	app/nft-gallery/page.tsx
#	app/page.tsx
#	app/profile/[slug]/page.tsx
#	app/upload/page.tsx
#	components/back-to-top.tsx
#	components/footer.tsx
#	components/header.tsx
#	components/madhava-helpbot.tsx
#	lib/groq-service.ts
#	next.config.js
#	package-lock.json
#	package.json
#	server/backend.js
#	server/routes/stories.js
#	server/routes/users.js
…d tests

- Remove duplicate inline descriptions; show descriptions only in tooltip hover
- Add rich parameterKey tooltips (title/examples/powerLevel) to 28 controls
- Create tooltip-enhanced.tsx with Radix Portal and proper hover behavior
- Create parameterTooltips.ts with 30+ parameter descriptions
- Fix mugshot overlay scanlines (globals.css gradient simplification)
- Fix tooltip triggers: wrap Info icons in focusable <span> elements
- Enhance /api/groq with request size limits, input validation, error categorization
- Add sr-only hidden descriptions for screen reader accessibility
- Add tests: Zod schema validation, preset save/load, API security
@rohits1450
Copy link
Copy Markdown
Author

@Drago-03 kindly review the changes

- Remove non-existent generateStoryWithProConfig import
- Use generateStoryContent for Pro Panel generation path
- Fix duplicate computed property keys (STORY_ANALYSIS/RECOMMENDATIONS share same model ID)
- Auto-format all files with Prettier
- Replace any type with proper ContactInfoProps in contact/page.tsx
Comment thread app/upload/page.tsx Outdated
{coverImagePreview ? (
<div className="absolute inset-0">
<img
src={coverImagePreview}

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.
Comment thread app/upload/page.tsx Outdated
<div className="flex-1 w-full bg-black/80 rounded-2xl border border-white/10 overflow-hidden relative group">
{filePreview ? (
<object
data={filePreview}

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.
- Resolved 20+ conflicts across app/, components/, server/, wiki/
- Took upstream: dashboard, ai-story/page (VedaScript Engine), home page,
  create-story-dialog, splash-screen, privacy-settings, all server files,
  wiki docs
- Kept HEAD: blog pages (Monad branding), health API routes
- Manually merged components/ai-story-generator.tsx — preserved NFT minting
  logic (handleMintNFT, session lock, idempotency hash, wallet connect UI)
  on top of upstream's VedaScript Engine UI overhaul
- Fixed TypeScript compile errors in ai-story-generator.tsx post-merge
- Fixed tsconfig.json ignoreDeprecations from '6.0' to '5.0' (TS 5.9.3)
# Conflicts:
#	app/actions/auth.ts
#	app/blog/blog-1/page.tsx
#	app/docs/page.tsx
#	app/gallery/page.tsx
#	app/marketplace/page.tsx
#	app/profile/[slug]/client.tsx
#	app/stories/[id]/client.tsx
#	app/stories/[id]/page.tsx
#	components/community-feed.tsx
#	components/footer.tsx
#	components/loading-screen.tsx
#	components/providers/web3-provider.tsx
#	components/settings/profile-form.tsx
#	hooks/use-monad.ts
#	hooks/use-user-role.ts
#	lib/blockchain.ts
#	server/routes/auth.js
#	server/routes/drafts.js
#	server/routes/stories.js
- Compute branch slug dynamically for NEXT_PUBLIC_URL in preview builds
  (was hardcoded to groqtales-preview.pages.dev which does not exist;
  now resolves to <branch-slug>.groqtales.pages.dev)
- Expose dynamic preview URL as GitHub deployment environment URL via
  steps.deploy.outputs.url
- Quote branch name in wrangler deploy command to handle slashes safely
- Update wrangler.toml env var comments: replace MONAD_TEST_RPC_URL /
  MINTER_PRIVATE_KEY with ALCHEMY_ETH_MAINNET_HTTP_URL /
  MINT_AUTHORITY_PRIVATE_KEY / NEXT_PUBLIC_CONTRACT_ADDR
- Fix [env.preview] name to groqtales (matches CF Pages project name)
@rohits1450
Copy link
Copy Markdown
Author

Hi @Drago-03 , so I have implemented a few suggestions asked for from your side.

Accessibility (components/pro-panel/controls/index.tsx)
All controls (sliders, inputs, selects, switches, toggles) are now fully screen-reader-friendly via proper aria-* attributes, linked labels, role="group" groupings, and sr-only description spans for keyboard/assistive tech users.

Tests (3 new test files)

proPanelSchemas.test.ts — 40+ Zod schema validation tests across all 9 parameter categories

PresetManager.test.ts — Save, load, delete, import/export, and edge case (malformed JSON, concurrent ops) coverage

groq.test.ts — API route tests covering size limits, rate limiting, validation errors, and malformed input handling

/api/groq Route Protection
Added request size cap (100KB), prompt/content length limits, action whitelisting, full Zod validation for Pro Panel config, structured error categorization (400/401/413/429/504/500), anonymized request logging, and safe JSON parsing.

Parameter Tooltips (UI Documentation)
Created parameterTooltips.ts with 30+ rich definitions (title, description, examples, impact rating). Built an enhanced Radix UI tooltip component and wired parameterKey props across all 28 controls in 9 sections. Inline descriptions were replaced with tooltip-only display to avoid duplication.

cf-worker/node_modules Cleanup
No code changes required — this was a pre-existing infra task (accidental commit cleanup + .gitignore fix). A BFG/filter-repo history rewrite is noted as a follow-up task post-PR merge.

Additionally the coderabbit issues are also resolved with Cloudflare config, and the branch is up-to-date with the changes.

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

Labels

Enhancement New feature or request in progress Expert Feature Request or proposal for a new feature OSCG26 Open Source Connect Global 2026 Contributions size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[UI] Advanced Story Controls “Pro Panel” with Presets & Preset Sharing

3 participants