Skip to content

fix: code review improvements — regex, perf, UI, parser, and submission fixes#27

Open
kalinichenko88 wants to merge 31 commits intomasterfrom
chore/refactoring
Open

fix: code review improvements — regex, perf, UI, parser, and submission fixes#27
kalinichenko88 wants to merge 31 commits intomasterfrom
chore/refactoring

Conversation

@kalinichenko88
Copy link
Copy Markdown
Owner

@kalinichenko88 kalinichenko88 commented Mar 24, 2026

Summary

Comprehensive code review fixes across the codebase:

Critical

  • Fix budget block regex to require closing fence at line start (prevents cross-block matching)
  • Add incremental decoration updates via RangeSet.map() with widgetChangeAnnotation (avoids full rebuild on every keystroke)
  • Replace fragile posAtDOM + regex position scan with decoration set iteration and half-open intervals
  • Sync editingValue / Row local state with props on external changes (undo, sort, drag-and-drop)
  • Fix process.exit(0) mid-async in version-bump script; update packages[""].version in package-lock.json
  • Skip lines without | separator in parser to prevent phantom rows
  • Strip trailing colons from category names to prevent :: corruption on round-trip

Major

  • Compare Map keys and row IDs in TableWidget.eq() to prevent stale store reuse
  • Switch debounce to trailing-edge to avoid flushing before isEditing is set
  • Create context menu on each open to reflect current isDeletingEnabled
  • Prevent onChange on Escape by skipping blur handler after cancel
  • Rename plugin ID to budget-planner for Community Plugin submission compliance

Minor

  • Fix MouseEventWheelEvent type in handleOnWheel
  • Fix version script path (version-bump.jsscripts/version-bump.js)
  • Replace sample placeholder text with budget block format example
  • Trim trailing whitespace from formatted rows without comments
  • Update README and CLAUDE.md

Tests added: 30 new tests (regex, structural change detection, parser, formatter)

Test plan

  • All 50 tests pass
  • TypeScript typecheck passes
  • ESLint passes
  • Production build succeeds
  • Manual test: edit budget blocks, undo, sort, drag-and-drop, Escape cancel
  • Manual test: adjacent budget blocks don't interfere
  • Manual test: context menu reflects category count changes

Inline ``` in row names or comments could prematurely close the regex
match, causing incorrect block boundaries. Add multiline flag and ^
anchor so only line-start ``` closes the block. Add tests covering
inline backticks, multiple blocks, and boundary cases.
Avoid full doc.toString() + regex + parser rebuild on every keystroke.
Widget-dispatched changes are tagged with an annotation and handled via
RangeSet.map(). External changes that don't contain fence markers also
use map(). Full rebuild only runs when block structure may have changed.

Extract changesAffectBlockStructure into a testable helper with 10 tests.
findCurrentPosition used doc.toString() + regex re-scan on every save
to locate the widget's block — O(doc size) and fragile with adjacent
blocks due to closed interval [from, to] matching the wrong block.

Now iterates the StateField's DecorationSet with half-open [from, to)
intervals. Uses a late-bound registry to share the field reference
without circular imports between tableExtension and TableWidget.
editingValue was initialized once via untrack and never updated when the
value prop changed externally (undo, sorting, drag-and-drop). Opening a
cell after such changes would show stale data and overwrite the current
value on blur. Add $effect to sync editingValue with the prop while not
in editing mode.
…-lock.json handling

Replaced process.exit(0) calls that could kill pending writeFile
operations with conditional skips per file. Also update packages[""]
.version in package-lock.json. Includes lint/format fixes for test
files and auto-formatted docs.
Lines without | that aren't categories were silently parsed as rows,
creating phantom entries in the UI. Now require at least one pipe
separator, matching the format the formatter always produces.
Category names ending with : produced :: in the formatted markdown,
corrupting the name on each round-trip through parser. Now strip
trailing colons before appending the delimiter.
eq() only compared Map values, ignoring keys (CategoryId, RowId).
Since nanoid regenerates IDs on each parse, the old widget was reused
with stale IDs in its Svelte store. Now compares full entries including
keys and row IDs, ensuring fresh state on rebuild.

Also replace trimEnd() with regex for ES2018 target compatibility.
Leading-edge debounce could fire the write immediately before the
Editable component sets isEditing = true, interrupting active editing.
Trailing-edge fires 100ms after the last call, giving state time to
settle.
…abled

Menu was built once at component init, capturing a stale
isDeletingEnabled value. Delete item stayed disabled even after adding
a second category. Now create the menu fresh on each right-click.
Local state (checked, name, amount, comment) was initialized once via
untrack and never synced from the row prop. Add $effect to keep local
state in sync with external prop changes, preventing stale data from
overwriting fresh values via the updateRow $effect.
Escape set editingValue back to the original but blur still fired
handleOnLeave which called onChange, triggering an unnecessary save
cycle. Add a cancelled flag so handleOnLeave skips onChange when
Escape already handled the exit.
BREAKING: Plugin ID changed from "obsidian-budget-planner-plugin" to
"budget-planner" to comply with Community Plugin submission rules (no
"obsidian" in ID, can't end with "plugin"). Also removed "for Obsidian"
from description. Existing manual installs need to rename their plugin
folder.
Rename title to Budget Planner, remove "Obsidian" references, update
install path to budget-planner/. Update CLAUDE.md with incremental
decoration updates, parser/formatter improvements, new test files, and
constants module documentation. Add formatter test for empty name with
zero amount.
@kalinichenko88 kalinichenko88 self-assigned this Mar 24, 2026
Add sync $effect for categoryName prop in CategoryRow to prevent stale
name from overwriting external changes (same pattern as Editable and
Row fixes). Fix formatter test to exact-match empty name + zero amount
behavior. Assert NO_CATEGORY_NAME in parser test.
Narrow release workflow trigger from '**' to 'v*' to ignore non-release
tags. Align tsconfig target and lib to ESNext to match module setting
and unlock modern type definitions (Vite handles actual compilation).
…on file switch

Replace debounced commit with immediate dispatch so the CodeMirror
document always reflects the current Svelte store state. Previously,
editing multiple cells and switching files lost changes because
updateRow never triggered a document sync and the destroy-time flush
was unreliable.

- Add onTableChange call to updateRow (consistent with all other actions)
- Replace debounced commitTableChange with immediate syncToDocument
- Add oninput handler to Editable so store stays fresh during typing
- Track startValue in Editable for proper Escape revert
- Remove redundant commitChange call and unused import from Row
- Reorder destroy() to unmount before flush so teardown callbacks
  propagate final values to the store
With immediate document sync, isSaving is never set to true and
commitChange has no callers. Remove both along with all disabled
guards that depended on isSaving. Also fix double dispatch on
checkbox toggle by letting the $effect handle updateRow instead
of calling it explicitly in handleOnCheckboxClick.
- Fix release script to create v-prefixed tags matching workflow trigger
- Create Row context menu on each open for consistency with CategoryRow
- Remove dead disabled prop from AddRow and Editable after isSaving removal
- Simplify syncToDocument and actions onTableChange signature
- Update CLAUDE.md with v* tag pattern and synchronous state management docs
DnD refresh was triggered on every store change via queueMicrotask,
including checkbox toggles. SortableJS destroy/create cycle on the
first <tbody> reset input.checked before Svelte could apply updates.

Now DnD refresh only runs when table structure changes (row/category
IDs), not on data-only changes. Also switch checkbox input to
bind:checked with pointer-events:none for reliable DOM sync.
style(table-row): simplify checkbox input and class binding
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant