Skip to content

Latest commit

 

History

History
80 lines (63 loc) · 4.1 KB

File metadata and controls

80 lines (63 loc) · 4.1 KB

AGENTS.md

Guidance for AI agents and contributors working on Typeable. Read this before editing — it documents the architecture contract that keeps changes localized.

What this app is

A client-side React + TypeScript (Vite) app that ranks candidate names (brand names, words, domains — any string) by how fast/comfortable they are to type on a chosen keyboard layout, and shows a per-term keyboard heatmap. See README.md for the product overview and the typing-effort model details.

The contract (don't break these)

Everything hangs off src/model/types.ts and the barrel src/model/index.ts. The UI imports the model only through the barrel (import { analyzeTerms, getLayout, LAYOUTS } from '../model').

Stable exports — keep these signatures:

  • model/layouts.ts: LAYOUTS, getLayout(id), resolveChar(layout, char)
  • model/effort.ts: MODEL (constants), analyzeTerm(term, layout): TermAnalysis, analyzeTerms(terms, layout, sortBy?): RankedTerm[] (sorted, rank: 1 = best under the sort; sortBy is a SortMode'ease' (default) = descending WPM, 'time' = ascending total estimated ms)
  • model/keys.ts: PHYSICAL_KEYS, KEY_BY_ID, FINGER_HOME, FINGER_WEIGHT, keyDistance, travelFromHome
  • components/Keyboard.tsx: named export Keyboard(props: KeyboardProps)

Key idea: the physical keyboard is layout-independent. keys.ts defines key positions (in key units, with row stagger), finger/hand assignments, and home keys, keyed by the QWERTY base character at each position. A layout is just a set of legends mapping each physical key id → the character it produces. Only legends change between QWERTY / Dvorak / Colemak.

File map & ownership

Area Files Notes
Contract / data model/types.ts, model/keys.ts, model/index.ts, index.css Foundation. Changing these ripples everywhere — do it deliberately.
Model model/layouts.ts, model/effort.ts, model/effort.sanity.ts Pure TS, no React. Must keep TermAnalysis shape and the heat array (one KeyHeat per used key).
Keyboard viz components/Keyboard.tsx, components/Keyboard.css SVG board from PHYSICAL_KEYS; colors by KeyHeat[] + HeatMode.
App / results App.tsx, App.css, components/{TermInput,LayoutPicker,ResultsTable,ComparisonChart,Methodology}.tsx Owns all app state; consumes the model barrel + <Keyboard/>.

Conventions

  • Styling: dark theme via CSS custom properties in index.css (--bg, --surface*, --border*, --text*, --accent, --good/--warn/--bad, the --heat-0..5 ramp, --finger-*, radii, shadows, fonts). Use tokens — do not hardcode colors.
  • No UI framework / no CSS framework. Charts and the keyboard are hand-rolled (SVG / divs). Keep dependencies minimal.
  • TypeScript is strict; verbatimModuleSyntax is on, so use import type for types and import type { JSX } from 'react' if you need JSX.Element.
  • The model is an estimate — keep it documented and tunable from MODEL.

How to extend

  • Add a keyboard layout: add a LayoutId in types.ts, add a legend map + entry in LAYOUTS (layouts.ts), and add it to the LayoutPicker options. Nothing else needs to change — resolveChar, the model, and the keyboard all read it generically.
  • Tune the model: edit the MODEL constants in effort.ts; re-run the sanity script to confirm orderings still hold.
  • Add a metric: extend TermMetrics in types.ts, populate it in analyzeTerm, then render it in the App detail pane.

Verify before you finish

npm run build                          # tsc -b + vite build must pass
npx tsx src/model/effort.sanity.ts     # model sanity assertions — must print PASS
npm run dev                            # then eyeball it in the browser

When changing the model or layouts, always re-run the sanity script. When changing the UI, run the dev server and visually confirm the ranking, the heatmap (used keys color across the ramp), the frequency/effort toggle, and layout switching.