Metadata fetching interrupts manual tag entry

I add a document to Zotero 7.0.20. Zotero creates a parent item with metadata.

Then I start typing tags into the tags field of the parent item. Meanwhile Zotero is fetching more metadata for the item, updating the abstract, etc. But when it adds the extra metadata, it interrupts my typing in the middle of a word and moves my cursor into the second tag field while I'm still typing the first tag. So then I have to go back and correct the tag I was typing.

This is very annoying and happens all the time. I wish it would wait until I am done typing, or otherwise preserve my cursor location so it wouldn't interfere with what I'm doing.
  • Do you have any plugins installed? I don't think Zotero has any functionality to add metadata once a parent item exists (various plugins, e.g. Zotero linter, do).
  • edited December 10, 2025
    This is when I add the item via Zotero Connector.

    I don't have any other plugins installed. This all happens within the first few seconds. Fetching the abstract etc seems to be as part of the process of creating the parent item. But by that point I'm already typing the tags, in the Zotero desktop interface.
  • I gave Claude my logs and the codebase to look into the issue. I'm not familiar with the code so I can't say if it's right, but as I'm still experiencing this issue I figured it might be helpful.

    Claude:
    =======

    # Tag input focus loss bug during Connector save

    ## Symptom

    When saving a document from Chrome's Zotero Connector, then switching to Zotero and typing a tag, the tag input loses focus mid-keystroke. The partially typed text gets saved as one tag, and the remaining keystrokes become a second tag (e.g., "foobar" → "foob" + "ar").

    ## Root cause

    Two bugs compound:

    ### Bug 1: `attachmentBox.js` line 229-230

    ```javascript
    if (this._item && val.id != this._item.id && document.activeElement.closest("editable-text")) {
    document.activeElement.blur();
    }
    ```

    The `attachmentBox` item setter blurs `document.activeElement` whenever ANY editable-text in the entire document is focused — not just ones inside attachmentBox. The intent was to prevent stale title data from being saved when switching between attachments, but the check is document-global.

    `this._item` is only updated when the assigned item is an attachment (line 232-233). For regular items, `this._item` retains whatever attachment was last viewed, making `val.id != this._item.id` always true when displaying a regular item.

    ### Bug 2: `itemDetails.js` line 419-426

    ```javascript
    notify = async (action, type, ids, _extraData) => {
    if (action == 'refresh' && this.item) {
    // No check that ids includes this.item.id
    await this.render();
    }
    };
    ```

    The notify handler triggers a full `itemDetails.render()` for ANY `refresh/item` notification, regardless of which item was refreshed.

    ## Trigger chain

    1. User saves a document from Chrome Connector. Zotero creates the parent item plus child attachments (PDFs, snapshots).
    2. Several seconds later, fulltext indexing completes on a child attachment. This fires `refresh/item` for the attachment item.
    3. `itemDetails.notify()` receives `refresh/item` for the attachment. It doesn't check whether the refreshed item matches the displayed item, so it calls `this.render()`.
    4. `itemDetails.render()` iterates all panes and sets `box.item = item` on each, including attachmentBox.
    5. `attachmentBox.item` setter fires. `this._item` is stale (the last attachment ever viewed). `val` is the current parent item. Since their IDs differ, and the tag input is a focused editable-text, it calls `document.activeElement.blur()`.
    6. The tag input loses focus. The blur event triggers `saveTag()`, which saves the partial text as a tag, clears the input, and schedules a `setTimeout` to refocus it.
    7. The refocused input captures the remaining keystrokes. The user presses Enter thinking they're confirming the full tag but actually confirms a fragment.

    ## Why mouse interaction matters

    The bug requires `this._item` on attachmentBox to be non-null, which only happens after the user has previously viewed an attachment item (by clicking on one). After that, the stale reference persists for the lifetime of the pane.

    ## Debug evidence

    Three occurrences captured via runtime logging. Each showed the same pattern:

    ```
    NOTIFY refresh/item ids=34106 ← unrelated item (attachment being indexed)
    NOTIFY activeElement=input#.input ← user is typing in tag input
    Viewing item ← itemDetails.render() fires
    Refreshing item box ← pane loop runs
    Setting mode to 'edit' ← attachmentBox renders (blur already happened)
    Stopping autocomplete search ← autocomplete torn down due to focus loss
    Saving tag ← saveTag fires from blur
    TAG FOCUSOUT: relatedTarget=null ← focus gone, nowhere specific
    ```

    `relatedTarget` being null confirmed `.blur()` was called directly.

    ## Fixes

    **Minimal fix** — scope the blur to attachmentBox's own elements (`attachmentBox.js:229`):
    ```javascript
    if (this._item && val.id != this._item.id && this.contains(document.activeElement)) {
    ```

    **Defense in depth** — don't re-render for unrelated items (`itemDetails.js:420`):
    ```javascript
    if (action == 'refresh' && this.item && ids.includes(this.item.id)) {
    ```

    ## Key files

    - `chrome/content/zotero/elements/attachmentBox.js` — item setter with document-global blur
    - `chrome/content/zotero/elements/itemDetails.js` — notify handler lacking item ID filter
    - `chrome/content/zotero/elements/tagsBox.js` — saveTag triggered by blur
    - `chrome/content/zotero/xpcom/notifier.js` — notification dispatch system

Sign In or Register to comment.