LT-22710: Make phonological rule formula cells non-editable - #1082
Draft
johnml1135 wants to merge 6 commits into
Draft
LT-22710: Make phonological rule formula cells non-editable#1082johnml1135 wants to merge 6 commits into
johnml1135 wants to merge 6 commits into
Conversation
Comment hygiene (advisory)No comment-style violations in the lines this branch adds since |
This comment has been minimized.
This comment has been minimized.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1082 +/- ##
==========================================
+ Coverage 38.28% 38.45% +0.17%
==========================================
Files 1499 1499
Lines 350230 350236 +6
Branches 40261 40261
==========================================
+ Hits 134096 134695 +599
+ Misses 186915 186342 -573
+ Partials 29219 29199 -20
🚀 New features to boost your workflow:
|
johnml1135
marked this pull request as draft
August 19, 2026 18:55
Adds the failing tests that demonstrate Docs/bugs/phon-rule-direct-editing.md: - RuleFormulaVcBaseEditabilityTests: RuleFormulaVcBase.Display never sets ktptEditable=NotEditable before AddStringAltMember for the natural-class abbreviation (kfragNC) or the terminal-unit name (kfragTerminalUnit), across RegRuleFormulaVc, MetaRuleFormulaVc, and AffixRuleFormulaVc. - RuleFormulaDirectEditReproTests: drives a real IVwRootBox/PatternView and calls IVwSelection.ReplaceWithTsString directly (bypassing PatternView.OnKeyPress entirely, the same low-level path IME composition or drag-and-drop would use) and shows it actually renames the live PhPhoneme.Name. All 5 tests fail against current code, confirming the defect by direct reproduction rather than code reading alone.
Enforces "a rule cell is not free text" structurally instead of patching another input path: - RuleFormulaVcBase.Display now sets ktptEditable=NotEditable before every AddStringAltMember/AddProp call that binds a fragment to a real domain object's field (natural class abbreviation/name, terminal unit name) or to a computed feature/variable line. This is shared by RegRuleFormulaVc, MetaRuleFormulaVc, and AffixRuleFormulaVc, so all three rule kinds are covered by one change. - RuleFormulaControl now sets m_view.ReadOnlyView = true. This also unregisters the keyboard/IME controller hook for the view (see SimpleRootSite.ReadOnlyView), closing the IME-composition bypass, not just the WM_CHAR path PatternView.OnKeyPress already filtered. - PatternView.AllowDisplaySelection now always returns true (the established pattern also used by InterlinPrintView/InterlinTaggingChild), so the now-read-only rootsite still shows a visible selection for chooser insert/delete to act on. - PatternView.OnKeyPress is left in place as defence in depth. Adds a test confirming Delete still raises RemoveItemsRequested with the rootsite read-only, and disposes the test view to avoid a finalizer-thread COM cleanup race. All 6 reproduction/fix tests pass; MorphologyEditorDllTests (13), LexTextControlsTests (356), and ITextDllTests (208) show no regressions.
Documents which layer owns the invariant, an experiment isolating that the view-constructor ktptEditable marking (not ReadOnlyView) is what actually blocks IVwSelection.ReplaceWithTsString, what was deliberately left unremoved (PatternEditingHelper's CanCut/CanPaste, shared with ComplexConcControl) and unfixed (ComplexConcPatternVc, unaudited fake-tag/literal spans elsewhere in the VC family), and manual verification still needed in a running FLEx.
- Add RuleFormulaControlWiringTests: constructs the real Reg/Meta/Affix rule formula controls and asserts ReadOnlyView on the shipped rootsite, so the production RuleFormulaControl.cs wiring is covered rather than only the test-only view built in RuleFormulaDirectEditReproTests. - Extend EditabilityRecordingEnv to record AddProp calls (previously it threw NotImplementedException on the exact call kfragFeature/kfragPlusVariable/ kfragMinusVariable make) and add one test per fragment. - Add AllowDisplaySelection_IsTrue_WhenRootsiteIsReadOnly. - Add the natural-class equivalent of the phoneme ReplaceWithTsString repro: builds a real PhSimpleContextNC special-cased to display only its abbreviation, selects it through the real rootbox, and confirms a direct ReplaceWithTsString cannot rename PhNaturalClass.Abbreviation -- the same end-to-end standard already applied to the phoneme path. Each addition was confirmed by ablation: flipping RuleFormulaControl's ReadOnlyView back to false, deleting the three feature/variable ktptEditable lines, removing AllowDisplaySelection, and removing kfragNC's ktptEditable line each turn exactly the new, targeted test(s) red and nothing else; ablating kfragNC's marking also reproduces "CORRUPTED" in PhNaturalClass.Abbreviation end-to-end, mirroring the phoneme case. Also corrects the review doc's characterization of ComplexConcPatternVc: it has no real domain-field bindings to corrupt, so a direct edit throws NotImplementedException out of UpdateProp rather than renaming anything -- a crash risk, not a data-corruption risk. Notes the audit surface is closed (PatternVcBase has exactly two subclasses, PatternView exactly two consumers) and flags ConstChartVc's apparent cell-level guard as unverified/SUSPECTED-safe rather than confirmed clean.
The bug analysis and architecture self-review were working documents for this fix. Their conclusions are now carried by the code and its tests; the reasoning, decisions and paths not taken live in the pull request body so they inform review without merging into the tree.
State why a fragment is not editable rather than restating what the next line renders, drop a pointer to a test class and one to another view constructor's internals, and cut an over-long implementation comment to the sentence that matters. Re-wrap lines past the width limit.
johnml1135
force-pushed
the
fix/phon-rule-formula-readonly
branch
from
August 19, 2026 19:47
b2832bf to
4fe7c4c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Typing into a phonological rule formula no longer changes anything. Until now a rule cell rendered the referenced phoneme's or natural class's own
Name/Abbreviationas editable text, so an edit that reached the view renamed that object across the entire project — every other rule referencing it changed with it.The question on opening a diff with four production lines and three hundred test lines is why the tests outweigh the fix. Because the invariant was previously held by a keystroke filter —
PatternView.OnKeyPress, itself added for LT-21888 — that covers exactly one input path. This branch moves enforcement into the view constructor, where nothing can route around it, and pins each layer with a test that goes red when that layer alone is removed. The review worth your time is whether a read-only rootsite breaks an interaction users still need, not whether the markings are right.Where to look
DeleteKey_StillRaisesRemoveItemsRequested_WhenRootsiteIsReadOnlypins it.ReadOnlyView = trueis a broad switch (selection, focus, IME registration, cut/paste).AllowDisplaySelectionrestores visible selection; three per-control tests pin the shipped wiring.PatternViewis shared with the Complex Concordance pattern builder, which still runs editable. Nothing here alters its behaviour.RuleFormulaVcBase; regular, metathesis and affix-process each have their own test.OnKeyPressandPatternEditingHelper.CanCut/CanPasteare deliberately kept — still load-bearing for the other consumer.Deliberately not here
ReplaceWithTsString; the user-facing input path is not.ComplexConcPatternVchas a sibling defect — a crash, not corruption. Separate branch.ConstChartVcappears guarded at cell level. Assessed by reading only.One side effect worth knowing.
ReadOnlyView = truealso forcesAcceptsReturnto false (SimpleRootSite.cs:942).PatternViewships Tab-false / Return-true, so Tab is unaffected and Return changes only in where it is disposed of —OnKeyPressalready swallowed it, and it now propagates to the host instead of being trapped. The rule formula is a DataTree slice, not a dialog with anAcceptButton, so this should be inert. Reasoned, not verified.Verification.
MorphologyEditorDllTests21/21,LexTextControlsTests353 passed / 3 skipped,ITextDllTests207 passed / 1 skipped. Against the test-only commit the reproduction suite is 5 failed / 0 passed. Not stacked.Reading this a year from now — start here
The investigation notes and architecture review that produced this fix were deliberately deleted from the branch rather than merged; their content is synthesised into these sections. They were working documents — a one-time analysis whose conclusions are now carried by the code and its tests.
The short version: a rule formula cell is not free text, and never was meant to be. That invariant lived in exactly one place — a
WM_CHARfilter — and anything reaching the root box by another route (IME composition being the realistic case for vernacular keyboards) walked straight past it into a shared domain object's name field.The layer cake
Rule formula editing, model outward:
PhSegmentRule/PhRegularRule→PhSegRuleRHScells →PhPhonContexttreesRuleFormulaVcBase←PatternVcBase←FwBaseVcPatternViewRuleFormulaControl+RegRule/AffixRule/MetaRulesubclassesInsertionControl— a hotlink strip, not a menuRuleFormulaSliceA "cell" is
StrucDesc/StrucChange/LeftContext/RightContexton aPhSegRuleRHS.kfragNCandkfragTerminalUnitrender the referencedPhNaturalClass's andPhTerminalUnit's real multistring properties — which is why an edit renamed the object rather than corrupting the rule.PatternVcBasehas exactly two subclasses andPatternViewexactly two consumers, so the audit surface here is closed, not open-ended.Decisions, and why
Enforce in the view constructor, not with another input filter. The obvious cheap fix is to intercept whatever new path let the edit through. That is how the bug got here:
OnKeyPresswas already such a fix. Marking fragmentsktptNotEditabledenies the edit at the layer that owns the question, so a future input path inherits the guard instead of needing its own.Both layers were kept because both are load-bearing, established by ablation rather than assumed. Removing just the
kfragTerminalUnitmarking while leavingReadOnlyView = truereproduces the corruption. So the fragment markings — notReadOnlyView— are what actually blockReplaceWithTsString.ReadOnlyViewdoes separate work: it unregisters the keyboard/IME controller hook and disables cut/paste. Neither alone is sufficient.AllowDisplaySelectionfollows the existingInterlinPrintView/InterlinTaggingChildpattern. A read-only rootsite suppresses selection display by default, but a pattern editor needs the user to see what an insert or delete will act on.Paths not taken
Removing
OnKeyPressas now-redundant. It is not. An audit found bracket-glyph and fake-tag spans inMetaRuleFormulaVc,AffixRuleFormulaVcandPatternVcBasefor which it remains the only guard. Marking those is out of scope here.Removing
PatternEditingHelper.CanCut/CanPaste. They look dead once the rootsite is read-only, butPatternViewis shared withComplexConcControl, which as of this branch still runs withReadOnlyView = false. Deleting them here would silently enable paste there.Update: the Complex Concordance branch does set
ReadOnlyView = true, so once both land these two overrides become provably dead —SimpleRootSite'sReadOnlyViewsetter isEditingHelper.Editable = !value(SimpleRootSite.cs:907-944), and baseEditingHelper.CanCut/CanPaste(EditingHelper.cs:3672,3789) already return false whenm_fEditableis false, with no dependence on the override.CanCopyis unaffected either way. Removing them is deliberately left to a follow-up after both branches are in, rather than making this branch depend on one that is not yet open.Fixing
ComplexConcPatternVcin the same branch. Different feature area, and a different failure mode: it binds no real domain fields, so it cannot reproduce this rename. It throws instead.Surprising findings
The corruption is real and reproducible, not theoretical. The original report described it as "sometimes the user can modify the underlying forms." A test now drives
IVwSelection.ReplaceWithTsStringon a phoneme terminal unit and the realPhPhoneme.Namecomes back as"CORRUPTED".ReadOnlyViewdoes not gate the low-level edit API. Setting it alone leavesReplaceWithTsStringworking. This was measured, not reasoned about, and it is the reason the fix has two layers.Three of the five fragment markings were initially untested — they could be deleted with the suite green. Found by mutation testing after the first implementation and closed since; the current suite catches each one individually.
Evidence
Each claim in "Where to look" is pinned by a mutation, run rather than argued:
kfragTerminalUnitmarkingPhPhoneme.Name="CORRUPTED"kfragNCmarkingkfragFeature/kfragPlusVariable/kfragMinusVariableReadOnlyViewback tofalseAllowDisplaySelectionThe
EditabilityRecordingEnvspy was extended to recordAddProp(it previously threw), which is what the feature and variable fragments call.Deferred, and what would unblock it
ComplexConcPatternVc— sibling defect in the otherPatternVcBasesubclass, confirmed by probe: noUpdatePropoverride, so any bypassing edit throws an unhandledNotImplementedException. Own branch.ConstChartVc:297binds a sharedCmPossibilityin a chooser-driven cell — same defect shape — but appears pre-guarded at the cell level byMakeCellsMethod.cs:495. Assessed by code reading only; never exercised.PatternVcBasesubclasses have now had a bug in this family. Whether "pattern views are never free text" should be enforced inPatternVcBase/PatternViewthemselves, so a third subclass cannot reintroduce it, is worth deciding once the Complex Concordance fix lands.This change is