From d46758db58f61a3697f92917226bd18b461a8d56 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Mon, 4 May 2026 15:45:38 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20keyboard=20shor?=
=?UTF-8?q?tcuts=20to=20flashcard=20study?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Implements keyboard shortcuts for faster study flow (Space/Enter to flip, 1-4 to rate).
- Adds visual shortcut hints (e.g., [Espacio], [1]) to buttons for discoverability.
- Enhances accessibility with descriptive ARIA labels and titles for shortcuts.
- Includes unit tests verifying shortcut functionality.
- Records UX learnings in palette journal.
Co-authored-by: godie <227743+godie@users.noreply.github.com>
---
.Jules/palette.md | 4 +++
src/v2/pages/V2FlashcardStudy.jsx | 48 ++++++++++++++++++++++----
src/v2/pages/V2FlashcardStudy.test.jsx | 33 ++++++++++++++++++
3 files changed, 78 insertions(+), 7 deletions(-)
diff --git a/.Jules/palette.md b/.Jules/palette.md
index 697428c..89d1fdc 100644
--- a/.Jules/palette.md
+++ b/.Jules/palette.md
@@ -107,3 +107,7 @@
## 2025-06-25 - [Copy-to-Clipboard UX and Immediate Feedback]
**Learning:** Adding a copy-to-clipboard feature for key user identifiers (like email or IDs) significantly reduces friction in user workflows. Implementing this with `navigator.clipboard.writeText` and providing immediate visual feedback via a toast notification ('Correo copiado al portapapeles') confirms success. Using icon-only buttons with explicit `aria-label` and `title` ensures the feature is both accessible and intuitive.
**Action:** Always provide a copy-to-clipboard option for static text that users frequently need to reuse, and ensure immediate feedback is provided upon action.
+
+## 2025-01-15 - [Keyboard Efficiency and Discoverability in Flashcards]
+**Learning:** For high-repetition tasks like flashcard study, keyboard shortcuts (Space/Enter for flip, 1-4 for rating) dramatically improve user flow and "flow state" immersion. However, shortcuts must be discoverable; adding bracketed visual hints like "[Espacio]" or "[1]" directly to button labels, along with descriptive "aria-label" and "title" attributes, ensures accessibility and lowers the learning curve for power users.
+**Action:** Always include keyboard shortcuts for core repetitive loops and provide integrated visual hints for those shortcuts to ensure they aren't "hidden" features.
diff --git a/src/v2/pages/V2FlashcardStudy.jsx b/src/v2/pages/V2FlashcardStudy.jsx
index c21524a..8d93873 100644
--- a/src/v2/pages/V2FlashcardStudy.jsx
+++ b/src/v2/pages/V2FlashcardStudy.jsx
@@ -236,10 +236,10 @@ const Flashcard = ({ card, isFlipped, onFlip }) => (
// Quality rating buttons (SM-2 mapped to UI)
const QualityButtons = ({ onRate, disabled }) => {
const buttons = [
- { quality: 1, label: 'Otra vez', sublabel: '< 1 día', color: '#ba1a1a', icon: 'replay' },
- { quality: 3, label: 'Difícil', sublabel: '2-3 días', color: '#9c4247', icon: 'sentiment_dissatisfied' },
- { quality: 4, label: 'Bien', sublabel: '4-6 días', color: '#0fa397', icon: 'sentiment_satisfied' },
- { quality: 5, label: 'Fácil', sublabel: '7+ días', color: '#4a6360', icon: 'sentiment_very_satisfied' }
+ { quality: 1, label: 'Otra vez', shortcut: '1', sublabel: '< 1 día', color: '#ba1a1a', icon: 'replay' },
+ { quality: 3, label: 'Difícil', shortcut: '2', sublabel: '2-3 días', color: '#9c4247', icon: 'sentiment_dissatisfied' },
+ { quality: 4, label: 'Bien', shortcut: '3 / Espacio', sublabel: '4-6 días', color: '#0fa397', icon: 'sentiment_satisfied' },
+ { quality: 5, label: 'Fácil', shortcut: '4', sublabel: '7+ días', color: '#4a6360', icon: 'sentiment_very_satisfied' }
];
return (
@@ -255,13 +255,14 @@ const QualityButtons = ({ onRate, disabled }) => {
opacity: disabled ? 0.5 : 1,
border: 'none'
}}
- aria-label={`Calificar como ${btn.label}`}
+ aria-label={`Calificar como ${btn.label} (atajo: tecla ${btn.shortcut})`}
+ title={`Atajo: ${btn.shortcut}`}
>
{btn.icon}
- {btn.label}
+ {btn.label} [{btn.quality === 4 ? '3' : btn.shortcut}]
{btn.sublabel}
@@ -400,6 +401,37 @@ const V2FlashcardStudy = () => {
const handleGoHome = useCallback(() => {
history.push('/dashboard');
}, [history]);
+
+ // Keyboard shortcuts
+ useEffect(() => {
+ const handleKeyDown = (e) => {
+ // Don't trigger if any overlay is active or if user is typing (though no inputs here yet)
+ if (loading || isSubmitting || isSessionComplete || !currentCard) return;
+
+ const { key } = e;
+
+ if (!isFlipped) {
+ if (key === ' ' || key === 'Enter') {
+ e.preventDefault();
+ handleFlip();
+ }
+ } else {
+ if (key === '1') {
+ handleRate(1);
+ } else if (key === '2') {
+ handleRate(3);
+ } else if (key === '3' || key === ' ' || key === 'Enter') {
+ e.preventDefault();
+ handleRate(4);
+ } else if (key === '4') {
+ handleRate(5);
+ }
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [loading, isSubmitting, isSessionComplete, currentCard, isFlipped, handleFlip, handleRate]);
// Loading state
if (loading) {
@@ -501,9 +533,11 @@ const V2FlashcardStudy = () => {
)}
diff --git a/src/v2/pages/V2FlashcardStudy.test.jsx b/src/v2/pages/V2FlashcardStudy.test.jsx
index 75fed30..0d370a2 100644
--- a/src/v2/pages/V2FlashcardStudy.test.jsx
+++ b/src/v2/pages/V2FlashcardStudy.test.jsx
@@ -143,4 +143,37 @@ describe('V2FlashcardStudy', () => {
expect(screen.getByText('¡Todo al día!')).toBeTruthy();
});
});
+
+ it('handles keyboard shortcuts for flipping and rating', async () => {
+ render(
+
+
+
+ );
+
+ await waitFor(() => screen.getByText('¿Cuál es la tríada de Virchow?'));
+
+ // Press Space to flip
+ fireEvent.keyDown(window, { key: ' ' });
+ await waitFor(() => {
+ expect(screen.getByText(/Estasis venosa/)).toBeTruthy();
+ });
+
+ // Press '4' to rate as Easy (Quality 5)
+ fireEvent.keyDown(window, { key: '4' });
+ await waitFor(() => {
+ expect(screen.getByText('Agente causal más común de epiglotitis')).toBeTruthy();
+ });
+
+ // Press ' ' to rate as Good (Quality 4) - after flipping
+ fireEvent.keyDown(window, { key: ' ' }); // Flip second card
+ await waitFor(() => {
+ expect(screen.getByText(/Haemophilus influenzae/)).toBeTruthy();
+ });
+
+ fireEvent.keyDown(window, { key: ' ' }); // Rate as Good
+ await waitFor(() => {
+ expect(screen.getByText('Signo de Murphy positivo indica...')).toBeTruthy();
+ });
+ });
});