Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(find:*)",
"Bash(cat:*)",
"Bash(yarn workspace:*)",
"Bash(git commit:*)",
"Bash(git add:*)"
]
}
}
12 changes: 11 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ This project prefers a highly componentized React codebase that avoids duplicate
- **Redux**: Compose selectors and helpers rather than copy/pasting logic. UI components should avoid data manipulation—use Redux selectors to transform and format data instead of doing it in components.
- **Error Handling**: Keep error handling reasonable but not excessive. This is a small app - simple null checks and basic 404s are fine. Don't over-engineer with detailed error types for every edge case.
- **Unit Tests**: Write unit tests for important service functions, especially those involving business logic or data transformations.
- **Testing**: After changes, run `yarn test:codex` from the repository root to ensure all tests pass.
- **Testing**: After changes, run `yarn test:codex` from the repository root to ensure all tests pass. Requires `dangerouslyDisableSandbox` (MongoMemoryServer binds to `0.0.0.0`).

## Screenshot Tests (Remote)

Screenshot tests run via GitHub Actions since they need Playwright with a browser.

1. Run `./scripts/test-screenshots-remote.sh` (`dangerouslyDisableSandbox` required) — pushes a temp branch, triggers the `test.yml` workflow
2. Parse the branch name from stdout (format: `screenshot-test-<timestamp>`)
3. Poll for the run ID: `gh run list --workflow=test.yml --branch=$BRANCH --limit=1 --json databaseId,status`
4. Watch it: `gh run watch $RUN_ID`
5. Clean up: `git push origin --delete $BRANCH`

Follow these guidelines to keep the codebase clean and maintainable.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
6 changes: 5 additions & 1 deletion apps/react/src/components/FlashCardOptionsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useIsCardOwner } from '../utils/useIsCardOwner';
import { useAppDispatch, useAppSelector } from 'MemoryFlashCore/src/redux/store';
import { updateHiddenCards } from 'MemoryFlashCore/src/redux/actions/update-hidden-cards-action';
import { deleteCard } from 'MemoryFlashCore/src/redux/actions/delete-card-action';
import { recordAttempt } from 'MemoryFlashCore/src/redux/actions/record-attempt-action';
import {
CardWithAttempts,
selectHiddenCardIds,
Expand Down Expand Up @@ -52,7 +53,10 @@ export const FlashCardOptionsMenu: React.FC<FlashCardOptionsMenuProps> = ({
if (canReveal) {
items.unshift({
label: 'Reveal answer',
onClick: () => setShowAnswer(true),
onClick: () => {
dispatch(recordAttempt(false));
setShowAnswer(true);
},
});
}

Expand Down
4 changes: 2 additions & 2 deletions apps/react/src/components/keyboard/KeyBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { WhiteKey } from './WhiteKey';

export const Keyboard = () => {
return (
<div className="p-4 flex justify-center">
<div className="relative flex justify-center">
<div className="p-4 flex justify-center w-full overflow-x-auto">
<div className="relative flex justify-center min-w-max">
{/* <div className='relative flex justify-center rounded-xl overflow-hidden '> */}
{[1, 2, 3, 4, 5].map((octave) => (
<KeyBoardKeys key={octave} rootMidi={36 + (octave - 1) * 12} />
Expand Down
5 changes: 4 additions & 1 deletion apps/react/src/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export const Layout: React.FC<LayoutProps> = ({
back,
}) => {
return (
<div className="h-screen w-full flex flex-col overflow-scroll bg-app" onScroll={onScroll}>
<div
className="h-screen w-full flex flex-col overflow-y-auto overflow-x-hidden bg-app"
onScroll={onScroll}
>
<Navbar right={right} back={back} />
<div
className={clsx(
Expand Down
4 changes: 2 additions & 2 deletions apps/react/src/components/notation/ChordToneDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ToneChip: React.FC<ToneChipProps> = ({ tone, required, onToggle }) => (
className={`px-2 py-0.5 rounded text-xs font-medium transition-colors ${
required
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-600 border border-dashed border-gray-400'
: 'bg-gray-200 dark:bg-gray-600 text-gray-600 dark:text-gray-200 border border-dashed border-gray-400 dark:border-gray-500'
}`}
>
{tone}
Expand All @@ -32,7 +32,7 @@ export const ChordToneDisplay: React.FC<ChordToneDisplayProps> = ({ chord, onTog

return (
<div
className={`rounded-lg p-2 text-sm ${isInvalid ? 'bg-red-100 text-red-800' : 'bg-gray-100'}`}
className={`rounded-lg p-2 text-sm ${isInvalid ? 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200' : 'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200'}`}
>
<div className="font-medium mb-1">{chord.chordName}</div>
{isInvalid ? (
Expand Down
2 changes: 1 addition & 1 deletion apps/react/tests/custom-deck-chord-memory-to-study.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ test('Create custom deck with Chord Memory card, study it, then edit it', async

await page.evaluate(() => {
window.scrollTo(0, 0);
document.querySelector('.overflow-scroll')?.scrollTo(0, 600);
document.querySelector('.overflow-y-auto')?.scrollTo(0, 600);
});
await expect(output).toHaveScreenshot('custom-deck-chord-memory-edit.png', screenshotOpts);

Expand Down
2 changes: 1 addition & 1 deletion apps/react/tests/custom-deck-notation-to-study.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ test('Create custom deck, add notation and text cards, then study', async ({
await expect(page.locator('#text-prompt')).toHaveValue(promptText);
await page.evaluate(() => {
window.scrollTo(0, 0);
document.querySelector('.overflow-scroll')?.scrollTo(0, 600);
document.querySelector('.overflow-y-auto')?.scrollTo(0, 600);
});
await expect(output).toHaveScreenshot(
'custom-deck-notation-to-study-notation-input-edit.png',
Expand Down
2 changes: 1 addition & 1 deletion apps/react/tests/helpers/visual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function setStaticScroll(
await page.evaluate(
({ windowTop: winTop, overflowTop: overTop }) => {
window.scrollTo(0, winTop);
document.querySelector('.overflow-scroll')?.scrollTo(0, overTop);
document.querySelector('.overflow-y-auto')?.scrollTo(0, overTop);
},
{ windowTop, overflowTop },
);
Expand Down
2 changes: 1 addition & 1 deletion apps/react/tests/notation-input-e-major-g-sharp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ test('NotationInputScreen E major G# rendering', async ({ page }) => {
// Wait a bit for rendering
await page.evaluate(() => {
window.scrollTo(0, 0);
document.querySelector('.overflow-scroll')?.scrollTo(0, 300);
document.querySelector('.overflow-y-auto')?.scrollTo(0, 300);
});

await expect(output).toHaveScreenshot('notation-input-e-major-g-sharp.png', screenshotOpts);
Expand Down
2 changes: 1 addition & 1 deletion apps/react/tests/notation-input-text-prompt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ test('NotationInputScreen text prompt card type', async ({ page }) => {
await page.waitForTimeout(200);
await page.evaluate(() => {
window.scrollTo(0, 0);
document.querySelector('.overflow-scroll')?.scrollTo(0, 300);
document.querySelector('.overflow-y-auto')?.scrollTo(0, 300);
});

await expect(output).toHaveScreenshot('notation-input-text-prompt.png', screenshotOpts);
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"dev": "concurrently \"yarn workspace MemoryFlashServer dev\" \"yarn workspace MemoryFlashReact dev\"",
"test": "yarn workspace MemoryFlashServer test && yarn workspace MemoryFlashCore test && yarn workspace MemoryFlashReact test:screenshots",
"test:codex": "yarn workspace MemoryFlashServer build && yarn workspace MemoryFlashReact build && yarn workspace MemoryFlashServer test && yarn workspace MemoryFlashCore test"
"test:codex": "yarn workspace MemoryFlashServer build && yarn workspace MemoryFlashReact build && yarn workspace MemoryFlashServer test && yarn workspace MemoryFlashCore test",
"test:screenshots:remote": "./scripts/test-screenshots-remote.sh"
},
"workspaces": [
"apps/*",
Expand Down
57 changes: 57 additions & 0 deletions scripts/test-screenshots-remote.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/bin/bash
set -e

# Get current branch and create temp branch name
ORIGINAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)
TEMP_BRANCH="screenshot-test-$(date +%s)"

echo "Creating temporary branch: $TEMP_BRANCH"

# Stash any changes (including untracked)
STASH_RESULT=$(git stash push -u -m "temp-screenshot-test" 2>&1) || true
HAS_STASH=false
if [[ "$STASH_RESULT" != *"No local changes"* ]]; then
HAS_STASH=true
fi

# Create and switch to temp branch
git checkout -b "$TEMP_BRANCH"

# Apply stashed changes if we had any
if [ "$HAS_STASH" = true ]; then
git stash pop
fi

# Commit everything
git add -A
git commit -m "Screenshot test - temporary commit" --allow-empty

# Push temp branch
git push origin "$TEMP_BRANCH"

# Trigger workflow
echo "Triggering workflow on $TEMP_BRANCH..."
gh workflow run test.yml --ref "$TEMP_BRANCH"

# Stash changes again before switching back (so we can restore them)
if [ "$HAS_STASH" = true ]; then
git stash push -u -m "restore-to-original"
fi

# Switch back to original branch
git checkout "$ORIGINAL_BRANCH"

# Restore uncommitted changes to original branch
if [ "$HAS_STASH" = true ]; then
git stash pop
fi

# Delete local temp branch
git branch -D "$TEMP_BRANCH"

echo ""
echo "Workflow triggered on branch: $TEMP_BRANCH"
echo "Run 'gh run watch' to monitor progress"
echo ""
echo "To clean up remote branch after tests complete:"
echo " git push origin --delete $TEMP_BRANCH"