Repository navigation
feat(editor): precise diagnostic→node mapping (v0.7) - #1
Merged
Merged
Conversation
Avalonia desktop exe; pulls in Avalonia.Desktop only at the executable seam so the library packages stay clear of Tmds.DBus.Protocol's transitive vulnerability. References Phoenixml.Platform + Editor.Xml as ProjectReferences for in-repo dev. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…views v0.1 layout: tree on the left (~360 dip), text on the right; classic XmlNotepad ergonomic. EditorState holds the active XmlDocument and fires ActiveDocumentChanged so views update in lockstep. No File menu yet (Task 4); window opens empty until Task 4 wires Open. Adds InternalsVisibleTo to Editor.Xml so Editor can call the internal SerializeToString helper. MainWindow is sealed so EditorMainWindow uses composition: wraps MainWindow and exposes it as .Window. App.axaml.cs assigns that to desktop.MainWindow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Procedural commands wired directly to menu click handlers. CommandRegistry-driven menu generation deferred to v0.2 once we have a command palette to consume the registry too. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Round of fixes after the first manual smoke test surfaced real issues. Each: - ConfigureAwait(false) in commands → 'Call from invalid thread' crash on Open. Stripped across all commands; CA2007 suppressed project-wide for the editor with a comment explaining the UI-app context where awaits MUST resume on the captured Avalonia SynchronizationContext. - Theme tokens never applied → DynamicResource lookups returning null. App.OnFrameworkInitializationCompleted now applies FluentDarkTheme.Default before any window opens. - Color vs IBrush mismatch → Background bindings can't auto-convert Color resources. Themes now store ImmutableSolidColorBrush instances under the same Phoenixml.Color.* keys. - Theme.Apply over-narrowly cast to StyledElement → Application isn't one in Avalonia 11. Now accepts either Application or StyledElement. - Status bar segment never updated when ActiveDocument changed → stayed '(no document)' forever. UpdateStatusBar helper invoked from OnActiveDocumentChanged. - Two title bars stacked on Linux (OS + custom) → SystemDecorations='Full' was keeping OS chrome. SystemDecorations='BorderOnly' + ExtendClientAreaChromeHints='NoChrome'. - Document tree label was full file:// URI causing horizontal overflow → XmlTreeNode.DocumentLabel extracts filename. - Right pane was single-line utf-16-declared XML on Open → underlying XmlSerializer produces unindented output. v0.1 sidesteps by displaying source file's raw text. Real v0.2 work item: proper XmlWriterSettings in PhoenixmlDb.Core's serializer. - Splitter invisible → bumped to 6 dip, bound Background to Phoenixml.Color.Border, added SizeWestEast cursor. - Tree expansion needed multiple precise clicks → IsExpanded property added to TreeNodeViewModel; root + first level auto-expanded; bound via TreeView.IsExpanded Style. Walkthrough doc at docs/editor-v0.1-walkthrough.md captures what works, fixes, and the v0.2 backlog.
Plan 7 wraps: - Directory.Packages.props: PhoenixmlDb.Core 1.0.24 → 1.0.30, picking up the Utf8StringWriter + indent-default-true fix from the feature/serializer-utf8-indent branch in phoenixmldb-core. - EditorMainWindow.OnActiveDocumentChanged: removed the source-file ReadAllText workaround. Text view now shows doc.SerializeToString() directly — the serializer output is finally readable indented UTF-8. - docs/editor-v0.1-walkthrough.md: marked the serializer fix resolved; promoted the 'edit commands wired to undo' item to v0.2 #1. - NuGet.config: temporary local feed pointing at the freshly-packed 1.0.30 nupkg while NuGet's CDN finishes propagating. Remove this file once 'curl https://api.nuget.org/v3-flatcontainer/phoenixmldb.core/index.json' shows 1.0.30. Validated: 166 tests pass, editor opens + Save round-trips clean indented UTF-8 XML.
XmlDocument.ReplaceNode: swaps a node in the resolver dictionary under the same NodeId. XdmNode types are immutable (init properties); edits happen by constructing a new node and swapping via this method. Parent Children lists (which reference by id) remain valid. RenameElementCommand: first concrete IEditorCommand. Reconstructs the XdmElement with a new LocalName, swaps via ReplaceNode. NodeId preserved; attributes, children, namespace, prefix, parent untouched. TryMergeWith collapses consecutive renames on the same element into one undo step, preserving the ORIGINAL pre-rename snapshot as revert target. Note: TryMergeWith parameter semantics — UndoStack calls stackTop.TryMergeWith(newCmd), so this=already-applied (has _previous), parameter=new cmd. Fixed vs. plan sketch. 7 tests: Apply, Revert, KeepsChildren, KeepsId, Merge, NoMerge, Serialize. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Provider's Changed event was declared in Plan 5 but never raised (CS0067 pragma suppressed the warning). Now it subscribes to _document.Changed in the constructor and re-fires on every edit, so TreeEditorView rebuilds in lockstep with commands. Editor's OnActiveDocumentChanged subscribes to the active doc's Changed event so the text view re-serializes after each edit; tracks _subscribedDocument to unsubscribe when the active document changes. Naive full re-render; incremental refresh is a Plan 10+ optimization. +1 test: ChangedEventFiresWhenRenameCommandExecutesViaUndoStack. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UndoCommand and RedoCommand call ActiveDocument.Undo.Undo/Redo. EditMenu adds Undo + Redo menu items with InputGesture display. KeyBindings on the window so shortcuts fire window-wide regardless of focus. RelayCommand helper for ICommand-bound keybindings. FileMenu.Build refactored to return a MenuItem (was Menu); the top-level Menu is now constructed in EditorMainWindow with both File and Edit items. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows the selected element's LocalName in an editable TextBox. Enter commits a RenameElementCommand through the document's UndoStack; Escape reverts the textbox; LostFocus auto-commits. Layout: tree on top (star), 1-dip separator, pane on bottom (140 dip). First concrete UI surface for editing. Plan 9 adds attribute + text + insert/delete UI on the same pattern.
…ound User feedback from the v0.2 rename demo: - Undo didn't refresh PropertiesPane textbox (the pane didn't subscribe to Document.Changed) - F2 inline rename preferred over side pane - Element labels in tree had redundant angle brackets - Attribute rename wasn't supported - Right pane was an editable TextBox that didn't flow back to XDM Changes: - TreeEditorView gains F2 inline rename. Selected row toggles to edit mode showing a TextBox (Enter commits, Escape cancels, LostFocus auto-commits). Raises NodeRenameRequested(ITreeNode, string newLabel). TreeNodeViewModel now implements INotifyPropertyChanged. - New RenameAttributeCommand mirrors RenameElementCommand for XdmAttribute. Same merge semantics. - Editor wires NodeRenameRequested -> routes to RenameElementCommand or RenameAttributeCommand based on node kind. - XmlTreeNode element labels drop the <> brackets -- show just 'catalog', not '<catalog>'. Attributes keep '@id' as the disambiguator. - TextEditorView set read-only in editor. Text->XDM sync (typing in the right pane and having it parse back) is Plan 10+ work; until then, the right pane displays the live serialized XML but doesn't accept typing. - PropertiesPane removed entirely. F2 gesture replaces it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User feedback: 'I'd like the UI flow to follow more of what we have going on with XmlNotepad. We have a treeview on the left, but on the right, we have the list of values contained in the elements/attributes. It really does make for a nicer visual flow of what's going on.' Replaces the TreeEditorView+TextEditorView+SplitView arrangement with a single new NodeListView control that renders nodes as a flat list, one row per visible (expanded) node, with two columns: name (with depth indent + chevron + label) on the left, value on the right. All rows share fixed column widths so values align vertically — the canonical XmlNotepad layout. ITreeNode gains a 'Value' property. XmlTreeNode populates it from the underlying XdmNode: attribute values, text-only-element text, comment text, PI value. Container elements (those with element children) return empty string in Value. Node children are resolved through XmlDocument.Resolve(NodeId) because XdmElement.Children is a list of NodeId references, not XdmNode instances. F2 inline rename works the same as before (now in the new control). NodeRenameRequested event has the same shape; editor's existing RenameElementCommand / RenameAttributeCommand wiring is unchanged. TextEditorView and TreeEditorView remain in the foundation for other consumers; the editor app just doesn't use them directly anymore. SplitView also unused by the editor for now (future tabs/secondary panes may pick it back up). 4 new Value tests added to XmlTreeNodeProviderTests (attribute value, text-only element content, container element empty, document node empty). All 183 tests pass (was 179). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User feedback after first launch of the XmlNotepad-style view: 1. CLI file arg wasn't actually wired (App.axaml.cs read desktop.Args but the path was never passed in) 2. Auto-expand only went 2 levels — every visible row was a container with an empty value column, so the demo looked uninformative 3. No visible separator between name and value columns 4. Text-only elements (e.g. <name>Laptop Pro</name>) duplicated their text: once as the parent's Value, once as a child text-node row labeled "Laptop Pro" with the same value This commit: - App.axaml.cs: pass desktop.Args[0] to editor.OpenFileOnLaunch when it points at an existing file. Silent skip otherwise (no scary startup dialog if the path's wrong). - NodeListView: auto-expand 3 levels via ExpandRecursively helper (was 2). For the catalog demo this surfaces product attributes + text-content elements on first open instead of bottoming out at containers. - NodeListView XAML: insert a 1-dip Border column themed with Phoenixml.Color.Border between the name and value columns so the right side reads as a defined column, not a black void. - XmlTreeNode.HasChildren: text-only elements (no attributes, no element children) now report false. Their text is in the Value column; no expand chevron, no child rows. - XmlTreeNodeProvider.GetElementChildren: when no element children exist, also skip text-node children entirely. Mixed content (text interleaved with elements) keeps the text rows so document order is preserved. Visual result (catalog fixture): leaf rows like <name>, <price>, <rating>, <tag> now show as a single line with name on the left and value on the right — no duplicate "Laptop Pro" rows underneath.
v0.2 walkthrough captures the XmlNotepad-style dual-pane state after Plan 8 + the UX iterations: how to run (including CLI file arg), what works, the 16 bugs caught and fixed during v0.2's real-use feedback loop, architectural validations, and the v0.3 priority list (value editing first).
Mirror of RenameAttributeCommand but mutates Value instead of LocalName. Same TryMergeWith semantics — consecutive sets on the same attribute collapse to one undo step that reverts to the pre-FIRST-edit value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the existing XdmText child's Value. Element itself is unchanged (its Children list still references the same NodeId). Throws on empty elements (would need to allocate a new NodeId and update the parent's Children list — Plan 10) and on mixed content / container elements (no single text child to update). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Click in a value cell → row enters value-editing mode (TextBox shown, TextBlock hidden). Enter commits, Escape cancels, click-away auto-commits. Raises NodeValueChangeRequested(ITreeNode, string) event for the host to route to a vocabulary command. Mirrors the F2 name-column edit pattern from Plan 8 with a different gesture (click vs F2) so the two are independently discoverable.
Routes by XDM node kind: - XdmAttribute → SetAttributeValueCommand - XdmElement with text-only content → SetTextContentCommand - Anything else (mixed / container / empty) → silently ignored (no command issued; the row rebuilds with its prior value)
Document.Changed can fire on a worker thread — XmlDocument.SaveAsAsync resumes off the UI thread after file I/O, then calls MarkClean which fires Changed. The provider re-raised it directly to NodeListView. Rebuild touches FindControl / ListBox.ItemsSource which require the UI thread → 'Call from invalid thread' crash on Save As. Marshal via Dispatcher.UIThread.Post when not already on the UI thread. Pre-existing v0.2 bug surfaced when smoke-testing v0.3 value editing.
Counter-based allocator on XmlDocument; lazy-initializes to max(parsed-id) + 1 on first call. AddNode adds a fresh node to _byId — for swap, ReplaceNode is still the right primitive. Foundation for Insert commands and lifting the empty-element restriction in SetTextContentCommand.
Adds the foundation for Compact-on-Save: each command declares the orphan NodeIds it needs to keep alive for undo/redo. Most commands return empty (their _previous snapshots reference live nodes); only deletion commands will populate this. UndoStack exposes its undo and redo stacks as read-only enumerations so the compactor can walk them.
Allocates a new NodeId, builds an empty XdmElement, inserts it into the parent's Children list at the specified index (or appends if null). Revert restores the parent without the inserted child; the orphaned node stays in _byId. Idempotent across redo: same _insertedId reused on re-Apply. ReferencedNodeIds reports the inserted id so Compact preserves it across Revert + redo state.
Allocates a new NodeId, builds an XdmAttribute with the given local name and value, appends to the parent's Attributes list. Same idempotency + ReferencedNodeIds semantics as InsertElementCommand.
Removes a node from its parent's Children/Attributes list. The node and its descendants stay in _byId so Revert is a single ReplaceNode of the parent (no rebuild). ReferencedNodeIds returns the entire deleted subtree so Compact-on-Save preserves it across saves while this command remains on the undo stack. Throws on document-root delete.
…mand Now allocates a new XdmText node for elements with no children. Container/mixed-content elements still reject (value column edits on those would be the wrong UX). Removes the ApplyOnEmptyElementThrows test from Plan 9 — behavior changed. TryMergeWith only merges within the ReplacedExistingText path; CreatedNewText edits get separate undo entries. Acceptable trade-off since first-time-text-in-empty-element is rare.
Mark-and-sweep eviction of orphan NodeIds in _byId at Save time. Mark phase: BFS from root + every command's ReferencedNodeIds across undo + redo stacks. Sweep: remove unmarked entries. Preserves undo across save without unbounded session growth. Trade-off: orphans pinned by undo/redo entries stay until those commands age out of the stacks.
Right-click on a row → Insert Child Element / Insert Attribute / Delete. Delete key on the selected row also fires the delete event. The host wires events to vocabulary commands; menu items are always shown (host decides whether to act based on node kind).
NodeInsertChildElementRequested → InsertElementCommand on the selected element, default name 'newElement'. Same for InsertAttribute. Delete routes to DeleteNodeCommand; document node and root-element-with-throw both silent-ignore. Also drops the 'el.Children.Count == 0 ⇒ reject' guard from the value-edit predicate now that Plan 10's SetTextContentCommand handles the empty case.
Material.Icons.Avalonia for cross-platform Avalonia glyphs. Toolbar sits between menu and content; buttons share the same Action<EditorMainWindow> handlers as the keyboard shortcuts so behavior stays consistent across menu, toolbar, and shortcut.
XmlNotepad-style address bar below the toolbar. Shows the active document's file path; Enter opens the typed path; Escape reverts. Reuses OpenFileOnLaunch's silent-skip semantics for invalid paths.
Wraps an XmlDocument with the title rendered on its tab header. Title appends '*' when the document has unsaved changes; updates fire PropertyChanged via Document.Changed so the tab strip re-renders without manual refresh.
Refactors single-document state into a tabs list with one active. ActiveDocument property is preserved as a forwarding accessor so existing callers (Save, Undo, address bar, status bar) compile unchanged. Setter routes to OpenDocument which deduplicates by document reference. CloseTab activates the right neighbor (or left if no right). New test project Phoenixml.Editor.Tests added to solution with 8 passing tests covering OpenDocument, CloseTab, ActiveDocument backwards-compat setter, and ActiveDocumentChanged event.
Avalonia TabStrip bound to EditorState.Tabs. Tab header shows DocumentTab.Title + a close button. Selection drives ActiveTab; close button calls CloseTab. Tab strip docks below the address bar so toolbar buttons stay visible regardless of tab count. TabStrip lives in Avalonia.Controls.Primitives (not Controls). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Explicit per-tab opening; the file-picker no longer silently replaces the previously-open document. Two opens of the same file path produce one tab (deduplication by XmlDocument reference). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JSON-backed editor settings with a single field for v0.5: RecentFiles. Store dedupes on record, caps at 10, persists in the background via SettingsService.SaveAsync. Load tolerates missing or corrupt files (starts fresh). Path is injectable for tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lists EditorSettings.RecentFiles, most recent first. Click an entry to open. Rebuilds on settings.Changed so newly opened files surface immediately. Empty state is rendered as a disabled '(no recent files)' item rather than an empty submenu. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EditorSettingsStore.RecordRecentFile is called from the file open path (CLI arg, file picker) and from Save As (so a 'save to new location' shows up in Recent Files immediately). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pivot in Plan 11: Avalonia's TabStrip + ItemsControl rendering of multiple tabs proved unreliable in our DockPanel layout (state was correct, count went 0→1→2 per diagnostics, but only the active tab rendered). User redirected: a left explorer column is more useful for folder-scoped XML editing anyway. What's added: - ExplorerNode: lazy-loading folder/file model. Filters to *.xml, *.xsd, *.xsl, *.xslt, *.sch and shows only folders containing XML content (recursive). - EditorExplorer: TreeView-based left column with 'Open Folder…' button + auto-root from active document's folder. Double-click a file → routes through OpenFileOnLaunch (silent skip on bad paths, same as address bar). What's kept: - EditorState tabs collection. Tests still pass; future tab strip can re-render against this without rework. What's removed: - EditorTabStrip.cs (deleted; no slop) - Diagnostic Console.WriteLine in EditorState.OpenDocument Layout: menu/toolbar/addressBar top; explorer + 22-dip collapse toggle docked left; nodeList fills the rest. Toggle hides the explorer's TreeView while keeping the toggle button visible.
Two UX fixes from explorer first-use feedback: 1. Double-clicking a folder collapsed the tree because Avalonia's default TreeViewItem behavior toggles expansion on row double-click, and our handler only set e.Handled for files. Now handled for both — folders skip the open path; the chevron remains the (correct) expand gesture. 2. Address bar previously only accepted file paths. Now also accepts directories — typing a folder + Enter retargets the explorer's root (via the new internal EditorExplorer.SetRoot exposed through EditorMainWindow.Explorer).
Previously every active-document change triggered SetRoot to the file's parent. Double-clicking a file from a deep folder in the explorer collapsed the user's chosen root down to that file's folder, losing the broader project view. Now: re-root only when the new file's folder is NOT under the current root. Same-tree opens stay put; cross-tree opens still follow.
Resolves XmlSchemaSet for a document. Per-document override in EditorSettings.PerDocumentSchema wins; otherwise reads xsi: schemaLocation / noNamespaceSchemaLocation hints from the document root. Relative paths resolve against the document's source directory. Returns null on missing/corrupt XSDs (validation is then skipped silently). Adds PerDocumentSchema and ExplorerWidth to EditorSettings — ExplorerWidth used by Plan 12's GridSplitter task. Implementation note: XdmAttribute.Namespace is a NamespaceId (not a string), so xsi: comparison uses NamespaceId.Xsi (the pre-seeded intern for http://www.w3.org/2001/XMLSchema-instance). TryLoad catches XmlException in addition to XmlSchemaException to handle corrupt (non-XML) XSD files gracefully. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Background validation: subscribes to active document's Document.Changed, debounces 500ms, runs XmlSchemaValidator.Validate SYNCHRONOUSLY on the UI thread (XmlDocument._byId is not thread-safe; the 500ms debounce keeps validation off the typing hot path). ConfigureAwait is omitted (matching existing editor command conventions) so the post-delay continuation resumes on Avalonia's SynchronizationContext — the same thread that called SetActiveDocument. Failures don't crash the editor — surface as a single error diagnostic on unexpected exceptions. Service is IDisposable; Dispose cancels the debounce and unsubscribes from the active document. Plan 13 may revisit if very large documents make synchronous validation user-visible — likely fix is a thread-safe snapshot of the relevant subset of _byId before handing off to Task.Run. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ProblemsView shows diagnostics from EditorValidationService. Bottom-docked, 120 dip default. SetActiveDocument plumbs the service to the active doc; DiagnosticsChanged updates the view. EditorMainWindow gains _validation (EditorValidationService backed by SchemaResolver). CA1001/CA2000 suppressed in csproj — both objects live for the application lifetime; disposal on process exit is correct for a desktop app. Collapse toggle deferred to a later polish round — keeps this change minimal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the docked-left explorer + collapse-button block with a Grid layout: Col 0 = explorer (initial width from settings), Col 1 = 4-dip GridSplitter, Col 2 = nodeList (*). Drag persists the new column width via EditorSettingsStore.UpdateExplorerWidth — sub-pixel changes are ignored to avoid spurious saves on every drag tick. Drops the explicit collapse button — the splitter itself is now the resize affordance (drag to ~zero to hide). Plan 13 may add a dedicated button back if user feedback wants it.
When the window closes with one or more dirty tabs, present a modal Save All / Discard All / Cancel dialog. Cancel keeps the window open. Save All loops through dirty docs and calls Document.SaveAsync; never-saved docs (Source == null) are skipped silently for v0.6 (Plan 13 should force Save As for them). Discard All calls MarkClean — small lie, fine because the editor exits immediately after. Status bar gains an 'N modified' segment so dirty docs that aren't currently visible (no tab strip in v0.6) are still surfaced to the user. XmlDocument gains a public new MarkClean() forwarding method so the editor assembly (a separate project) can call it without requiring InternalsVisibleTo. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
App.axaml only included FluentTheme; Material.Icons.Avalonia ships its own style dictionary that must be added to Application.Styles or MaterialIcon controls render as empty content. Toolbar buttons were silently invisible from Plan 10 onwards.
UpdateExplorerWidth and RecordRecentFile fire-and-forget SaveAsync calls were racing with explicit awaited SaveAsync calls (used by tests). Two writes to the same .tmp file would clobber each other before File.Move, and the catch(IOException) swallowed the loser silently — sometimes leaving the persisted file with stale content. Added a SemaphoreSlim to serialize saves. Production effect: rapid UpdateExplorerWidth calls during a splitter drag now queue rather than race; same for rapid RecordRecentFile calls.
Adds SourceLine and SourceColumn fields on XdmNode (default 0). No code changes yet — Tasks B2–B5 wire the fields into validation + diagnostic mapping.
Replaces the placeholder Location with a structured DiagnosticLocation record (NodeId + Line + Column). Foundation for precise click-to-navigate (Task B4) and inline overlay (B5). Updated DiagnosticTests and ProblemsViewTests fixtures to use the new DiagnosticLocation? type.
XmlSchemaValidator's event handler walks the in-memory tree to find the closest node whose SourceLine ≤ diagnostic line (tiebreak on column). Populates IDiagnostic.Location with the matched NodeId. Trade-off: validator runs against the serialized form, so its line/col is in the *serialized* output. Our SourceLine/Column is parse-time. Lookup is approximate but exact for simple violations on docs that serialize back close to themselves.
Diagnostics with a non-null Location route directly to the matched NodeId. Falls back to the regex heuristic from Plan 12 for diagnostics without a mapped node (e.g., document-level errors).
DiagnosticIndex (NodeId → severity) populated from the validation service's DiagnosticsChanged event. XmlTreeNode.IconKey returns 'diag.error' / 'diag.warning' when the node has a diagnostic; NodeListView renders ✕ / ⚠ glyphs in red/yellow before the row label. IDiagnosticIndex interface added to Phoenixml.Platform.Abstractions so the provider can reference it without a reverse dependency on Phoenixml.Editor. XmlTreeNodeProvider.TriggerRefresh() triggers a rebuild after each DiagnosticsChanged event. Plan 14 may swap to real icon assets — for v0.7, Unicode glyphs are visible and zero-asset.
SelectById only walks _flatRows (visible/expanded nodes); if the offending node was inside a collapsed parent the click silently no-op'd. Added NodeListView.EnsureVisibleAndSelect which DFS-walks the provider tree to locate the target id, marks each ancestor expanded along the path, rebuilds, then selects. EditorMainWindow's click-to-navigate (both precise and regex fallback paths) routes through the new method.
Previously XmlSchemaValidator serialized the document and re-parsed it through XmlReader to validate. The validator's reported line/col was in the SERIALIZED output, which doesn't match our parse-time SourceLine/SourceColumn — diagnostic→node mapping landed on the wrong nodes (e.g., glyph on the first <year>1978</year> instead of the invalid <year>not-a-year</year>). New approach: drive System.Xml.Schema.XmlSchemaValidator (the runtime class) push-style by walking XdmDocument and calling ValidateElement / ValidateAttribute / ValidateText / ValidateEndElement explicitly. The current XdmNode is in scope when each call returns; diagnostics attach the exact NodeId without any line/col arithmetic. Text-content diagnostics attach to the containing element (not the text node) so the inline glyph renders on the element row. Also adds XmlDocument.ResolveNamespace(NamespaceId) (internal) to convert interned namespace IDs to URI strings for the validator. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
Summary
Plan 13 Phase B — consumes PhoenixmlDb.Core 1.1.0's new
SourceLine/SourceColumnfields and rewires the schema-validation pipeline for exact diagnostic→node mapping.PhoenixmlDb.CorePackageReference to 1.1.0.IDiagnostic.LocationasDiagnosticLocation(NodeId, Line, Column)(wasobject?).XmlSchemaValidator's serialize-and-reparse implementation with a push-based walk overXdmDocument, drivingSystem.Xml.Schema.XmlSchemaValidatordirectly. The currentXdmNodeis in scope when each call returns; diagnostics attach the exactNodeIdwithout any line/col arithmetic.EditorMainWindow.problems.DiagnosticActivatedusesEnsureVisibleAndSelect(new) — DFS-walks the provider tree, expands every ancestor on the path, rebuilds, then selects. Works from a fully-collapsed tree.DiagnosticIndex(NodeId → severity) populated from the validation service.XmlTreeNode.IconKeyreturnsdiag.error/diag.warning.NodeListViewrow template renders ✕ red / ⚠ yellow glyphs in front of offending rows.Why
v0.6's click-to-nav was best-effort: regex-extract a quoted element name from the validator message, find the first matching
XdmElement. False positives on duplicates. v0.7's first attempt at precision (commit b9c05ca) usedFindNodeNearLineagainst parse-timeSourceLine/SourceColumn— but the validator round-trips through serialization, so its line/col was in the serialized output, not the source. Even on a freshly-loaded document, multi-line attribute formatting in the source vs. single-line in the serialized output meant lines didn't align. Glyphs landed on the wrong rows.Push-based validation eliminates the round-trip and the line/col mapping problem in one move.
Smoke test (2026-05-09)
Loaded
library.xml(two-book<library>, second book missing@isbnand with<year>not-a-year</year>):<book>row and the second<year>row. First book is clean.Test plan
TreatWarningsAsErrors.phoenixmldb-coremerged).Commits
fa96d68chore: bump PhoenixmlDb.Core to 1.1.0cee4af5feat(diagnostics): IDiagnostic.Location carries a NodeId referenceb9c05cafeat(validation): map diagnostics to XdmNode via parse-time positions (superseded by 122889e)a8f5197feat(editor): precise click-to-navigate using DiagnosticLocation5c30183feat(editor): inline error overlay on offending tree rows11733fdfix(editor): expand ancestors when navigating from a diagnostic122889efix(validation): push-based validator over XdmDocument walk🤖 Generated with Claude Code