Skip to content

Multiple Keyboard Layouts#105

Merged
Hugo0 merged 4 commits intoHugo0:mainfrom
professor-eggs:main
Jan 25, 2026
Merged

Multiple Keyboard Layouts#105
Hugo0 merged 4 commits intoHugo0:mainfrom
professor-eggs:main

Conversation

@professor-eggs
Copy link
Contributor

@professor-eggs professor-eggs commented Nov 15, 2025

This pull request introduces support for multiple selectable keyboard layouts per language in game. The main changes include refactoring the keyboard loading logic, updating the language model to handle multiple layouts, adding new keyboard layout data for English and Hebrew, and providing a UI for users to select their preferred keyboard layout, which is persisted via cookies.

Keyboard Layout System Enhancements:

  • Refactored load_keyboard in webapp/app.py to support both legacy array and new object-based keyboard config formats, normalizing layouts and handling missing or malformed files gracefully.
  • Updated the Language class in webapp/app.py to support multiple keyboard layouts, including logic for selecting the active layout, generating an alphabetical fallback, and storing layout metadata. [1] [2]
  • Changed the language route to accept and persist the selected keyboard layout via query parameter and cookies, ensuring user preference is remembered.

Keyboard Data Updates:

  • Replaced the English keyboard file (webapp/data/languages/en/en_keyboard.json) with a new format supporting "qwerty", "dvorak", and "alphabetical" layouts. [1] [2]
  • Updated the Hebrew keyboard file (webapp/data/languages/he/he_keyboard.json) to provide "alphabetical" and "Hebrew QWERTY" layouts in the new format.

User Interface Improvements:

  • Added a keyboard layout selector dropdown to game.html, allowing users to switch layouts when more than one is available.

Closes #106

Summary by CodeRabbit

  • New Features
    • Multiple keyboard layouts per language (QWERTY, Dvorak, Alphabetical) added.
    • Keyboard layout selector added to the Settings modal (appears when multiple layouts exist).
    • User layout preference is saved and remembered across visits.
    • Hebrew now includes configured keyboard layouts and a usable default alphabetical layout when none provided.
    • Automatic alphabetical layout generated as a fallback when layouts are missing.

* Add keyboard layout selection and update keyboard configurations for multiple languages

* Refactor keyboard layout selector form for improved styling
@coderabbitai
Copy link

coderabbitai bot commented Nov 15, 2025

Walkthrough

Adds multi-layout keyboard support: normalizes keyboard data, exposes multiple named layouts, selects an active layout per request (query or cookie), auto-generates an alphabetical layout when none provided, and surfaces a layout selector in the game settings.

Changes

Cohort / File(s) Summary
Backend: keyboard handling
webapp/app.py
Rewrote load_keyboard() to return normalized {"default":..., "layouts":{...}}; added keyboard_layout parameter to Language.__init__; introduced _build_keyboard_layouts(), _select_keyboard_layout(), _generate_alphabetical_keyboard(); language route reads query/cookie, instantiates Language with chosen layout, and sets a persistent cookie.
Data: language keyboard definitions
webapp/data/languages/en/en_keyboard.json, webapp/data/languages/he/he_keyboard.json
Replaced flat arrays with object schema containing default and layouts map; English now includes qwerty, dvorak, and alphabetical; Hebrew adds alphabetical and hebrew_qwerty, each layout has label and rows.
Frontend: selector UI
webapp/templates/game.html
Added conditional keyboard layout selector (form + select) in SETTINGS/modal when `language.keyboard_layouts

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser
    participant App as Flask App
    participant Lang as Language
    participant UI as game.html

    User->>Browser: Request /language?code=en&layout=dvorak
    Browser->>App: GET /language?code=en&layout=dvorak

    rect rgb(230,240,255)
    Note over App: Load & normalize keyboard data
    App->>App: load_keyboard(config) => {default, layouts}
    App->>Lang: Language(code, word_list, keyboard_layout=dvorak)
    Lang->>Lang: _build_keyboard_layouts(config)
    Lang->>Lang: _select_keyboard_layout(dvorak)
    Lang->>Lang: set self.keyboard (rows, label, name)
    end

    App->>Browser: Set-Cookie: keyboard_layout=dvorak
    App->>UI: render with Language instance

    UI->>Browser: render selector if multiple layouts
    Browser->>App: POST/GET with new layout (user change)
    App->>Browser: Update cookie + render updated UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • webapp/app.py — keyboard normalization, fallback logic, cookie handling, and new public Language.__init__ signature
    • JSON keyboard files — verify layout rows, labels, and default keys
    • Template changes — ensure select names/values match backend expectations

Poem

🐰
I hopped through keys both old and new,
QWERTY, Dvorak, an ABC view.
A cookie holds the layout you choose,
Rows align where fingers muse,
Hoppity, now each language sings its tune.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Multiple Keyboard Layouts' directly and clearly summarizes the main change: adding support for multiple selectable keyboard layouts across languages.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #106: multiple keyboard layouts per language, selectable alternatives (QWERTY and alphabetical), persistent user preferences via cookies, and flexible format with safe loading.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #106: keyboard loading refactoring, Language class updates, layout selection logic, data file updates with new layouts, and UI selector addition.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
webapp/app.py (1)

123-126: Narrow the exception scope in load_keyboard.
Catching bare Exception hides unrelated errors (e.g., OS errors) when loading keyboard data, making failures harder to diagnose. Limit this to json.JSONDecodeError and expected I/O errors instead of swallowing everything.

-    except Exception:
+    except json.JSONDecodeError:
+        return {"default": None, "layouts": {}}

If you also want to cover filesystem issues, catch OSError explicitly in a separate clause.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4840fa3 and 5e608c8.

📒 Files selected for processing (4)
  • webapp/app.py (4 hunks)
  • webapp/data/languages/en/en_keyboard.json (1 hunks)
  • webapp/data/languages/he/he_keyboard.json (1 hunks)
  • webapp/templates/game.html (2 hunks)
🧰 Additional context used
🪛 Ruff (0.14.4)
webapp/app.py

125-125: Do not catch blind exception: Exception

(BLE001)

@professor-eggs
Copy link
Contributor Author

@coderabbitai generate docstrings

@coderabbitai
Copy link

coderabbitai bot commented Nov 16, 2025

✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai
Copy link

coderabbitai bot commented Nov 16, 2025

Warning

Docstrings generation - IN PROGRESS

Generating docstrings for this pull request

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
webapp/app.py (3)

118-167: Keyboard loading: blind except Exception hides real errors and hinders debugging

The overall normalization logic looks solid and safely handles legacy list configs and new multi-layout dicts, but the error handling is too broad:

  • except Exception: (Line 131) swallows any runtime problem (JSON syntax errors, permission issues, programming mistakes) and silently falls back to an empty layout, which then auto-generates a keyboard. This makes it hard to notice broken keyboard JSON and can mask genuine bugs. Static analysis (BLE001) is flagging exactly this.

I recommend:

  • Narrow the second handler to the expected failure modes (e.g. JSON decoding and I/O):
-    except FileNotFoundError:
-        return {"default": None, "layouts": {}}
-    except Exception:
-        return {"default": None, "layouts": {}}
+    except FileNotFoundError:
+        return {"default": None, "layouts": {}}
+    except (OSError, json.JSONDecodeError) as err:
+        # Optional: log so malformed keyboard configs are visible in logs.
+        app.logger.warning("Failed to load keyboard %s: %s", keyboard_path, err)
+        return {"default": None, "layouts": {}}
  • If you want to be stricter during development, you could re‑raise unexpected exceptions instead of treating them as “no keyboard”.

245-297: Language keyboard layout initialization is robust; consider minimal defensive check

The layout initialization in Language.__init__ and _build_keyboard_layouts is cohesive and resilient:

  • It always produces at least one layout by auto‑generating an alphabetical layout when keyboard_config["layouts"] is empty.
  • It cleanly separates concerns: load_keyboard normalizes raw JSON; _build_keyboard_layouts enforces a canonical {"label", "rows"} shape; _select_keyboard_layout handles request/default precedence.

Given that _build_keyboard_layouts guarantees at least one layout today, self.keyboard_layouts[self.keyboard_layout_name] is safe. If you ever relax that guarantee, a small guard would prevent a future KeyError:

-        self.keyboard_layouts = self._build_keyboard_layouts(keyboard_config)
-        self.keyboard_layout_name = self._select_keyboard_layout(
-            keyboard_layout, keyboard_config.get("default")
-        )
-        layout_meta = self.keyboard_layouts[self.keyboard_layout_name]
+        self.keyboard_layouts = self._build_keyboard_layouts(keyboard_config)
+        if not self.keyboard_layouts:
+            # Should not happen with current _build_keyboard_layouts contract,
+            # but avoids a KeyError if that ever changes.
+            self.keyboard_layouts = {
+                "alphabetical": {
+                    "label": "Alphabetical",
+                    "rows": self._generate_alphabetical_keyboard(),
+                }
+            }
+        self.keyboard_layout_name = self._select_keyboard_layout(
+            keyboard_layout, keyboard_config.get("default")
+        )
+        layout_meta = self.keyboard_layouts[self.keyboard_layout_name]

If you’re confident in the current contract, this can be considered optional; the existing logic cleanly satisfies the multi-layout requirement and fallback behavior.


390-402: Layout selection & cookie persistence: behavior aligns with requirements; consider minor cookie hardening

The updated language route correctly:

  • Reads the requested layout from ?layout= with precedence over the cookie (keyboard_layout_<lang>), then delegates to Language which safely falls back when the value is unknown.
  • Sets the cookie only when the effective language.keyboard_layout_name changes, avoiding unnecessary rewrites.
  • Uses a 1‑year max_age and samesite="Lax", which is reasonable for a per-user preference.

Two small optional refinements:

  • Add secure=True to set_cookie to match the HTTPS redirect behavior in before_request, preventing this preference from ever being sent over plain HTTP in case of misconfiguration.
  • If you ever care about explicit “reset to default” semantics, you might prefer checking for "layout" in request.args instead of falsy/truthy, so an empty layout value doesn’t unintentionally fall back to the cookie.

Example hardened cookie call:

-        response.set_cookie(
+        response.set_cookie(
             cookie_key,
             selected_layout,
             max_age=60 * 60 * 24 * 365,
-            samesite="Lax",
+            samesite="Lax",
+            secure=True,
         )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e608c8 and 029ca9d.

📒 Files selected for processing (2)
  • webapp/app.py (4 hunks)
  • webapp/templates/game.html (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • webapp/templates/game.html
🧰 Additional context used
🪛 Ruff (0.14.4)
webapp/app.py

131-131: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (1)
webapp/app.py (1)

299-334: Alphabetical keyboard generation and row balancing look correct

The selection and generation helpers behave as intended:

  • _select_keyboard_layout correctly prioritizes user-requested layout, then config default, then the first available layout, ensuring an always-valid choice given the _build_keyboard_layouts guarantee.
  • _generate_alphabetical_keyboard:
    • Uses self.characters (already filtered to only used characters) to build 10-column rows.
    • Safely adds the “⇨” and “⌫” keys and redistributes characters when the last row is overly long (11 or 12 entries), without risking index errors thanks to the explicit length checks.

This should produce stable, readable fallback layouts for languages without explicit keyboard configs, and the logic appears safe for varying alphabet sizes.

@coderabbitai
Copy link

coderabbitai bot commented Nov 16, 2025

✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@professor-eggs
Copy link
Contributor Author

Hello @Hugo0 ! I imagine you're super busy! What do you think about this feature/implementation? Any chance this could be merged?

Did this for a non technical friend of mine and currently hosting this branch for him on a legacy free render instance that turns itself off pretty often.

Lemme know if this isn't the direction you see for this app and I'll figure something else out if it isn't.

Thank you!

@Hugo0 Hugo0 merged commit 8f59b7f into Hugo0:main Jan 25, 2026
@Hugo0
Copy link
Owner

Hugo0 commented Jan 25, 2026

Thanks @professor-eggs! Super sorry for the late response here. Insane that you're hosting a fork; you're a good friend.

I've now incorporated your changes in commits 8f59b7f and 8b1b3f5 in PR #109, along with a bunch of other stuff. Let me know if you like the direction of the app!

Hugo0 added a commit that referenced this pull request Feb 22, 2026
Integrate professor-eggs' multiple keyboard layouts feature:
- New keyboard JSON format with named layouts (qwerty, dvorak, alphabetical)
- Backward compatible with legacy array format
- User layout preference persisted via cookies
- Layout selector in Settings modal (only shows when multiple layouts exist)
- Auto-generated alphabetical fallback when no keyboard defined

Changes:
- load_keyboard() now returns normalized {default, layouts} structure
- Language class handles layout selection with fallback chain
- English keyboard: QWERTY, Dvorak, Alphabetical layouts
- Hebrew keyboard: Alphabetical, Hebrew QWERTY layouts
- Added dark mode styling to layout selector

Based on PR #105 by @professor-eggs, adapted for current codebase.

Co-Authored-By: professor-eggs <professor-eggs@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for multiple keyboard layouts

2 participants