Skip to content

feat(quiz): add hint and explain buttons with AI composer - #330

Merged
urjitc merged 2 commits into
capy/consolidate-item-schemasfrom
capy/quiz-hint-explain-buttons
Apr 10, 2026
Merged

feat(quiz): add hint and explain buttons with AI composer#330
urjitc merged 2 commits into
capy/consolidate-item-schemasfrom
capy/quiz-hint-explain-buttons

Conversation

@urjitc

@urjitc urjitc commented Apr 10, 2026

Copy link
Copy Markdown
Member

Add on-demand Hint and Explain buttons to quiz UI that fill AI composer with contextual prompts without auto-sending, allowing users to request hints before answering or explanations after submitting.

Changes

  • src/components/workspace-canvas/QuizContent.tsx: Add Lightbulb, MessageCircleQuestion, and focusComposerInput imports; implement handleAskHint and handleAskExplain handlers that ensure card selection, expand chat panel, set composer text, and focus input; add pre-submission Hint button in left spacer area (shows when !isSubmitted); add post-submission "Ask AI to explain" button inside correct/incorrect feedback box (shows when isSubmitted)

Open in Capy ENG-12 · 5.4

@urjitc urjitc added the capy Generated by capy.ai label Apr 10, 2026 — with Capy AI
@vercel

vercel Bot commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
thinkex Ready Ready Preview, Comment Apr 10, 2026 3:37am

Request Review

@coderabbitai

coderabbitai Bot commented Apr 10, 2026

Copy link
Copy Markdown

Important

Review skipped

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

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

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 17f1464c-78eb-4571-90bd-71ce9f549e8c

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch capy/quiz-hint-explain-buttons

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.

@greptile-apps

greptile-apps Bot commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds on-demand Hint (pre-submission) and Ask AI to explain (post-submission) buttons to the quiz UI. Both handlers call composer.setText() to pre-fill the AI composer without auto-sending — a good pattern — but diverge from the established handleUpdateQuiz approach by omitting the else branch and try/catch that surface toast errors when the composer is unavailable.

Confidence Score: 4/5

Safe to merge after adding error handling to the two new handlers; the silent failure + unintended card-selection side effect should be addressed first.

One P1 finding: both handleAskHint and handleAskExplain silently fail with a card-selection side effect when the composer is unavailable, deviating from the established pattern in handleUpdateQuiz. The fix is small and well-defined.

src/components/workspace-canvas/QuizContent.tsx — handleAskHint and handleAskExplain need else-branch toast errors and try/catch.

Important Files Changed

Filename Overview
src/components/workspace-canvas/QuizContent.tsx Adds Hint and Explain AI composer buttons; both new handlers silently fail with an unintended card-selection side effect when the composer is unavailable, unlike the existing handleUpdateQuiz pattern which surfaces toast errors.

Fix All in Cursor

Reviews (1): Last reviewed commit: "Add on-demand Hint and Explain buttons t..." | Re-trigger Greptile

Comment on lines +276 to +310
const handleAskHint = () => {
if (!selectedCardIds.has(item.id)) {
toggleCardSelection(item.id);
}

// Stop propagation so card click doesn't open modal when interacting with quiz
const stopPropagation = (e: React.MouseEvent) => {
e.stopPropagation();
};
const composer = aui?.composer?.();
if (composer) {
composer.setText(
`Give me a hint for this question in "${item.name}": ${currentQuestion.questionText}`,
);
useUIStore.getState().setIsChatExpanded(true);
focusComposerInput(true);
}
};

if (!currentQuestion && !showResults) {
// Template-created items have "Update me" name and should show generating skeleton
const isAwaitingGeneration = item.name === "Update me" && questions.length === 0;
const handleAskExplain = () => {
if (!selectedCardIds.has(item.id)) {
toggleCardSelection(item.id);
}

if (isAwaitingGeneration) {
return (
<div className={cn("flex flex-col h-full", className)}>
{/* Question Area Skeleton */}
<div className={cn(
"flex-1 p-2",
"overflow-y-auto",
"workspace-card-readonly-editor",
"cursor-default"
)}>
<div className="mb-6">
<div className="text-sm text-gray-500/60 prose prose-sm max-w-none dark:text-foreground/60 dark:prose-invert">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse"></div>
Generating quiz questions...
</div>
</div>
</div>

{/* Options Skeleton */}
<div className="space-y-2">
{[0, 1, 2, 3].map((index) => (
<div
key={index}
className="w-full p-3 text-left rounded-lg border bg-white/5 border-white/10"
>
<div className="flex items-center gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium border bg-white/10 border-white/20 text-foreground/60 dark:text-white/60">
{String.fromCharCode(65 + index)}
</span>
<div className="text-sm text-foreground/40 flex-1 dark:text-white/40">
<div className="w-3/4 h-3 bg-white/10 rounded animate-pulse"></div>
</div>
</div>
</div>
))}
</div>

{/* Progress Bar Skeleton */}
<div className="mt-4 flex items-center justify-between">
<div className="flex-1 mx-4">
<div className="w-full h-1.5 bg-white/10 rounded-full overflow-hidden">
<div className="h-full bg-blue-400 rounded-full animate-pulse" style={{ width: '30%' }} />
</div>
</div>
</div>
</div>
const composer = aui?.composer?.();
if (composer) {
const userAnswer =
selectedAnswer !== null
? currentQuestion.options[selectedAnswer]
: "N/A";
const correctAnswer =
currentQuestion.options[currentQuestion.correctIndex];
composer.setText(
`Explain this question in "${item.name}": ${currentQuestion.questionText}\n\nI answered: ${userAnswer}\nCorrect answer: ${correctAnswer}`,
);
useUIStore.getState().setIsChatExpanded(true);
focusComposerInput(true);
}
};

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.

P1 Silent failure with unintended side-effect when composer is unavailable

Both handleAskHint and handleAskExplain lack the else branch that handleUpdateQuiz uses to surface a toast error. When aui?.composer?.() returns null, the button does nothing visible — but toggleCardSelection(item.id) has already fired, silently toggling the card's selection state. Users will see their card's selection change but receive no explanation. handleUpdateQuiz shows toast.error("Chat not available. Please try again.") exactly for this case.

Additionally, neither handler wraps composer.setText() in a try/catch, so a runtime throw would be unhandled — unlike handleUpdateQuiz which catches and toasts the error.

Suggested change
const handleAskHint = () => {
if (!selectedCardIds.has(item.id)) {
toggleCardSelection(item.id);
}
// Stop propagation so card click doesn't open modal when interacting with quiz
const stopPropagation = (e: React.MouseEvent) => {
e.stopPropagation();
};
const composer = aui?.composer?.();
if (composer) {
composer.setText(
`Give me a hint for this question in "${item.name}": ${currentQuestion.questionText}`,
);
useUIStore.getState().setIsChatExpanded(true);
focusComposerInput(true);
}
};
if (!currentQuestion && !showResults) {
// Template-created items have "Update me" name and should show generating skeleton
const isAwaitingGeneration = item.name === "Update me" && questions.length === 0;
const handleAskExplain = () => {
if (!selectedCardIds.has(item.id)) {
toggleCardSelection(item.id);
}
if (isAwaitingGeneration) {
return (
<div className={cn("flex flex-col h-full", className)}>
{/* Question Area Skeleton */}
<div className={cn(
"flex-1 p-2",
"overflow-y-auto",
"workspace-card-readonly-editor",
"cursor-default"
)}>
<div className="mb-6">
<div className="text-sm text-gray-500/60 prose prose-sm max-w-none dark:text-foreground/60 dark:prose-invert">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse"></div>
Generating quiz questions...
</div>
</div>
</div>
{/* Options Skeleton */}
<div className="space-y-2">
{[0, 1, 2, 3].map((index) => (
<div
key={index}
className="w-full p-3 text-left rounded-lg border bg-white/5 border-white/10"
>
<div className="flex items-center gap-3">
<span className="flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium border bg-white/10 border-white/20 text-foreground/60 dark:text-white/60">
{String.fromCharCode(65 + index)}
</span>
<div className="text-sm text-foreground/40 flex-1 dark:text-white/40">
<div className="w-3/4 h-3 bg-white/10 rounded animate-pulse"></div>
</div>
</div>
</div>
))}
</div>
{/* Progress Bar Skeleton */}
<div className="mt-4 flex items-center justify-between">
<div className="flex-1 mx-4">
<div className="w-full h-1.5 bg-white/10 rounded-full overflow-hidden">
<div className="h-full bg-blue-400 rounded-full animate-pulse" style={{ width: '30%' }} />
</div>
</div>
</div>
</div>
const composer = aui?.composer?.();
if (composer) {
const userAnswer =
selectedAnswer !== null
? currentQuestion.options[selectedAnswer]
: "N/A";
const correctAnswer =
currentQuestion.options[currentQuestion.correctIndex];
composer.setText(
`Explain this question in "${item.name}": ${currentQuestion.questionText}\n\nI answered: ${userAnswer}\nCorrect answer: ${correctAnswer}`,
);
useUIStore.getState().setIsChatExpanded(true);
focusComposerInput(true);
}
};
const handleAskHint = () => {
if (!selectedCardIds.has(item.id)) {
toggleCardSelection(item.id);
}
const composer = aui?.composer?.();
if (composer) {
try {
composer.setText(
`Give me a hint for this question in "${item.name}": ${currentQuestion.questionText}`,
);
useUIStore.getState().setIsChatExpanded(true);
focusComposerInput(true);
} catch (error) {
toast.error("Failed to set hint. Please try again.");
}
} else {
toast.error("Chat not available. Please try again.");
}
};
const handleAskExplain = () => {
if (!selectedCardIds.has(item.id)) {
toggleCardSelection(item.id);
}
const composer = aui?.composer?.();
if (composer) {
try {
const userAnswer =
selectedAnswer !== null
? currentQuestion.options[selectedAnswer]
: "N/A";
const correctAnswer =
currentQuestion.options[currentQuestion.correctIndex];
composer.setText(
`Explain this question in "${item.name}": ${currentQuestion.questionText}\n\nI answered: ${userAnswer}\nCorrect answer: ${correctAnswer}`,
);
useUIStore.getState().setIsChatExpanded(true);
focusComposerInput(true);
} catch (error) {
toast.error("Failed to set explanation. Please try again.");
}
} else {
toast.error("Chat not available. Please try again.");
}
};

Fix in Cursor

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

No issues found across 1 file

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@urjitc
urjitc merged commit 3b46c1c into capy/consolidate-item-schemas Apr 10, 2026
4 of 5 checks passed
@urjitc
urjitc deleted the capy/quiz-hint-explain-buttons branch April 10, 2026 03:36
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Dev Board Apr 10, 2026
urjitc added a commit that referenced this pull request Apr 10, 2026
* Create canonical item-data-schemas as single source of truth; remove quiz hint/explanation/source

* Add .min(0) to correctIndex in canonical quizQuestionSchema

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Fix input schemas: require front/back/correctIndex, use permissive schema in autogen

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(quiz): add hint and explain buttons with AI composer (#330)

* Add on-demand Hint and Explain buttons to quiz UI for AI interaction

* fix(quiz): handle composer errors for hint actions

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capy Generated by capy.ai

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant