Skip to content

jonamar/unscramm

Repository files navigation

Unscramm v3 (Vite + React + TypeScript)

Radically simple rewrite of Unscramm with a minimal stack:

  • Vite + React + TypeScript
  • Tailwind CSS v4 (tokens via @theme in CSS)
  • Framer Motion for animations
  • Vitest + Testing Library for tests

See PRD: docs/unscramm-v3-prd-spec.md and style guide: docs/design_guidelines/styleguide.md.

Purpose & Intent

Unscramm is an interactive "animated spellcheck" that shows how a misspelled word transforms into the correct word via clear, accessible character transitions. It serves two goals:

  • Accessibility-focused visualization for dyslexic readers, or anyone learning to read, to see how letters change through deletion, movement, and insertion.
  • A process experiment in rigorous, modular development where the majority of implementation is driven through AI-assisted workflows with clear specs and tests.

Multi-Platform Support

Unscramm runs on multiple platforms from a single codebase:

  • 🌐 Chrome Extension - Browser popup with spell-checking
  • 🖥️ Mac Menu Bar App - Native macOS app via Tauri

See PLATFORM-GUIDE.md for architecture details and platform-specific development.

Quick Start

# Chrome Extension
npm install
npm run dev:chrome          # Development
npm run build:chrome        # Production build

# Mac Menu Bar App (macOS only)
npm run tauri:dev          # Development
npm run tauri:build        # Production build

Getting Started

  • Install: npm install
  • Dev: npm run dev (defaults to Chrome extension)
  • Build: npm run build (defaults to Chrome extension)
  • Test (CI): npm test

Chrome Extension

The app is packaged as a Manifest V3 Chrome extension with a popup UI and built-in spell-checking capabilities.

Features

  • Built-in Dictionary: Includes a frequency-based English dictionary (~370k words) for offline spell suggestions
  • Smart Suggestions: Uses Damerau-Levenshtein distance to find the best corrections for misspelled words
  • Three-Stage Flow:
    1. Input: Paste from clipboard or type a word
    2. Suggestions: View ranked spelling suggestions
    3. Animation: Watch the transformation from misspelled to correct word
  • Animated Visualization: Color-coded character transitions (red=deletions, green=insertions, yellow=moves)

Building the Extension

npm run build

This builds the extension to the dist/ folder, including:

  • manifest.json - Chrome extension manifest
  • index.html - Popup UI
  • assets/ - Bundled JS, CSS, and images
  • icon*.svg - Extension icons
  • frequency-dictionary.txt - Built-in word frequency dictionary

Loading in Chrome

  1. Build the extension: npm run build
  2. Open Chrome and navigate to chrome://extensions/
  3. Enable "Developer mode" (toggle in top-right)
  4. Click "Load unpacked"
  5. Select the dist/ folder
  6. The Unscramm extension icon will appear in your toolbar
  7. Click the icon to open the popup and use the app

Design Tokens

Source of truth: src/index.css under the @theme block. These CSS variables define colors, typography, and semantic states used across the app.

  • Colors: --color-bg, --color-panel, --color-button, --color-button-hover, --color-text, --color-text-secondary
  • Semantic colors: --color-deletion (red), --color-move (yellow), --color-insertion (green)
  • Typography: --font-sans

Usage:

  • Tailwind with variables: bg-[--color-panel] text-[--color-text]
  • Utilities from src/index.css: .text-deletion, .text-move, .text-insertion

Benefits: consistency, easy theming, maintainability, and accessible contrast tuning from a single place.

Testing Stack

  • Runner: Vitest (v2)
  • Environment: jsdom
  • Library: @testing-library/react + @testing-library/jest-dom
  • Setup: src/test/setup.ts

The setup file does two things:

  1. jest-dom matchers via @testing-library/jest-dom/vitest so you can use matchers like toBeInTheDocument().
  2. matchMedia polyfill for jsdom so components using reduced-motion queries work in tests.

matchMedia polyfill

In src/test/setup.ts, we define a minimal matchMedia on globalThis and mirror it to window:

import '@testing-library/jest-dom/vitest'

if (typeof (globalThis as any).matchMedia !== 'function') {
  const reduced = (globalThis as any).__TEST_MATCH_MEDIA_REDUCED__ ?? true
  ;(globalThis as any).matchMedia = (query: string) => ({
    matches: reduced && query.includes('prefers-reduced-motion: reduce'),
    media: query,
    onchange: null,
    addListener() {},
    removeListener() {},
    addEventListener() {},
    removeEventListener() {},
    dispatchEvent() { return false },
  })
}
if (typeof window !== 'undefined' && typeof window.matchMedia !== 'function') {
  // @ts-ignore
  window.matchMedia = (globalThis as any).matchMedia
}

Tip: set (globalThis as any).__TEST_MATCH_MEDIA_REDUCED__ = false if you need to simulate non-reduced motion in specific tests.

Running tests

npm test

There are unit tests for computeEditPlan() and a smoke test for WordUnscrambler that asserts the final DOM equals the target word after animation.

Tailwind CSS

This project uses Tailwind CSS v4 with tokens defined directly in CSS (no tailwind.config.js required). See src/index.css:

@import "tailwindcss";
@theme {
  --color-bg: #111;
  --color-panel: #181818;
  --color-button: #333;
  --color-button-hover: #222;
  --color-text: #ffffff;
  --color-text-secondary: #777777;
  --color-deletion: #ef4444;
  --color-insertion: #22c55e;
  --color-move: #eab308;
  --font-sans: "Istok Web", system-ui, Avenir, Helvetica, Arial, sans-serif;
}

These map 1:1 to the semantic tokens in docs/design_guidelines/styleguide.md.

Component: WordUnscrambler

The core component lives at src/components/WordUnscrambler.tsx and follows the PRD phases:

  • idledeletingmovinginsertingfinal

It uses computeEditPlan() to determine deletions/insertions and performs a FLIP-style reorder for survivors. Reduced motion is respected via matchMedia('(prefers-reduced-motion: reduce)').

About

Visually unscramble words to make sense of them -- for those of us who find spelling hard.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •