Skip to content

Replace fancier-settings with a TypeScript settings engine#283

Merged
bartekplus merged 13 commits intomasterfrom
feature/new-options-page
Mar 8, 2026
Merged

Replace fancier-settings with a TypeScript settings engine#283
bartekplus merged 13 commits intomasterfrom
feature/new-options-page

Conversation

@bartekplus
Copy link
Owner

@bartekplus bartekplus commented Mar 8, 2026

Summary

  • replace the vendored fancier-settings implementation with the in-repo TypeScript settings-engine, including the settings page wiring, manifest, i18n, and styles
  • add explicit storage backends in @core/application/storage and route settings consumers to that single storage owner
  • polish control behavior and rule-toggle-cards UX with follow-up fixes for tooltip positioning, roving focus, hash routing, programmatic action dispatch, and storage filtering
  • expand the focused test coverage around settings controls, storage behavior, and rule-toggle-cards interactions

Testing

  • bun test tests/store.test.ts tests/settingsEngine.controls.test.ts tests/ruleToggleCards.test.ts
  • bun run check

bartekplus and others added 10 commits March 8, 2026 05:51
Promotes the vendored fancier-settings JS library to first-party
TypeScript. Splits into a generic framework (src/ui/settings-engine/)
and app-specific code (src/ui/options/), then deletes the old
src/third_party/fancier-settings/ directory entirely.

Key changes:
- New generic framework: store, i18n, controls, layout, SettingsEngine
- Store and storage backends moved to src/core/application/storage/ to
  respect architecture boundaries
- UX improvements: tab fade-in, focus rings, grammar rule keyboard nav,
  modal focus trap, inline "Saved ✓" feedback, color hex label
- All 17 e2e smoke tests and 1187 unit tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add tests/settingsEngine.controls.test.ts with 37 unit tests covering
  all control types (CheckboxControl, SliderControl, TextControl,
  TextareaControl, SelectControl, RadioControl, ButtonControl,
  DescriptionControl, ValueOnlyControl, BaseControl.destroy)
- Fix SliderControl tooltip to use textContent instead of innerText so
  it works correctly in JSDOM and detached DOM environments

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

- SliderControl: compute left% from value/min/max to position tooltip
  above the thumb instead of staying fixed at the left edge
- RuleToggleCardsControl: set aria-checked on initial card creation and
  sync it in updateStateUI() so screen readers see the correct state
- ListBoxControl: collapse store_persist/storeWithFeedback/_storeInternal
  chain into direct this.store.set() calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SettingsEngine: extract instantiateControl() to eliminate ~80 lines
  of duplicated switch statement between createControl and modal nesting
- FieldControl: remove dead _clearHandle assignment and redundant null
  check in showSavedIndicator's setTimeout callback
- Controls (Checkbox, Slider, Select, Textarea, ListBoxMultiSelect,
  RuleToggleCards): remove redundant name-undefined guards before
  persistToStorage() — the base class already handles this
- TextControl: collapse duplicated if/else (input.type set in both
  branches) to a single assignment + ternary for className; remove
  redundant name check before params.store check
- SliderControl: extract formatValue() private method to eliminate
  duplicated displayModifier ternary; convert updateDisplay closure to
  private method for consistency
- RuleToggleCardsControl: replace nested ternary for rawOptions with
  if/else chain per project conventions
- LocalStorageBackend: convert verbose Promise constructor + try/catch
  to plain async methods; use startsWith() over substring comparison

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: tab: field in manifest used i18n.get("advanced_tab") (the
translated label) as both the tab key and the hash anchor, while popup
deep-links always use the stable key "advanced_tab". Comparing
location.hash against a translated string never matched.

Fix:
- types.ts: add TabConfig { id, label } + tabs[] to ManifestDefinition
- settingsManifest.ts: change all tab: i18n.get("xxx") to tab: "xxx"
  (62 entries); add explicit tabs[] array mapping id to translated label
- SettingsEngine: build tabLabelMap from manifest.tabs before iterating
  settings; key tabs by stable id; set tabA.href="#id" and call
  history.replaceState on click; defer hash activation until after all
  tabs are created; add hashchange listener for back/forward navigation
- index.ts: export TabConfig from public barrel
- ruleToggleCards.test.ts: update assertion to stable key "grammar_tab"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bartekplus bartekplus self-assigned this Mar 8, 2026
@bartekplus bartekplus changed the title Clarify settings storage ownership Replace fancier-settings with a TypeScript settings engine Mar 8, 2026
@bartekplus bartekplus added the enhancement New feature or request label Mar 8, 2026
@bartekplus bartekplus merged commit 2a5e28a into master Mar 8, 2026
8 checks passed
@bartekplus bartekplus deleted the feature/new-options-page branch March 8, 2026 06:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant