Skip to content

Internalize category race condition fix - #476

Open
nabalone wants to merge 9 commits into
developfrom
internalization_categoryfix2
Open

Internalize category race condition fix#476
nabalone wants to merge 9 commits into
developfrom
internalization_categoryfix2

Conversation

@nabalone

@nabalone nabalone commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

The artifact-category deferred-commit work. Now targets develop (PR #469 "Internalize changes part 1" has been squash-merged into develop, so this rebases to just the category delta on top of it).

What changed

  • SelectArtifactCategory → free-solo Autocomplete. Blur/change only resolve against existing categories (resolveExisting); a brand-new typed name is created at form submission via a single commit() handle exposed through commitRef. Avoids leaving an orphaned category behind when the user navigates away without saving.
  • Consumers wired to commitRef so creation happens at save: ResourceOverview/ResourceCategory, DiscussionCard, and PassageDetailArtifacts(+mobile) for both the edit and add dialogs (two refs because two instances can be mounted).
  • Record path fix: recording reaches finish() via afterUploadCb rather than uploadMedia, so the upload-path beforeUpload commit never ran and a newly typed category was silently dropped. The record path now commits the category too (guarded on mediaId).
  • RefObject instead of deprecated MutableRefObject (React 19).

Review follow-ups included

  • Save enabled for a new-category-only edit: deferring creation meant a new typed name never fired onCategoryChange, so forms gating Save on a changed flag (ResourceOverview, DiscussionCard) could never enable it. Added a lightweight onNewDraft(hasDraft) callback that marks those forms dirty as the user types a new name. Kept the single commitRef for creation — a ref can't reactively re-enable a button, so one small callback is the minimal complement.
  • Never show a duplicated category name: the picker options are now deduplicated by localized name (creation already blocks duplicate localized names, but two distinct slugs can collapse to the same localized string), so the previous render-index key workaround is removed.

Test plan

  • Type a brand-new category and Save in: edit-resource dialog, discussion card (as admin), Add Audio Resource (upload) — category is created and attached.
  • Add Audio Resource by recording with a new category typed — category is created and attached (was previously dropped).
  • Type a new category as the only edit — Save/Add enables.
  • Navigate away without saving after typing a new name — no orphan category created.
  • Scripture-highlight categories still show the info tooltip on the selected value and in the dropdown.

🤖 Generated with Claude Code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Copying from the Previous PR, Sarah wrote: "We shouldn't ever allow a duplicated name...is this covering a possible temporary state?"

@nabalone
nabalone changed the base branch from internalize_changes_part1 to develop July 24, 2026 15:28
@nabalone
nabalone force-pushed the internalization_categoryfix2 branch from 6b1dc76 to 6ce8867 Compare July 24, 2026 15:31
nabalone and others added 9 commits July 28, 2026 13:31
React 19 types collapse the two ref shapes into a single writable
RefObject<T>, deprecating MutableRefObject. Swap the category commit-ref
prop types accordingly; useRef call sites are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The recording path reaches finish() via afterUploadCb rather than
uploadMedia, so the beforeUpload commit never ran and a newly typed
category was silently dropped (resource saved with no category, category
never created). Commit deferred metaData edits in afterUploadCb too,
guarded on mediaId so no orphan category is created when nothing was
recorded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deferring new-category creation to commit() means a brand-new typed name
never fires onCategoryChange, so consumers that gate their Save button on
a `changed` flag (ResourceOverview, DiscussionCard) could never enable it
when the new category was the only edit — a regression vs the old
create-on-blur behavior.

Add a lightweight onNewDraft(hasDraft) callback to SelectArtifactCategory
that fires as the user types a new (allowNew) name matching no existing
category. ResourceCategory and DiscussionCard use it to mark themselves
dirty. Kept the single commitRef function ref as the creation channel
(no second ref / draft-name machinery); a ref alone can't reactively
re-enable a button, so one small callback is the minimal complement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Autocomplete options were the raw localized names with no dedupe, and
a render-index was baked into the React key to avoid collisions when two
categories shared a localized name — i.e. it masked the duplicate rather
than preventing it. New-category creation already blocks duplicate
localized names, but two distinct slugs can still collapse to the same
localized string (e.g. created under different languages).

Deduplicate the options by localized name (keeping the first occurrence,
matching how resolveExisting/commit resolve a typed name to an id), so a
name can never appear twice. The key can then be the unique category id
and the index workaround is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit() ran on every save and re-resolved the field text to an id by
name. When the field is unchanged that round-trip could (a) drop the
category — if its localized name is filtered out of the options the field
shows blank, so commit() saw '' and cleared it — or (b) switch it to a
different id when two slugs share the same localized name (commit picks
the first match, not necessarily the committed one).

Short-circuit commit() when the trimmed input equals the committed
category's name (including empty === empty), returning the existing
categoryId untouched. New/changed names still resolve and create as
before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nabalone
nabalone force-pushed the internalization_categoryfix2 branch from bdc5790 to 1ded511 Compare July 28, 2026 17:32
@nabalone nabalone changed the title Internalize category: defer new-category creation to save (SelectArtifactCategory commitRef) Internalize category race condition fix Jul 28, 2026
@nabalone

Copy link
Copy Markdown
Collaborator Author

Claude says test failure is preexisting, I haven't looked into it

@nabalone
nabalone marked this pull request as ready for review July 28, 2026 21:36
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.

2 participants