Skip to content

fix(widget): indent unit shadowing + Tab inserting literal tab - #26

Merged
LikeSundayLikeRain merged 1 commit into
mainfrom
fix/indent-unit-shadowing
Jun 10, 2026
Merged

fix(widget): indent unit shadowing + Tab inserting literal tab#26
LikeSundayLikeRain merged 1 commit into
mainfrom
fix/indent-unit-shadowing

Conversation

@LikeSundayLikeRain

Copy link
Copy Markdown
Owner

Summary

User reported "indentation is mixed of spaces and tabs" in the widget editor. Two compounding bugs:

  1. A top-level indentUnit.of(' ') in the editable extensions array silently shadowed the per-language indentUnit.of(...) supplied inside languageCompartment.of(buildLanguageExtensions(slug, override)). CM6 facets resolve in extension order; the bare facet won, so auto-indent on Enter always emitted 4 spaces regardless of language (JS/TS 'auto' default of 2 was unreachable) or the user's indentSizeOverride setting.
  2. The empty-selection Tab branch called @codemirror/commands insertTab, which dispatches a literal \t regardless of the indentUnit facet. Combined with the spaces-emitting auto-indent, that produced the mixed-tabs-and-spaces symptom.

The setting was also non-reactive — changes only applied to widgets mounted afterward.

Fixes

  • src/widget/WidgetController.ts: Delete the shadowing indentUnit.of(' '). Replace insertTab(target) with replaceSelection(state.facet(indentUnit)) so Tab and Enter consult the same facet. Add reconfigureIndent(override) method (pure-effects languageCompartment.reconfigure(buildLanguageExtensions(currentSlug, override)) — no changes, no SelfWriteSuppression involvement).
  • src/widget/widgetRegistry.ts: Add applyIndentReconfigure(override) mirroring the existing applyDelay / reconfigureVim precedent — walk every mounted controller across every file.
  • src/settings/SettingsTab.ts: Wire the indent-size dropdown onChange to call applyIndentReconfigure(val) after persist so the setting applies live without note reload. Reconfigure only swaps the live facet for FUTURE typing; existing buffer content is never rewritten.
  • .planning/research/PITFALLS.md: Document as Pitfall 37 with warning signs (canary: JS/TS users on auto see 4-space indents).
  • CLAUDE.md: Architecture sentence on the indent-unit single-source-of-truth and reactivity flow.

Test plan

  • 13 new tests in tests/widget/tabHandlerIndent.test.ts using real CM6 — verify Tab inserts spaces from the indentUnit facet (not literal \t) for python3/javascript/typescript across auto/2/4/8 overrides; Go always inserts \t per D-06; multi-line Tab via indentMore consistent.
  • 9 new tests in tests/widget/indentReactivity.test.ts — verify reconfigureIndent dispatches an effects-only payload built from the live currentSlug + new override; verify WidgetRegistry.applyIndentReconfigure fans out across every controller; tightened assertions on Compartment.reconfigure call ledger.
  • npm run build
  • npm run lint ✓ (0 errors)
  • npx vitest run ✓ (2965 passed, 0 failed)
  • Adversarial verify across 4 lenses (vim, language-switch, reactivity, IME/completeness) — 4/4 stood, 0 refuted.
  • Deployed to dogfood vault (~/Documents/interview-prep/.obsidian/plugins/leetcode/).

The child editor had two competing indentUnit sources: buildLanguageExtensions
inside the languageCompartment (per-language + setting-aware) and a top-level
indentUnit.of('    ') that silently shadowed it. Auto-indent on Enter always
emitted 4 spaces regardless of language or the user's indentSizeOverride
setting; the JS/TS 'auto' default (2 spaces) was unreachable.

The empty-selection Tab branch additionally called @codemirror/commands'
insertTab, which dispatches a literal "\t" regardless of the indentUnit
facet — combined with the spaces-emitting auto-indent, this produced
"mixed tabs and spaces in the same buffer" (the user-reported symptom).

Fixes:
- Remove the shadowing top-level `indentUnit.of('    ')`; trust the
  languageCompartment payload as the single source of truth.
- Replace `insertTab(target)` with `replaceSelection(state.facet(indentUnit))`
  so Tab and Enter consult the same facet and cannot diverge.
- Add `WidgetRegistry.applyIndentReconfigure(override)` mirroring `applyDelay`
  / `reconfigureVim` so the dropdown setting applies live to every mounted
  widget — pure-effects Compartment.reconfigure, no body change, no
  SelfWriteSuppression interaction.

Tests: 22 new tests across two files (real CM6 keymap behavior +
mock-based reactivity assertions). Documents the failure mode as
PITFALLS.md Pitfall 37 and adds an architecture line to CLAUDE.md.
@LikeSundayLikeRain
LikeSundayLikeRain merged commit a4ce8e9 into main Jun 10, 2026
1 check passed
@LikeSundayLikeRain
LikeSundayLikeRain deleted the fix/indent-unit-shadowing branch June 10, 2026 21:59
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