feat(vscode): replace inline previews with block note viewer - #181
feat(vscode): replace inline previews with block note viewer#181mors119 wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughThe VS Code extension replaces legacy inline note previews with expandable block viewers above code anchors. It adds grouped note modeling, viewer settings, expansion state, CodeLens controls, decoration rendering, lifecycle wiring, documentation, and focused tests. ChangesNote viewer foundation
Rendering and controls
Extension integration
Migration and validation
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Extension
participant NoteViewerState
participant NoteViewerCodeLens
participant FrilVaultDecorator
participant Editor
Extension->>NoteViewerState: create state
Extension->>NoteViewerCodeLens: register provider
Extension->>FrilVaultDecorator: inject state
Editor->>NoteViewerCodeLens: request lenses
NoteViewerCodeLens->>NoteViewerState: read expansion state
Editor->>FrilVaultDecorator: render document notes
FrilVaultDecorator->>Editor: set block decorations
Editor->>Extension: invoke toggle command
Extension->>NoteViewerState: toggle group state
NoteViewerState-->>FrilVaultDecorator: emit refresh
NoteViewerState-->>NoteViewerCodeLens: refresh lenses
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/vscode-extension/src/features/note-viewer/codelens.ts`:
- Around line 34-56: Update the CodeLens provider around the store snapshot used
to build groups so it returns no lenses while the snapshot is loading or belongs
to a different document than document. Only offer the “Note Add” lens after the
snapshot is ready and confirmed for the current document, and add a regression
test covering loading/editor-switch state where existing notes must not produce
“Note Add”.
In `@apps/vscode-extension/src/features/note-viewer/model.ts`:
- Around line 32-39: Update the expanded-state calculation in the map within the
note viewer model to use options.isExpanded(group.id) as the authoritative
value, removing the || options.defaultExpanded fallback. Preserve the existing
group and collapsedText/expandedText mapping behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f0421a8a-1695-4d03-b04a-235f354f4eeb
📒 Files selected for processing (22)
apps/vscode-extension/CHANGELOG.mdapps/vscode-extension/README.mdapps/vscode-extension/package.jsonapps/vscode-extension/src/constants/ids.tsapps/vscode-extension/src/extension.tsapps/vscode-extension/src/features/decorations/decorator.tsapps/vscode-extension/src/features/decorations/gutterHover.tsapps/vscode-extension/src/features/hover/hoverProvider.tsapps/vscode-extension/src/features/hover/register.tsapps/vscode-extension/src/features/inline-editor/config.tsapps/vscode-extension/src/features/inline-editor/editor.tsapps/vscode-extension/src/features/note-viewer/codelens.tsapps/vscode-extension/src/features/note-viewer/config.tsapps/vscode-extension/src/features/note-viewer/model.tsapps/vscode-extension/src/features/note-viewer/state.tsapps/vscode-extension/src/features/presentation/editorNoteView.tsapps/vscode-extension/src/features/presentation/inlinePreviewSettings.tsapps/vscode-extension/src/test/gutterMarker.test.tsapps/vscode-extension/src/test/hoverProviderRegistration.test.tsapps/vscode-extension/src/test/noteViewerCodeLens.test.tsapps/vscode-extension/src/test/noteViewerModel.test.tsapps/vscode-extension/src/test/noteViewerState.test.ts
💤 Files with no reviewable changes (7)
- apps/vscode-extension/src/features/hover/register.ts
- apps/vscode-extension/src/test/hoverProviderRegistration.test.ts
- apps/vscode-extension/src/features/hover/hoverProvider.ts
- apps/vscode-extension/src/features/decorations/gutterHover.ts
- apps/vscode-extension/src/features/presentation/inlinePreviewSettings.ts
- apps/vscode-extension/src/test/gutterMarker.test.ts
- apps/vscode-extension/src/features/presentation/editorNoteView.ts
| const notes = store.getSnapshot().notes.filter((note) => note.source_file === relativePath); | ||
| const groups = groupNotesForViewer(notes, document.lineCount); | ||
| const lenses: vscode.CodeLens[] = []; | ||
| const documentUri = document.uri.toString(); | ||
| const defaultState = getNoteViewerDefaultState(); | ||
|
|
||
| for (const note of notes) { | ||
| const lineNumber = resolvePresentationNoteLine(note); | ||
|
|
||
| if (lineNumber === undefined) { | ||
| continue; | ||
| } | ||
|
|
||
| const line = lineNumber - 1; | ||
| for (const group of groups) { | ||
| const expanded = state.isExpanded(documentUri, group.id, defaultState); | ||
| lenses.push( | ||
| new vscode.CodeLens(new vscode.Range(line, 0, line, 0), { | ||
| title: 'Note Edit', | ||
| command: 'frilvault.editNote', | ||
| arguments: [note.note.id, note.source_file, note], | ||
| new vscode.CodeLens(new vscode.Range(group.line, 0, group.line, 0), { | ||
| title: expanded ? 'Collapse Note' : 'Expand Note', | ||
| command: COMMAND_IDS.noteViewerToggle, | ||
| arguments: [documentUri, group.id], | ||
| }), | ||
| ); | ||
| } | ||
|
|
||
| const activeEditor = vscode.window.activeTextEditor; | ||
| if (activeEditor && activeEditor.document.uri.toString() === document.uri.toString()) { | ||
| if (activeEditor && activeEditor.document.uri.toString() === documentUri) { | ||
| const activeLine = activeEditor.selection.active.line; | ||
| const hasNoteOnActiveLine = notes.some((note) => { | ||
| const lineNumber = resolvePresentationNoteLine(note); | ||
|
|
||
| return lineNumber !== undefined && lineNumber - 1 === activeLine; | ||
| }); | ||
| const hasNoteOnActiveLine = groups.some((group) => group.line === activeLine); | ||
|
|
||
| if (!hasNoteOnActiveLine) { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not offer “Note Add” from a stale snapshot.
During an editor switch, the store can be loading or still represent another document. This makes groups empty and exposes “Note Add” even where notes already exist. Return no lenses until the snapshot is ready for document, and add a loading/editor-switch regression test.
Proposed fix
- const notes = store.getSnapshot().notes.filter((note) => note.source_file === relativePath);
+ const snapshot = store.getSnapshot();
+ if (snapshot.loading || snapshot.editorDocumentUri !== document.uri.toString()) {
+ return [];
+ }
+
+ const notes = snapshot.notes.filter((note) => note.source_file === relativePath);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const notes = store.getSnapshot().notes.filter((note) => note.source_file === relativePath); | |
| const groups = groupNotesForViewer(notes, document.lineCount); | |
| const lenses: vscode.CodeLens[] = []; | |
| const documentUri = document.uri.toString(); | |
| const defaultState = getNoteViewerDefaultState(); | |
| for (const note of notes) { | |
| const lineNumber = resolvePresentationNoteLine(note); | |
| if (lineNumber === undefined) { | |
| continue; | |
| } | |
| const line = lineNumber - 1; | |
| for (const group of groups) { | |
| const expanded = state.isExpanded(documentUri, group.id, defaultState); | |
| lenses.push( | |
| new vscode.CodeLens(new vscode.Range(line, 0, line, 0), { | |
| title: 'Note Edit', | |
| command: 'frilvault.editNote', | |
| arguments: [note.note.id, note.source_file, note], | |
| new vscode.CodeLens(new vscode.Range(group.line, 0, group.line, 0), { | |
| title: expanded ? 'Collapse Note' : 'Expand Note', | |
| command: COMMAND_IDS.noteViewerToggle, | |
| arguments: [documentUri, group.id], | |
| }), | |
| ); | |
| } | |
| const activeEditor = vscode.window.activeTextEditor; | |
| if (activeEditor && activeEditor.document.uri.toString() === document.uri.toString()) { | |
| if (activeEditor && activeEditor.document.uri.toString() === documentUri) { | |
| const activeLine = activeEditor.selection.active.line; | |
| const hasNoteOnActiveLine = notes.some((note) => { | |
| const lineNumber = resolvePresentationNoteLine(note); | |
| return lineNumber !== undefined && lineNumber - 1 === activeLine; | |
| }); | |
| const hasNoteOnActiveLine = groups.some((group) => group.line === activeLine); | |
| if (!hasNoteOnActiveLine) { | |
| const snapshot = store.getSnapshot(); | |
| if (snapshot.loading || snapshot.editorDocumentUri !== document.uri.toString()) { | |
| return []; | |
| } | |
| const notes = snapshot.notes.filter((note) => note.source_file === relativePath); | |
| const groups = groupNotesForViewer(notes, document.lineCount); | |
| const lenses: vscode.CodeLens[] = []; | |
| const documentUri = document.uri.toString(); | |
| const defaultState = getNoteViewerDefaultState(); | |
| for (const group of groups) { | |
| const expanded = state.isExpanded(documentUri, group.id, defaultState); | |
| lenses.push( | |
| new vscode.CodeLens(new vscode.Range(group.line, 0, group.line, 0), { | |
| title: expanded ? 'Collapse Note' : 'Expand Note', | |
| command: COMMAND_IDS.noteViewerToggle, | |
| arguments: [documentUri, group.id], | |
| }), | |
| ); | |
| } | |
| const activeEditor = vscode.window.activeTextEditor; | |
| if (activeEditor && activeEditor.document.uri.toString() === documentUri) { | |
| const activeLine = activeEditor.selection.active.line; | |
| const hasNoteOnActiveLine = groups.some((group) => group.line === activeLine); | |
| if (!hasNoteOnActiveLine) { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/vscode-extension/src/features/note-viewer/codelens.ts` around lines 34 -
56, Update the CodeLens provider around the store snapshot used to build groups
so it returns no lenses while the snapshot is loading or belongs to a different
document than document. Only offer the “Note Add” lens after the snapshot is
ready and confirmed for the current document, and add a regression test covering
loading/editor-switch state where existing notes must not produce “Note Add”.
| return groupNotesForViewer(notes, lineCount).map((group) => { | ||
| const expanded = options.isExpanded(group.id) || options.defaultExpanded; | ||
|
|
||
| return { | ||
| group, | ||
| expanded, | ||
| collapsedText: formatCollapsedBlock(group, options.maxPreviewLines), | ||
| expandedText: formatExpandedBlock(group), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Respect explicit collapse overrides.
Line 33 turns false back into true whenever the default is expanded. Since NoteViewerState.isExpanded() already resolves the default, a user cannot collapse a viewer with defaultState: "expanded"; the CodeLens says “Expand Note” while the block remains expanded. Treat isExpanded as authoritative and remove the redundant default fallback from this model contract.
Proposed fix
export interface NoteViewerModelOptions {
- defaultExpanded: boolean;
isExpanded: (groupId: string) => boolean;
maxPreviewLines: number;
}
- const expanded = options.isExpanded(group.id) || options.defaultExpanded;
+ const expanded = options.isExpanded(group.id);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return groupNotesForViewer(notes, lineCount).map((group) => { | |
| const expanded = options.isExpanded(group.id) || options.defaultExpanded; | |
| return { | |
| group, | |
| expanded, | |
| collapsedText: formatCollapsedBlock(group, options.maxPreviewLines), | |
| expandedText: formatExpandedBlock(group), | |
| return groupNotesForViewer(notes, lineCount).map((group) => { | |
| const expanded = options.isExpanded(group.id); | |
| return { | |
| group, | |
| expanded, | |
| collapsedText: formatCollapsedBlock(group, options.maxPreviewLines), | |
| expandedText: formatExpandedBlock(group), |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/vscode-extension/src/features/note-viewer/model.ts` around lines 32 -
39, Update the expanded-state calculation in the map within the note viewer
model to use options.isExpanded(group.id) as the authoritative value, removing
the || options.defaultExpanded fallback. Preserve the existing group and
collapsedText/expandedText mapping behavior.
Summary
Replace the legacy inline after-line note preview with a single block-style editor viewer for VS Code.
Closes #179
Type of Change
Required
running 23 tests
test command::add::tests::create_anchor_builds_line_anchor ... ok
test command::add::tests::create_anchor_builds_symbol_anchor ... ok
test tests::parses_explorer_json_format ... ok
test tests::parses_health_command_alias ... ok
test tests::parses_attach_command ... ok
test tests::parses_gitignore_check_json_format ... ok
test tests::parses_add_command_with_tags ... ok
test tests::parses_index_json_format ... ok
test tests::parses_list_format_text ... ok
test tests::parses_list_format_json ... ok
test tests::parses_repair_interactive_flag ... ok
test tests::parses_health_json_format ... ok
test tests::parses_search_with_file_and_json_format ... ok
test tests::parses_repair_json_format ... ok
test tests::parses_stats_json_format ... ok
test tests::parses_search_with_tag ... ok
test tests::parses_symbol_add_command ... ok
test tests::resolve_format_defaults_to_text ... ok
test tests::parses_sync_json_format ... ok
test tests::rejects_legacy_json_flag ... ok
test index_command::index_command_outputs_json_workspace_counts ... ok
test index_command::index_command_outputs_text_workspace_counts ... ok
test index_command::run_dispatches_index_command ... ok
test result: ok. 23 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
running 116 tests
test symbol::resolver::tests::resolve_prefers_line_hint_when_multiple_matches ... ok
test symbol::resolver::tests::find_by_name_locates_symbol ... ok
test symbol::resolver::tests::resolve_uses_signature_marker ... ok
test tests::attachment_test::attachment_repository_rejects_unsupported_image_type ... ok
test tests::gitignore_test::append_vault_to_gitignore_appends_to_existing_gitignore ... ok
test tests::gitignore_test::append_vault_to_gitignore_creates_gitignore_file ... ok
test tests::gitignore_test::is_vault_gitignored_detects_vault_entry ... ok
test tests::gitignore_test::append_vault_to_gitignore_is_idempotent ... ok
test tests::gitignore_test::is_vault_gitignored_returns_false_when_gitignore_is_missing ... ok
test tests::note_cache_test::clear_removes_all_cached_entries ... ok
test tests::note_cache_test::get_returns_none_for_missing_path ... ok
test tests::frilvault_app_test::frilvault_open_creates_workspace_service ... ok
test tests::note_cache_test::insert_and_get_return_cached_note_file ... ok
test tests::note_cache_test::invalidate_removes_cached_entry ... ok
test tests::note_entity_test::create_note_from_input ... ok
test tests::gitignore_test::is_vault_gitignored_ignores_comments_and_blank_lines ... ok
test tests::note_entity_test::create_note_generates_uuid ... ok
test tests::note_entity_test::create_note_stores_tags ... ok
test tests::note_resolver_test::resolve_note_path_returns_vault_path ... ok
test tests::note_resolver_test::source_file_from_note_path_returns_source_path ... ok
test tests::note_resolver_test::workspace_relative_path ... ok
test tests::frilvault_app_test::frilvault_open_creates_note_service ... ok
test tests::attachment_test::attachment_repository_rejects_oversized_image ... ok
test tests::attachment_test::delete_note_removes_image_directory ... ok
test tests::attachment_test::attach_image_stores_file_and_updates_note_metadata ... ok
test tests::attachment_test::detach_image_removes_file_and_metadata ... ok
test tests::note_service_test::add_note_updates_persisted_index ... ok
test tests::note_service_test::add_line_type_note_creates_json_file ... ok
test tests::note_service_test::add_note_and_load_note ... ok
test tests::note_service_test::add_symbol_type_note_creates_json_file ... ok
test tests::note_service_test::delete_note_removes_note ... ok
test tests::note_service_test::delete_note_updates_persisted_index_count ... ok
test tests::note_service_test::find_symbol_note_returns_matching_symbol ... ok
test tests::note_service_test::load_notes_from_existing_json ... ok
test tests::note_service_test::search_by_symbol_returns_empty_when_not_found ... ok
test tests::note_service_test::search_by_symbol_returns_matching_notes ... ok
test tests::note_service_test::list_symbol_notes_returns_only_symbol_notes ... ok
test tests::note_service_test::query_notes_combines_file_keyword_and_tag_filters ... ok
test tests::note_service_test::search_finds_symbol_anchor ... ok
test tests::note_service_test::search_notes_by_file_accepts_absolute_workspace_paths ... ok
test tests::note_uri_test::note_uri_parses_legacy_unversioned_format ... ok
test tests::note_uri_test::note_uri_rejects_malformed_inputs ... ok
test tests::note_service_test::search_by_tag_returns_matching_notes ... ok
test tests::note_uri_test::note_uri_rejects_path_traversal_in_workspace ... ok
test tests::note_uri_test::note_uri_resolver_serialize_matches_service_helper ... ok
test tests::note_uri_test::note_uri_round_trips_with_versioned_format ... ok
test tests::note_service_test::search_notes_by_file_returns_notes_for_source_file ... ok
test tests::note_service_test::update_note_changes_content ... ok
test tests::note_service_test::update_note_changes_tags ... ok
test tests::note_service_test::search_notes_finds_matching_notes ... ok
test tests::note_uri_test::resolve_note_uri_returns_missing_note_error ... ok
test tests::parser_test::deserialize_note_file ... ok
test tests::note_service_test::update_note_rejects_stale_expected_updated_at ... ok
test tests::parser_test::deserialize_note_file_without_attachments_defaults_to_empty ... ok
test tests::parser_test::deserialize_note_file_without_tags_defaults_to_empty ... ok
test tests::parser_test::serialize_note_file ... ok
test tests::note_uri_test::resolve_note_uri_returns_matching_note ... ok
test tests::note_uri_test::resolve_note_uri_returns_unknown_workspace_for_mismatch ... ok
test tests::note_uri_test::resolve_note_uri_returns_unresolved_anchor_for_symbol_without_match ... ok
test tests::note_uri_test::resolve_note_uri_returns_stale_note_when_source_file_is_missing ... ok
test tests::repair_engin_test::repair_engine_applies_high_confidence_moves_when_threshold_allows ... ok
test tests::repair_engin_test::repair_engine_moves_note_files ... ok
test tests::repair_engin_test::repair_engine_invalidates_cache_correctly ... ok
test tests::symbol_resolver_test::find_symbol_in_source_locates_symbol_by_name ... ok
test tests::note_uri_test::resolve_note_uri_survives_source_file_rename_after_sync ... ok
test tests::vault_context_test::clear_notes_cache_removes_all_entries ... ok
test tests::symbol_resolver_test::symbol_note_survives_line_movement ... ok
test tests::repository_test::list_all_note_files_returns_all_note_files ... ok
test tests::symbol_resolver_test::symbol_note_survives_function_relocation ... ok
test tests::vault_context_test::load_notes_populates_cache ... ok
test tests::vault_context_test::invalidate_notes_removes_cached_entry ... ok
test tests::vault_context_test::load_notes_uses_cache_on_second_load ... ok
test tests::vault_context_test::add_note_invalidates_cached_entry ... ok
test tests::vault_context_test::note_service_preloads_notes_for_source_file ... ok
test tests::vault_context_test::preload_notes_populates_cache ... ok
test tests::vault_context_test::delete_note_invalidates_cached_entry ... ok
test tests::workspace_index_repository_test::detects_removed_and_added_files ... ok
test tests::symbol_resolver_test::symbol_note_survives_file_relocation ... ok
test tests::vault_context_test::preload_notes_skips_disk_when_cache_is_warm ... ok
test tests::workspace_index_repository_test::detects_renamed_file_by_name_similarity ... ok
test tests::workspace_index_repository_test::create_if_missing_creates_index_directory ... ok
test tests::workspace_index_repository_test::detects_strong_rename_by_name_and_path ... ok
test tests::vault_context_test::search_notes_uses_cache_for_indexed_files ... ok
test tests::vault_context_test::update_note_invalidates_cached_entry ... ok
test tests::workspace_index_repository_test::load_returns_default_index_when_missing ... ok
test tests::vault_context_test::search_notes_reflects_updated_content_after_update ... ok
test tests::workspace_index_repository_test::health_check_refreshes_exists_flags_from_loaded_index ... ok
test tests::workspace_index_repository_test::rejects_unrelated_files ... ok
test tests::workspace_index_repository_test::apply_repairs_moves_note_file ... ok
test tests::workspace_index_repository_test::health_check_detects_missing_files_from_note_repository ... ok
test tests::workspace_index_repository_test::load_or_rebuild_loads_existing_index_without_scanning ... ok
test tests::workspace_index_test::workspace_index_can_store_files ... ok
test tests::workspace_index_repository_test::load_or_rebuild_scans_when_index_file_is_missing ... ok
test tests::workspace_index_test::workspace_index_starts_empty ... ok
test tests::workspace_index_repository_test::save_and_load_index ... ok
test tests::workspace_index_test::workspace_index_upsert_and_move_files ... ok
test tests::workspace_repository_test::create_if_missing_creates_default_directories ... ok
test tests::workspace_repository_test::create_if_missing_creates_workspace_metadata ... ok
test tests::workspace_index_repository_test::rebuild_marks_missing_files_as_not_existing ... ok
test tests::workspace_index_repository_test::rebuild_creates_index_from_note_files ... ok
test tests::workspace_service_test::index_returns_file_counts_for_indexed_files ... ok
test tests::workspace_index_repository_test::repair_suggests_matching_file_names ... ok
test tests::workspace_index_repository_test::stats_counts_line_and_symbol_notes ... ok
test tests::workspace_service_test::sync_notes_directory_changes_succeeds_without_notes_folder ... ok
test tests::workspace_service_test::explorer_builds_directory_file_and_note_groups ... ok
test tests::workspace_service_test::warm_up_succeeds_without_notes_folder ... ok
test tests::workspace_service_test::sync_notes_directory_changes_detects_renamed_note_files ... ok
test workspace::content_match::tests::extract_markers_skips_line_anchors ... ok
test tests::workspace_service_test::repair_suggests_content_match_when_source_file_is_renamed ... ok
test tests::workspace_service_test::sync_external_changes_refreshes_cache_and_index ... ok
test tests::workspace_service_test::sync_notes_directory_changes_clears_cache_and_rebuilds_index ... ok
test workspace::content_match::tests::extract_markers_uses_symbol_signatures_when_available ... ok
test workspace::content_match::tests::match_score_returns_one_when_all_markers_are_present ... ok
test tests::workspace_service_test::warm_up_builds_index_from_note_files ... ok
test tests::workspace_service_test::sync_source_file_changes_relocates_notes_after_source_rename ... ok
test tests::workspace_service_test::warm_up_uses_persisted_index_without_rescanning ... ok
test result: ok. 116 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s passes
Rust validation was not run because this change only touches the VS Code extension surface.
Checklist
Implementation
Architectural Decisions
Validation
Limitations / Follow-up
Summary by CodeRabbit
New Features
Changed
Documentation