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.
Three defects in
packages/chrome-extension/src/utils/triggerDescription.ts, surfaced while writing the test that #98 asked for. All three are currently pinned asKNOWN ISSUEinpackages/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:
1. No events are dispatched, so TinyMCE is never told the content changed
triggerDateandtriggerDurationboth dispatch the sharedpressEnterkeydown after setting.value.triggerDescriptiondispatches nothing — it only writestextContentonto the iframe'sbody#tinymce. TinyMCE keeps its own model of the editor content and updates it from DOM events; a directtextContentwrite bypasses that entirely.2. The
<textarea>bexio actually submits is never syncedThe field that gets posted with the form is
#monitoring_text;body#tinymceis 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 contractgetDescriptionField()(src/selectors/descriptionField.ts:7-12) either returns the element or throwsError("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,triggerDescriptiontherefore rejects instead of quietly doing nothing.That rejection then goes nowhere useful.
onMessage.ts:33calls it withoutawait:so it never joins
handleExchangeRequest's promise chain. The listener's.thenfires 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
ExchangeResponsecontract exists precisely so the side panel can distinguish "applied" from "nothing happened", and this path reports success on failure.Suggested fix
if (descriptionField)guard, or makegetDescriptionFieldreturnnullinstead of throwing and keep the guard — pick one, they are mutually exclusive.await triggerDescription(...)inonMessage.tsso a failure reaches the dispatcher'scatchand is reported as{ ok: false, error }. It is a cheap, bounded call, unlikefillForm, so awaiting it does not risk holding the message channel open (see the doc comment inonMessage.ts).#monitoring_textas well and/or dispatch the events TinyMCE listens for.triggerDescriptionsection ofdocs/architecture/form-layer.mdfor whatever changes.