Skip to content

triggerDescription dispatches no events, never syncs the submitted textarea, and its dead guard turns a missing iframe into a silent unhandled rejection #124

Description

@Cyclodex

Three defects in packages/chrome-extension/src/utils/triggerDescription.ts, surfaced while writing the test that #98 asked for. All three are currently pinned as KNOWN ISSUE in packages/chrome-extension/test/utils/triggerDescription.test.ts (added in #118) rather than fixed, so anyone changing this code has the current behaviour recorded — but the behaviour itself is wrong.

The whole function is four lines:

async function triggerDescription(value: string) {
    const descriptionField = getDescriptionField();
    if (descriptionField) {
        descriptionField.textContent = value;
    }
}

1. No events are dispatched, so TinyMCE is never told the content changed

triggerDate and triggerDuration both dispatch the shared pressEnter keydown after setting .value. triggerDescription dispatches nothing — it only writes textContent onto the iframe's body#tinymce. TinyMCE keeps its own model of the editor content and updates it from DOM events; a direct textContent write bypasses that entirely.

2. The <textarea> bexio actually submits is never synced

The field that gets posted with the form is #monitoring_text; body#tinymce is only the editable surface inside #monitoring_text_ifr. TinyMCE normally copies the editor content back into that textarea on its own triggers — which, per defect 1, never fire here. So an imported note can be visible in the editor and still not be part of what bexio saves.

This is the one worth checking against real bexio first: if the visible editor content does reliably reach the server today, then something else is syncing it and only defects 1 and 3 need work. If it does not, ManicTime notes are being silently dropped on save, which would be the most user-visible bug of the three.

3. if (descriptionField) is dead code, and the failure escapes the messaging contract

getDescriptionField() (src/selectors/descriptionField.ts:7-12) either returns the element or throws Error("Description field not found") — it never returns a falsy value, so the guard can never be false. On a page where the iframe is not populated, triggerDescription therefore rejects instead of quietly doing nothing.

That rejection then goes nowhere useful. onMessage.ts:33 calls it without await:

const applyNotesSetting = await loadApplyNotesSetting();
if (applyNotesSetting && request.notes !== undefined) {
  triggerDescription(request.notes);   // not awaited
}

so it never joins handleExchangeRequest's promise chain. The listener's .then fires on the un-awaited path and answers the side panel with { ok: true }, while the rejection surfaces separately as an unhandled rejection in the console. The user is told the entry was applied; the notes were not.

Note this defeats part of what #86 built: the ExchangeResponse contract exists precisely so the side panel can distinguish "applied" from "nothing happened", and this path reports success on failure.

Suggested fix

  • Drop the dead if (descriptionField) guard, or make getDescriptionField return null instead of throwing and keep the guard — pick one, they are mutually exclusive.
  • await triggerDescription(...) in onMessage.ts so a failure reaches the dispatcher's catch and is reported as { ok: false, error }. It is a cheap, bounded call, unlike fillForm, so awaiting it does not risk holding the message channel open (see the doc comment in onMessage.ts).
  • Investigate defect 2 against real bexio and, if the textarea genuinely is not synced, write the value into #monitoring_text as well and/or dispatch the events TinyMCE listens for.
  • Update the pinned tests and the triggerDescription section of docs/architecture/form-layer.md for whatever changes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions