Skip to content

Add analysis-keyed mutations (edit, merge, delete) - #286

Merged
alex-rawlings-yyc merged 55 commits into
mainfrom
analysis-catalog-mutations
Sep 23, 2026
Merged

alex-rawlings-yyc merged 55 commits into
mainfrom
analysis-catalog-mutations

Conversation

@alex-rawlings-yyc

@alex-rawlings-yyc alex-rawlings-yyc commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Analysis Catalog entries can now be edited, merged, and deleted.
    • Edit glosses, morpheme breakdowns, and individual morpheme glosses directly in catalog rows.
    • Merge homographs with reordering, confidence selection, field editing, and preview warnings.
    • Delete confirmations explain fallback, blank, usage, and unapplied-token impacts.
    • Unsaved breakdowns are preserved across filtering and require confirmation before closing or destructive actions.
    • Added clearer notices for merged records, stranded drafts, missing glosses, and unavailable breakdowns.
    • Filters now stay synchronized with currently available choices.
  • Bug Fixes

    • Improved preservation of annotations and lexicon links during morpheme edits and re-segmentation.

@alex-rawlings-yyc alex-rawlings-yyc self-assigned this Aug 31, 2026
@coderabbitai

coderabbitai Bot commented Aug 31, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 412ad0c5-e93d-46bf-8797-8bceed6ea460

📝 Walkthrough

Walkthrough

Changes

Analysis Catalog editing

Layer / File(s) Summary
Analysis-level store operations
src/store/analysisSlice.ts, src/components/AnalysisStore.tsx, src/utils/*
Adds analysis-keyed editing, merging, deletion, collapse tracking, morpheme reconciliation, deletion outcomes, filter reconciliation, and merge-master derivation.
Catalog editing workflow
src/components/AnalysisCatalogPanel.tsx, src/components/CatalogRowEditor.tsx, src/components/CatalogRowView.tsx, src/components/CatalogMergeModal.tsx
Adds editable rows, merge and delete actions, staged merge fields, draft protection, merge notices, and survivor handling.
Morpheme and modal behavior
src/components/MorphemeEditor.tsx, src/components/MorphemeBox.tsx, src/components/modals/ModalShell.tsx, src/components/Catalog*Modal.tsx
Adds re-split confirmation, read-only breakdown rendering, modal popover containment, and localized close, merge, and delete confirmations.
Filter and integration updates
src/components/CatalogFilterPopover.tsx, src/components/InterlinearizerLoader.tsx, src/hooks/useRowWindow.ts
Reconciles withdrawn filter selections, supplies live surface text, and ensures merge survivors remain mounted.
Validation and support
src/__tests__/*, contributions/localizedStrings.json, package.json, .github/dependabot.yml, __mocks__/*
Adds broad component, store, utility, modal, and reducer tests, plus localization, dnd-kit dependencies, Dependabot grouping, and test mocks.

Priority: ➖ Normal

Estimated code review effort: 5 (Critical) | ~90 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant CatalogRowEditor
  participant AnalysisCatalogPanel
  participant AnalysisStore
  participant AnalysisSlice
  CatalogRowEditor->>AnalysisCatalogPanel: commit gloss or morpheme edit
  AnalysisCatalogPanel->>AnalysisStore: dispatch analysis-level write
  AnalysisStore->>AnalysisSlice: update linked tokens and collapse duplicates
  AnalysisSlice-->>AnalysisStore: return collapse survivor state
  AnalysisStore-->>AnalysisCatalogPanel: report edited, merged, or removed outcome
  AnalysisCatalogPanel-->>CatalogRowEditor: render updated row or merge notice
Loading

Suggested reviewers: imnasnainaec

Merge Risk: 🔵 Low · up to e2aee

The change is mergeable with bounded follow-up: several catalog labels are unclear or untranslated, and one optional editor configuration does not update correctly, but normal merge and editing paths are not shown to fail.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding analysis-keyed edit, merge, and delete mutations, which matches the pull request objectives and implementation.
Docstring Coverage ✅ Passed Docstring coverage is 97.60% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 125 functions across 32 files. (3 skipped: …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@alex-rawlings-yyc
alex-rawlings-yyc force-pushed the analysis-catalog-mutations branch 3 times, most recently from 02b4532 to af36923 Compare September 2, 2026 19:57
@alex-rawlings-yyc
alex-rawlings-yyc marked this pull request as ready for review September 2, 2026 21:23
@alex-rawlings-yyc
alex-rawlings-yyc force-pushed the analysis-catalog-mutations branch from c36b249 to c0d3d46 Compare September 3, 2026 17:35
imnasnainaec

This comment was marked as resolved.

@alex-rawlings-yyc alex-rawlings-yyc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@alex-rawlings-yyc made 6 comments.
Reviewable status: 1 of 24 files reviewed, 6 unresolved discussions (waiting on imnasnainaec).


src/components/CatalogRowEditor.tsx line 277 at r1 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

⛏️ Consider only showing this appliesToAll message when an analysis has multiple uses.

Done — the note is now absent below two uses, and names the count when it does show: "Edits here apply to all 7 uses of this analysis."


src/components/CatalogRowEditor.tsx line 295 at r1 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

For consistency with the token chip, could we add a box/border around the morpheme breakdown part of this row editor?

One edge example where it'd be especially helpful:

We don't allow saving breakdowns that equal the surface form, but we can import them from PT9. When that happens, it's very unclear what the extra textbox is for:
image.png

Done, in both the editable and read-only branches so switching between them doesn't rearrange the row. The PT9 case is what sold it — the border is what tells an imported single-morpheme breakdown apart from the form it repeats. The box and its columns size to their contents rather than to a fixed width, and the row above it got more breathing room.


src/components/CatalogRowEditor.tsx line 316 at r1 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

❓ For consistent style and a less-packed row editor, could we re-use the pop-over MorphemeEditor here?

Done — the row editor renders MorphemeBreakdownPopover now, and its duplicated draft/confirm/commit logic is gone. The popover gained an optional controlled draft so the panel can keep holding it, which the unsaved-draft guards depend on. Two knock-ons worth flagging: the merge and delete controls moved out to a CatalogRowActions component rendered below the usage list, so what a delete is about to take is in view above the button; and two catalog-only behavior changes fall out of the shared editor, both covered by tests — emptying the field no longer resets the breakdown (Discard is the explicit route), and Escape during a confirmation dismisses the panel rather than stepping back to the draft. Nothing writes either way.


src/components/CatalogRowEditor.tsx line 389 at r1 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

The inputs are overlapping

image.png

Fixed. Each morpheme column now sizes to its own form above a minimum, rather than truncating inside a fixed width, so a long breakdown wraps instead of overrunning its neighbor.


src/store/analysisSlice.ts line 1363 at r1 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

Inspired by Devin, drafted by Claude, partially reviewed by me:

usageCount counts approved links only (:1236-1246), but
removeAnalysisAndLinks (:370-375) drops every link regardless of status. So a
candidate-only payload gets "used nowhere, so nothing else changes"
(localizedStrings.json:81) while its candidate links leave the saved draft — reachable,
since loadFromProject (useDraftProject.ts:282-295) copies imported analysis into an
editable draft and the zero-usages filter surfaces exactly those rows.

The selector is right: a candidate link changes nothing a token reads, and a link to a
deleted payload has to go with it. Fix the copy instead — reword deleteBlankNone to
"No token is analyzed as this, so nothing on screen changes." Counting candidates here
would surface a status concept no row or filter shows (analysis-query.ts:157, :284).

Verified and fixed as the copy change. The reachability holds up: analysisMerger demotes a would-be-approved PT9 record to candidate when the token already carries an approval, and each entry gets its own id, so a candidate-only payload does reach an editable draft. Left the selector counting approved links only, with a comment recording why.


src/store/analysisSlice.ts line 1373 at r1 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

Inspired by Devin, drafted by Claude, partially reviewed by me:

With Show suggestions off, useResolvedTokenAnalysis is short-circuited
(TokenChip.tsx:124-127), so the affected tokens read blank and "N uses will fall back
to X" names something the reader can't see.

The flag isn't in Redux, so this selector can't see it. Degrade in the hook:
useAnalysisDeletionOutcome (AnalysisStore.tsx:618-628) sits inside the callbacks
context carrying showSuggestions, so map a fallback outcome to
{ kind: 'blank', usageCount } when suggestions are hidden. Both messages stay true,
and handleDeleteConfirm's stale-outcome re-read is unaffected — both reads go
through the same getter.

Verified and fixed in the hook as suggested. One adjustment: the gate is showSuggestions && !readOnly, mirroring what TokenChip passes into useResolvedTokenAnalysis. This turned up four existing catalog tests asserting fallback copy under a harness defaulting showSuggestions to false — they now pass it explicitly.

@imnasnainaec

This comment was marked as resolved.

@imnasnainaec

This comment was marked as resolved.

imnasnainaec

This comment was marked as resolved.

@alex-rawlings-yyc
alex-rawlings-yyc force-pushed the analysis-catalog-mutations branch from 324ceac to 5e3628c Compare September 4, 2026 18:44

@alex-rawlings-yyc alex-rawlings-yyc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

All three done. The surface form now gets its own labelled line above everything else, and the dialog below it reads left to right as two columns: the analysis you clicked Merge on, an arrow, then the homographs it can become. Every analysis on either side shows its morpheme breakdown under its gloss, alongside the usage count.

On the third point, the source is shown on the left as the fixed subject of the merge rather than as a marked entry in the list, so the direction is stated by the layout instead of having to be inferred from which row is highlighted. Merging the other way is a matter of opening the picker from that row instead.

Re: scrollbar: Fixed — the list no longer gains a scrollbar on click. The rebuilt dialog replaced the overflowing button rows with radio rows in a container sized to fit them, so the overflow that caused it is gone rather than padded around.

@alex-rawlings-yyc made 2 comments.
Reviewable status: 1 of 24 files reviewed, 1 unresolved discussion (waiting on imnasnainaec).


src/store/analysisSlice.ts line 1363 at r1 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

Devin insists this is still an issue:

selectAnalysisDeletionOutcome counts only approved token links, while removeAnalysisAndLinks deletes links of every status. As a result, candidate-only, rejected, suggested, or stale assignments are omitted from the destructive confirmation. Update the deletion outcome and CatalogDeleteModal copy to account for non-approved links that will also be removed, or preserve those assignments through an explicit replacement policy. Keep the approved usage count separate if it remains useful for describing visible applied uses. Add tests covering a candidate-only analysis through both outcome selection and confirmed deletion.

Right on the facts. Updated the outcome and copy, but with the counts kept separate rather than merged.

AnalysisDeletionOutcome gains unappliedCount (distinct tokenRef, so an approved token isn't counted on both sides), and the modal adds a second sentence when it's non-zero: "1 token also records this analysis without using it; that record is deleted too."

Not folded into usageCount: nothing renders a non-approved link, so that would make the confirmation say "1 use will be left with no analysis" for a row the catalog lists as 0 uses. Tests cover every non-approved status, the dedup, and a candidate-only record through both selection and deletion. A replacement policy preserving these assignments is a feature, not a fix — no surface offers a candidate today.

@imnasnainaec imnasnainaec left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Much improved! I'm still pondering if there's a clean layout that could avoid forcing the user to find and click the other row to switch the merge direction.

@imnasnainaec reviewed 2 files and all commit messages, made 2 comments, and resolved 1 discussion.
Reviewable status: 2 of 25 files reviewed, 1 unresolved discussion (waiting on alex-rawlings-yyc).


src/components/AnalysisStore.tsx at r3 (raw file):
CI typecheck failed: https://github.com/sillsdev/interlinearizer-extension/actions/runs/33907518405/job/101135874261?pr=286

@alex-rawlings-yyc

Copy link
Copy Markdown
Contributor Author

I'm workshopping that now

@imnasnainaec

This comment was marked as resolved.

@imnasnainaec

This comment was marked as outdated.

@alex-rawlings-yyc alex-rawlings-yyc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think I fixed the read-only issue (I don't have a readonly project to test with afaik).

I reworked the merge UI again. I think we're getting closer to what we want.

@alex-rawlings-yyc made 2 comments.
Reviewable status: 2 of 25 files reviewed, 1 unresolved discussion (waiting on imnasnainaec).


src/components/AnalysisStore.tsx at r3 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

CI typecheck failed: https://github.com/sillsdev/interlinearizer-extension/actions/runs/33907518405/job/101135874261?pr=286

Fixed

@alex-rawlings-yyc

Copy link
Copy Markdown
Contributor Author
Screenshot_2026-09-04_15-03-26

Here is the current look of the merge UI

@imnasnainaec

Copy link
Copy Markdown
Contributor

I think I fixed the read-only issue (I don't have a readonly project to test with afaik).

@alex-rawlings-yyc Mock PT9 projects were just added via #283, so you can get a read-only project by opening one of them in our Interlinearizer and importing when prompted.

@alex-rawlings-yyc
alex-rawlings-yyc force-pushed the analysis-catalog-mutations branch 2 times, most recently from f8d97ff to e0d64a7 Compare September 9, 2026 19:09
@alex-rawlings-yyc

Copy link
Copy Markdown
Contributor Author
Screenshot_2026-09-04_15-03-26

Here is the current look of the merge UI

@imnasnainaec imnasnainaec left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@imnasnainaec reviewed 6 files and all commit messages, made 1 comment, and resolved 1 discussion.
Reviewable status: 6 of 33 files reviewed, 1 unresolved discussion (waiting on alex-rawlings-yyc).


contributions/localizedStrings.json line 98 at r8 (raw file):

      "%interlinearizer_analysisCatalog_deleteTitle%": "Delete the analysis of {form}?",
      "%interlinearizer_analysisCatalog_deleteBlank%": "{count} uses will be left with no analysis.",
      "%interlinearizer_analysisCatalog_deleteBlankOne%": "1 use will be left with no analysis.",

For the most part, paranext-core doesn't have a separate localization string for the singular case. The one place they do (paranext-core/extensions/src/platform-enhanced-resources/contributions/localizedStrings.json), it is formatted differently than what's proposed here.

Full localization should ideally handle more than just singluar-vs-plural, so I'm inclined to drop the singular handling entirely until such time as core has a consistent and/or more robust approach.

If you really want to keep the singular/plural distinction, we should at least match the platform-enhanced-resources pattern/style.

Another reason I'd drop it in this pr is so that it can be applied uniformly across the extension in a focused pr, allowing the rule to be clearly documented, and avoiding inconsistent coverage in this extension.

More reasons: keep the code here a touch simpler; minimize churn while wording is still in flux in these early stages.

@alex-rawlings-yyc

Copy link
Copy Markdown
Contributor Author

contributions/localizedStrings.json line 98 at r8 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

For the most part, paranext-core doesn't have a separate localization string for the singular case. The one place they do (paranext-core/extensions/src/platform-enhanced-resources/contributions/localizedStrings.json), it is formatted differently than what's proposed here.

Full localization should ideally handle more than just singluar-vs-plural, so I'm inclined to drop the singular handling entirely until such time as core has a consistent and/or more robust approach.

If you really want to keep the singular/plural distinction, we should at least match the platform-enhanced-resources pattern/style.

Another reason I'd drop it in this pr is so that it can be applied uniformly across the extension in a focused pr, allowing the rule to be clearly documented, and avoiding inconsistent coverage in this extension.

More reasons: keep the code here a touch simpler; minimize churn while wording is still in flux in these early stages.

Good call; dropped

@alex-rawlings-yyc
alex-rawlings-yyc force-pushed the analysis-catalog-mutations branch 2 times, most recently from ec24b63 to a2ec5b1 Compare September 16, 2026 17:39

@imnasnainaec imnasnainaec left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In the merge dialog, the morpheme breakdown part
image.png

should probably reuse the catalog UI/format:
image copy 1.png

(especially because the former is blank and confusing when none of the merge candidates have a breakdown...)
image copy 2.png

@imnasnainaec reviewed all commit messages, made 2 comments, and resolved 1 discussion.
Reviewable status: 4 of 33 files reviewed, 1 unresolved discussion (waiting on alex-rawlings-yyc).


src/store/analysisSlice.ts line 1529 at r10 (raw file):

      l.analysisId === analysisId &&
      l.status === 'approved' &&
      normalizeSurfaceForm(l.token.surfaceText) !== normalizeSurfaceForm(analysis.surfaceText),

Claude suggestion synthesis of a Devin flag:

drifted only compares two stored snapshots (TokenAnalysis.surfaceText vs TokenAnalysisLink.token.surfaceText), which move together and are never resynced to the live baseline text outside a gloss/morpheme edit on that token. So it can't detect the case it exists for: the baseline moves, the token was never re-edited, and this stays false while the fallback promised here no longer applies to the live token.

Fix: derive drifted from the live token surface text (as selectResolvedTokenAnalysis does), not from the link's stored copy.

The merge content utility is renamed from "master" to match the store's
MergedContent terminology.
@alex-rawlings-yyc
alex-rawlings-yyc force-pushed the analysis-catalog-mutations branch from 7dd1b47 to 6ebfb3b Compare September 23, 2026 15:33
imnasnainaec

This comment was marked as resolved.

@imnasnainaec imnasnainaec left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@imnasnainaec partially reviewed 29 files and all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on alex-rawlings-yyc).

@alex-rawlings-yyc
alex-rawlings-yyc enabled auto-merge (squash) September 23, 2026 17:27
@alex-rawlings-yyc
alex-rawlings-yyc merged commit c896b61 into main Sep 23, 2026
10 checks passed
@alex-rawlings-yyc
alex-rawlings-yyc deleted the analysis-catalog-mutations branch September 23, 2026 17:28
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.

Analysis catalog: analysis-keyed mutations (edit, merge, delete)

2 participants