Skip to content

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

Open
alex-rawlings-yyc wants to merge 14 commits into
mainfrom
analysis-catalog-mutations
Open

Add analysis-keyed mutations (edit, merge, delete)#286
alex-rawlings-yyc wants to merge 14 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

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

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

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: Team

Run ID: 94b5b2be-c605-45b3-a963-7ee6ec142553


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 2 times, most recently from 2dc45e8 to 621ba4b Compare September 1, 2026 22:47
The catalog's rows are shared TokenAnalysis payloads with no token in
context, so the existing tokenRef-keyed reducers cannot serve them:
those
fork a shared payload to keep an edit local to one token, which is the
opposite of what editing a catalog row means.

Add a second reducer family keyed by analysisId — writeAnalysisGloss,
writeAnalysisMorphemes, writeAnalysisMorphemeGloss, deleteAnalysis, and
mergeAnalysisInto — that never forks. The key alone determines the blast
radius, so neither family needs a scope flag. Each write re-converges
onto
a content-identical sibling as the per-token path does.

Add selectAnalysisDeletionOutcome, which reports whether deleting a row
leaves its tokens blank or falling back to a surviving homograph, so the
confirmation can name the concrete consequence. It rebuilds the pool
with
the payload dropped outright rather than discounting a single approval,
which would leave a multi-token payload competing to replace itself.

Add selectAnalysisMergePeers to offer merge targets from the row's own
pool bucket, so merging is confined to genuine homographs.
Reconcile the filter selections against the facets each render, so a
selection cannot outlive the choice it names: an edit beside the panel
that empties a facet no longer strands the listing behind a control that
is off screen. Distinguish a placeholder label from a real value reading
the same, the platform combo box resolving a selection by its label
alone. Carry an unchanged morpheme across a re-split whole, keeping the
id its references depend on along with its gloss and lexicon refs. Count
the deletion preview's usages off the approved-token index the catalog
row counts by, as its own doc already promised.
Merge peers came off the suggestion pool, which admits approved analyses
alone, so an imported homograph was listed as a catalog row that could
neither be merged into nor merged away. Index every payload by surface
form instead, matching what mergeAnalysisInto already accepts.
Commit the reconciled set back over the reader's choices, so a value
dropped from a facet is gone rather than merely unused. Held, it
returned
the moment its facet did: an edit that withdrew the last row carrying a
chosen value, undone, left the listing narrowed by a filter the reader
had
watched release, with the restored row the only one it kept.
The catalog listed what the draft records without offering any way to
correct it, while the reducers that would do so had no caller. A row is
a
shared analysis with no token in context, so editing one is an edit to
every token linked to it — the correction a mis-split word repeated a
hundred times needs.

Expanding a row now reveals its gloss, its morpheme breakdown and each
morpheme's gloss, and the controls for merging or deleting it. Fields
commit on blur and Enter and revert on Escape, so an edit is not written
across every token of an analysis mid-word.

Deleting is confirmed by a modal naming the concrete consequence — how
many uses go blank, or which surviving homograph they fall back to.
Delete
ships before undo, so that copy is the only guard, and a confirmation
that
promised a fallback that does not exist would be worse than none.
Merging
is offered only to a row with pool peers, since there is otherwise
nothing
to reassign its tokens to, and preselects no target: it moves every use
of
one analysis onto another and drops the source.

An edit that makes a row identical to a sibling collapses the two, which
leaves the edited row gone from the listing and another's count grown. A
banner above the list now names where the edit went and what the
survivor
counts, and the listing scrolls to it, so the pair reads as the
convergence it is rather than as lost work. Nothing the write reports
says
a collapse happened, so the dispatch hook reads the store either side of
it and follows the old links to see where they now point.

Merge peers are bucketed by the normalized surface form the pool buckets
by; grouping by the raw text would offer the control to homographs
differing only in case and then open an empty picker.

The platform-bible-react stub's Input dropped onBlur, which left every
commit-on-blur field silently uncommitted in tests.
readEditOutcome treated any link surviving on the edited record's tokens
as
the survivor of a collapse. A token may hold several links at once — the
one-per-token invariant covers only approved ones — so emptying a record
whose token kept an unrelated suggested or candidate link reported a
merge
into a record that had gained nothing. Identify the survivor instead by
a
link naming a record its token was not already linked to, which only a
collapse produces.

Register the catalog row editor's drafts with the pending-edit tracker,
as
the interlinear view's inputs already do. The gloss fields commit on
blur,
so this only lights the unsaved indicator while typing; the breakdown
editor commits on Enter or Save alone, and its draft was discarded by a
project switch with no confirmation. Compare the draft as forms rather
than
as text so the whole word pre-filled for an unsegmented breakdown does
not
register as unsaved work.
readEditOutcome inferred a merge from links the edited record's tokens
had moved onto. A record no token links to moves none, so collapsing one
left exactly the state its removal would leave and was reported as a
removal — the row vanished with no notice, for the unused records a
Paratext 9 import lands by the hundred.

Record the survivor in the reducer that collapses the record, where the
payload deciding merge-from-removal is still in hand, and read it back
through transient state that never reaches storage.

Compare useAnalysisMergePeers shallowly. Its selector filters per row
and
so allocates a fresh array on every call, re-rendering every row holding
peers on every store change.

Cover the analysis-keyed hooks directly rather than through the panel,
where their write outcomes were asserted only as the absence of a notice
element — three distinct paths sharing one negative DOM fact.
Three ways the catalog dropped work or the thread of it.

A merge notice names the row an edit collapsed onto and scrolls to it,
but the reveal only fires for a row the window has mounted. An unlinked
record inherits no usages to carry its survivor up the listing, so the
survivor stays where its gloss sorts it — past the first chunk, and the
notice pointed at nothing on screen. Let a caller name a row the window
must cover, applied over the count so a new listing still resets.

The breakdown editor held its draft itself, and the breakdown commits on
neither blur nor unmount, so collapsing the row or a query that stopped
listing it took a typed re-segmentation with it. Hold the drafts in the
panel, which outlives all three routes, and report them from there — the
editor unmounts while the draft it was reporting for is still owed.

Clearing the breakdown drops every morpheme gloss on the record, for
every token it holds, and nothing forks a copy first. Confirm it, as the
token editor confirms its own reset.
The breakdown editor commits on neither blur nor unmount, so a
re-segmentation survives a row collapsing or a query that stops
listing it. Closing the panel ended that silently: the draft went
with the unmounted panel, and the unsaved marker cleared with it.

Gate the close control on the predicate the unsaved marker already
reported, and offer discard alone — saving would commit an edit that
drops the old morphemes' glosses for every token the record holds.

The question is mounted on the draft still standing as well as on
the ask, so saving or canceling it from the row beneath withdraws
the question instead of asking about work that is no longer unsaved.
Both breakdown editors now name the forms whose glosses a re-split would
drop
and confirm first, matching the prompt each already showed for clearing
a
breakdown outright. The token editor skips the prompt for a shared
payload,
which the write forks rather than re-segmenting in place.
@alex-rawlings-yyc
alex-rawlings-yyc force-pushed the analysis-catalog-mutations branch from 02b4532 to af36923 Compare September 2, 2026 19:57
An imported analysis no longer offers editors or merge/delete, and a
breakdown draft whose row an edit elsewhere removed is now reported.
@alex-rawlings-yyc
alex-rawlings-yyc marked this pull request as ready for review September 2, 2026 21:23
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)

1 participant