Skip to content

feat: add custom text via URL query parameter#574

Open
Dronakurl wants to merge 15 commits into
aradzie:masterfrom
Dronakurl:feature/custom-text-url-query
Open

feat: add custom text via URL query parameter#574
Dronakurl wants to merge 15 commits into
aradzie:masterfrom
Dronakurl:feature/custom-text-url-query

Conversation

@Dronakurl

Copy link
Copy Markdown

Summary

Add ability to share custom typing practice text via URL query parameter. Users can now add ?text=YOUR_TEXT to any keybr.com URL to practice with specific text without manual pasting.

What

  • Server-side extraction of text query parameter (10,000 char limit)
  • Auto-switch to CUSTOM lesson type when text provided
  • Session-only behavior (text not persisted to settings)
  • URL cleanup after processing (removes ?text= from URL)
  • Comprehensive test coverage including edge cases
  • i18n support for user-facing messages

Usage Examples

https://keybr.com/?text=Hello%20World
https://keybr.com/?text=The%20quick%20brown%20fox

Changes

Core Files

  • packages/server/lib/app/page/controller.tsx - Server-side URL param extraction
  • packages/page-practice/lib/practice/useUrlCustomText.ts - Hook with URL cleanup
  • packages/keybr-pages-shared/lib/UrlTextContext.tsx - Session-only context
  • packages/keybr-lesson/lib/customtext.ts - Accept initial text from URL
  • packages/keybr-lesson-loader/lib/LessonLoader.tsx - Use URL text from context
  • packages/page-practice/lib/settings/lesson/CustomTextLessonSettings.tsx - Display URL text
  • packages/page-practice/lib/PracticePage.tsx - Context provider placement
  • packages/page-practice/lib/practice/useUrlCustomText.test.tsx - Comprehensive tests

Tests & Translations

  • 9 test files with PageData type updates
  • 43 translation files with new strings

Testing

All tests pass: npm test -- --scope=@keybr/page-practice (21 tests)

Dronakurl and others added 9 commits February 22, 2026 22:37
Allow users to practice custom text submitted via the `text` URL query parameter.

Features:
- Extract `text` parameter from URL on server-side
- Pass custom text to client via PageData
- Apply text to settings using React hook on mount
- Show visual indicator when text is loaded from URL
- Enforce 10,000 character limit

Usage: http://localhost:3000/?text=Hello%20World

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add useRef to track if custom text has been applied, preventing
the useEffect from running repeatedly and causing React error aradzie#185.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When custom text is provided via URL parameter, automatically switch
the lesson type to CUSTOM so the text is immediately available for
practice without manual mode switching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add customText: null to all PageDataContext.Provider values in test files
to satisfy the updated PageData type which now includes the customText field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit fixes critical issues with the custom text via URL feature:

## Session-Only URL Text (Fixes Settings Persistence)
- URL-loaded text is no longer persisted to user settings
- Created UrlTextContext in @keybr/pages-shared to pass URL text
- Modified CustomTextLesson to accept optional initialText parameter
- useUrlCustomText now returns text instead of persisting it
- Only switches lesson type, does not modify customText.content setting

## Server-Side Validation (Security Fix)
- Added validation and truncation on server before sending to client
- Prevents abuse with extremely long URLs
- MAX_URL_TEXT_LENGTH = 10_000 characters enforced server-side

## Unit Tests
- Added comprehensive unit tests for useUrlCustomText hook
- Tests cover: null input, trimming, truncation, whitespace handling

## i18n Translations
- Added translation keys for URL text message in:
  - English (en.json)
  - German (de.json)
  - French (fr.json)
  - Spanish (es.json)

This ensures users don't get "stuck" with text from URLs they visited once,
and provides proper security validation and test coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes issues when custom text is loaded from URL:
- URL text now displays correctly in the textarea (was showing placeholder/saved text)
- URL text stats now display correctly (word count, etc.)
- Users can now edit the text, which saves it to settings
- Added useUrlText hook to CustomTextLessonSettings

This resolves the confusing behavior where URL text was active
but the UI showed the old saved text or placeholder.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The context provider was only in PracticeScreen, but SettingsScreen
(which contains CustomTextLessonSettings) is a separate view.
Moving the provider to PracticePage ensures both views have access
to the URL text context.

This fixes the issues where:
- URL text was not showing in settings UI
- Stats were not displaying correctly for URL text
- "sHagCCk1" placeholder was showing instead of URL text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add comprehensive test coverage for special characters, unicode, and edge cases:
- Special characters from URL encoding (@#$%^&*())
- Unicode and emoji characters
- Mixed quote types (double, single, backticks)
- HTML-like characters (angle brackets, entities)
- Newlines and tabs in text
- Long text near browser URL limit (~925 chars)
- Zero-width and invisible unicode characters
- URL-encoded newlines and special characters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents the text parameter from persisting in the URL after it has
been applied to the practice session, allowing users to refresh or
share the page without the text being re-applied.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@semanticdiff-com

semanticdiff-com Bot commented Feb 23, 2026

Copy link
Copy Markdown

Review changes with  SemanticDiff

Changed Files
File Status
  packages/keybr-lesson-loader/lib/LessonLoader.tsx  77% smaller
  packages/page-practice/lib/practice/PracticeScreen.test.tsx  68% smaller
  packages/keybr-intl/translations/de.json  50% smaller
  packages/keybr-intl/translations/es.json  50% smaller
  packages/keybr-intl/translations/fr.json  50% smaller
  packages/keybr-lesson/lib/customtext.ts  44% smaller
  packages/page-practice/lib/PracticePage.tsx  37% smaller
  packages/keybr-color/lib/convert-xyz.test.ts  24% smaller
  build.sh Unsupported file format
  packages/keybr-intl/translations/af.json  0% smaller
  packages/keybr-intl/translations/ar.json  0% smaller
  packages/keybr-intl/translations/bg.json  0% smaller
  packages/keybr-intl/translations/bn.json  0% smaller
  packages/keybr-intl/translations/ca.json  0% smaller
  packages/keybr-intl/translations/cs.json  0% smaller
  packages/keybr-intl/translations/da.json  0% smaller
  packages/keybr-intl/translations/el.json  0% smaller
  packages/keybr-intl/translations/en.json  0% smaller
  packages/keybr-intl/translations/eo.json  0% smaller
  packages/keybr-intl/translations/et.json  0% smaller
  packages/keybr-intl/translations/fa.json  0% smaller
  packages/keybr-intl/translations/fo.json  0% smaller
  packages/keybr-intl/translations/ga.json  0% smaller
  packages/keybr-intl/translations/he.json  0% smaller
  packages/keybr-intl/translations/hr.json  0% smaller
  packages/keybr-intl/translations/hu.json  0% smaller
  packages/keybr-intl/translations/id.json  0% smaller
  packages/keybr-intl/translations/is.json  0% smaller
  packages/keybr-intl/translations/it.json  0% smaller
  packages/keybr-intl/translations/ja.json  0% smaller
  packages/keybr-intl/translations/ko.json  0% smaller
  packages/keybr-intl/translations/lt.json  0% smaller
  packages/keybr-intl/translations/mn.json  0% smaller
  packages/keybr-intl/translations/nb.json  0% smaller
  packages/keybr-intl/translations/ne.json  0% smaller
  packages/keybr-intl/translations/nl.json  0% smaller
  packages/keybr-intl/translations/pl.json  0% smaller
  packages/keybr-intl/translations/pt-br.json  0% smaller
  packages/keybr-intl/translations/pt-pt.json  0% smaller
  packages/keybr-intl/translations/ro.json  0% smaller
  packages/keybr-intl/translations/ru.json  0% smaller
  packages/keybr-intl/translations/sk.json  0% smaller
  packages/keybr-intl/translations/sl.json  0% smaller
  packages/keybr-intl/translations/sq.json  0% smaller
  packages/keybr-intl/translations/sv.json  0% smaller
  packages/keybr-intl/translations/th.json  0% smaller
  packages/keybr-intl/translations/tr.json  0% smaller
  packages/keybr-intl/translations/uk.json  0% smaller
  packages/keybr-intl/translations/vi.json  0% smaller
  packages/keybr-intl/translations/zh-hans.json  0% smaller
  packages/keybr-intl/translations/zh-hant.json  0% smaller
  packages/keybr-intl/translations/zh-tw.json  0% smaller
  packages/keybr-pages-browser/lib/NavMenu.test.tsx  0% smaller
  packages/keybr-pages-browser/lib/SubMenu.test.tsx  0% smaller
  packages/keybr-pages-browser/lib/Template.test.tsx  0% smaller
  packages/keybr-pages-server/lib/Shell.test.tsx  0% smaller
  packages/keybr-pages-shared/lib/UrlTextContext.tsx  0% smaller
  packages/keybr-pages-shared/lib/index.ts  0% smaller
  packages/keybr-pages-shared/lib/types.ts  0% smaller
  packages/page-account/lib/AccountPage.test.tsx  0% smaller
  packages/page-practice/lib/practice/useUrlCustomText.test.tsx  0% smaller
  packages/page-practice/lib/practice/useUrlCustomText.ts  0% smaller
  packages/page-practice/lib/settings/lesson/CustomTextLessonSettings.tsx  0% smaller
  packages/server/lib/app/page/controller.tsx  0% smaller

Dronakurl and others added 3 commits February 23, 2026 22:22
Translate "lessonType.customText.fromUrl" and its description
to all 43 supported languages using the new generic translation
script.

Languages translated: af, ar, bn, ca, cs, da, el, en, eo, et,
fa, fo, ga, he, hr, hu, id, is, it, ja, ko, lt, mn, nb, ne,
nl, pl, pt-br, pt-pt, ro, ru, sk, sl, sq, sv, th, tr, uk, vi,
zh-hans, zh-hant, zh-tw, bg

Already translated: de, es, fr

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Auto-disable lowercase setting when URL text contains uppercase
letters, so the original text casing is preserved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.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.

1 participant