Skip to content

feat(vscode): replace inline previews with block note viewer - #181

Open
mors119 wants to merge 1 commit into
FrilLab:mainfrom
mors119:feat/179-block-note-viewer
Open

feat(vscode): replace inline previews with block note viewer#181
mors119 wants to merge 1 commit into
FrilLab:mainfrom
mors119:feat/179-block-note-viewer

Conversation

@mors119

@mors119 mors119 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replace the legacy inline after-line note preview with a single block-style editor viewer for VS Code.

Closes #179

Type of Change

  • feat
  • fix
  • docs
  • refactor
  • test
  • chore

Required

  • passes
  • passes
  • passes
  • [ ]
    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

  • Code builds successfully
  • Tests pass
  • Documentation updated

Implementation

  • replace the legacy inline preview decoration path with a block-style viewer rendered from a single note-viewer model
  • keep gutter markers and reuse the existing note actions through a hover-driven entry instead of a second viewer path
  • add document-scoped viewer state for collapse and expand behavior while an editor remains open
  • register a dedicated note viewer CodeLens provider for , , and
  • remove obsolete inline preview settings and the duplicate hover-provider registration path
  • add model, state, and CodeLens coverage for the new viewer behavior

Architectural Decisions

  • keep note loading in and reuse existing anchor resolution instead of adding a second note-loading path
  • use supported VS Code decoration attachments for the multiline block text and CodeLens only for lightweight actions
  • keep edit, delete, open, and copy flows in the existing gutter action service rather than duplicating command logic in the viewer
  • move inline editor debounce configuration into its own config helper so the removed preview settings file does not remain as a dead dependency

Validation

  • [x]
  • [x]
  • [x]
  • [x]
  • [x]
  • [x]
  • [x]

Limitations / Follow-up

  • VS Code's supported editor APIs do not provide a custom multiline inline block with embedded interactive controls, so the implementation uses a hybrid of decoration-rendered block content plus CodeLens and hover-triggered actions.
  • The repository's cached bundle in this environment was missing its Electron executable, so the extension test host was verified against the local install instead.

Summary by CodeRabbit

  • New Features

    • Added expandable note blocks above code anchors in the VS Code extension.
    • Added controls to expand or collapse note groups, with support for multi-line content, previews, and tags.
    • Added settings to enable the viewer, choose its default state, and limit collapsed previews.
  • Changed

    • Replaced legacy inline note previews with the new block-style viewer.
    • Note actions remain available through hover menus.
  • Documentation

    • Updated setup guidance, viewer behavior, settings, and known limitations.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The 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.

Changes

Note viewer foundation

Layer / File(s) Summary
Viewer model, configuration, and state
apps/vscode-extension/package.json, src/features/note-viewer/config.ts, src/features/note-viewer/model.ts, src/features/note-viewer/state.ts
Adds viewer settings, grouped note blocks with collapsed and expanded text, anchor resolution, tag formatting, and per-document expansion overrides.

Rendering and controls

Layer / File(s) Summary
Block rendering and CodeLens controls
src/features/decorations/decorator.ts, src/features/note-viewer/codelens.ts, src/features/presentation/editorNoteView.ts, src/test/gutterMarker.test.ts
Renders note blocks through VS Code decorations, adds hover actions and expand/collapse CodeLens commands, and removes obsolete inline hover-summary exports and tests.

Extension integration

Layer / File(s) Summary
Activation, commands, and lifecycle
src/constants/ids.ts, src/extension.ts
Registers the note-viewer toggle command, creates and refreshes viewer state, tracks visible editors, wires CodeLens and decorations, and disposes state during shutdown.

Migration and validation

Layer / File(s) Summary
Legacy path removal and verification
CHANGELOG.md, README.md, src/features/inline-editor/*, src/features/presentation/inlinePreviewSettings.ts, src/features/hover/*, src/test/noteViewer*
Documents the new viewer, moves debounce configuration to the inline-editor module, removes legacy inline and hover-provider paths, and adds model, state, and CodeLens coverage.

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
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: replacing inline note previews with a block note viewer.
Linked Issues check ✅ Passed The PR implements the block note viewer, state, CodeLens/actions, and removal of legacy inline paths, matching the linked feature goals.
Out of Scope Changes check ✅ Passed The changes stay focused on the note viewer migration and supporting docs/tests, with no clear unrelated additions.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 629562c and 7db5b6e.

📒 Files selected for processing (22)
  • apps/vscode-extension/CHANGELOG.md
  • apps/vscode-extension/README.md
  • apps/vscode-extension/package.json
  • apps/vscode-extension/src/constants/ids.ts
  • apps/vscode-extension/src/extension.ts
  • apps/vscode-extension/src/features/decorations/decorator.ts
  • apps/vscode-extension/src/features/decorations/gutterHover.ts
  • apps/vscode-extension/src/features/hover/hoverProvider.ts
  • apps/vscode-extension/src/features/hover/register.ts
  • apps/vscode-extension/src/features/inline-editor/config.ts
  • apps/vscode-extension/src/features/inline-editor/editor.ts
  • apps/vscode-extension/src/features/note-viewer/codelens.ts
  • apps/vscode-extension/src/features/note-viewer/config.ts
  • apps/vscode-extension/src/features/note-viewer/model.ts
  • apps/vscode-extension/src/features/note-viewer/state.ts
  • apps/vscode-extension/src/features/presentation/editorNoteView.ts
  • apps/vscode-extension/src/features/presentation/inlinePreviewSettings.ts
  • apps/vscode-extension/src/test/gutterMarker.test.ts
  • apps/vscode-extension/src/test/hoverProviderRegistration.test.ts
  • apps/vscode-extension/src/test/noteViewerCodeLens.test.ts
  • apps/vscode-extension/src/test/noteViewerModel.test.ts
  • apps/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

Comment on lines 34 to 56
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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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”.

Comment on lines +32 to +39
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),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

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.

[FEATURE] Replace legacy inline note viewer with expandable block note viewer

2 participants