This document is the single source of truth for developers maintaining or extending NC Connector for Thunderbird.
It complements:
docs/ADDON_DESCRIPTION.md(architecture overview)docs/ATN_REVIEW_CHECKLIST_INTERNAL.md(internal review constraints you must not violate)docs/ATN_REVIEW_NOTES.md(reviewer-facing release-specific notes)
- 1. Scope & goals
- 2. Supported versions
- 3. Repository layout
- 4. Running & debugging
- 5. Internationalization (i18n)
- 6. Options & storage keys
- 7. Calendar integration (Talk button in event editor)
- 8. Talk wizard (end-to-end flow)
- 9. Calendar monitoring & server sync
- 10. Sharing wizard (compose window)
- 11. Data model
- 12. Runtime messaging contracts
- 13. Network endpoints used
- 14. Packaging & release checklist
- 15. Troubleshooting
- 16. Reviewer constraints (must-read)
Goals for this project:
- Provide Nextcloud Talk room creation directly from the calendar event editor (dialog + tab).
- Provide Nextcloud sharing directly from the compose window (sharing wizard).
- Maintain no feature loss across reviewer-driven changes.
- Keep custom experiments minimal, deterministic, and auditable.
- Keep all user-facing text localized via WebExtension i18n (
_locales/**/messages.json).
Non-goals:
- We do not aim to implement our own calendar backend or duplicate Thunderbird’s calendar logic.
- We do not modify the official calendar experiment (
experiments/calendar/**) used by the add-on.
Thunderbird:
- Target: Thunderbird ESR 140.*
Enforced bymanifest.json:strict_min_version: "140.0"strict_max_version: "140.*"
Nextcloud:
- Requires a Nextcloud instance with:
- OCS endpoints enabled
- Nextcloud Talk installed
- Files sharing (DAV + OCS) enabled
- optional
nc_connectorbackend app for centralized seat/policy runtime
Top-level:
manifest.json— add-on manifest (MV2) + experiment registrationsmodules/— shared logic (background + reusable modules)ui/— HTML/JS/CSS for options and wizardsexperiments/experiments/calendar/— official calendar experiment API (kept as-is) for persisted item monitoringexperiments/ncCalToolbar/— minimal custom experiment for deterministic editor toolbar integration (dialog + tab)experiments/ncComposePrefs/— read-only experiment exposing Thunderbird compose big-attachment prefs for conflict locking
_locales/— translations (messages.jsonper locale)docs/— developer & reviewer documentation
Key files you’ll touch most:
modules/bgState.js— shared runtime state, startup initialization, and[NCBG]log helpermodules/bgComposeAttachments.js— compose attachment automation, threshold prompts, and sharing-launch context handlingmodules/bgComposeShareCleanup.js— compose-tab and wizard-window remote cleanup lifecyclemodules/bgComposeShareInsert.js— mode-aware share-block insertion (HTML vs plain-text compose)modules/bgComposePasswordDispatch.js— separate-password-mail dispatch and password policy fetch/generatemodules/bgCompose.js— compose/window/tab listener wiringmodules/bgSignature.js— central backend email-signature policy orchestration for compose windowsui/signatureCompose.js— compose-script DOM bridge for managed signature cleanup/insertionmodules/bgCalendarLifecycle.js— calendar wizard context and editor-close cleanup lifecycle helpersmodules/bgCalendar.js—ncCalToolbarintegration, room metadata mapping, and persisted calendar monitoring syncmodules/bgRouter.js—runtime.onMessagedispatcher for Talk/Sharing/Options/UI bridge contractsmodules/policyRuntime.js— centralized backend seat/policy status fetch + normalization (/apps/ncc_backend_4mc/api/v1/status)modules/background.js— thin bootstrap entrypointmodules/hostPermissions.js— single host-permission gate used by core/talk/sharing runtime modulesmodules/shareTemplateContract.js— shared share-template marker contract used by render + insert modulesmodules/nccore.js— Nextcloud auth/login-flow helpersmodules/talkAddressbook.js— system-addressbook CardDAV fetch/cache/search/status helpersmodules/talkcore.js— Nextcloud Talk API helpers (OCS, room lifecycle, capabilities)modules/ncSharing.js— Nextcloud sharing/DAV helpers used by the sharing wizardmodules/icalContract.js— shared iCal/vCard parser contract (powered by vendoredvendor/ical.js)experiments/ncComposePrefs/parent.js— read-only compose preference bridge (mail.compose.big_attachments.*)ui/talkDialog.html+ui/talkDialog.js— Talk wizard UIui/nextcloudSharingWizard.html+ui/nextcloudSharingWizard.js— Sharing wizard UIui/debugForwarder.js— shared runtime debug forwarding/helper layer for Talk, Sharing, and attachment prompt UIsui/addressbookUi.js— shared system-addressbook tooltip lock helper used by Talk wizard + optionsui/passwordPolicyClient.js— shared password-policy fetch/generate helper for both wizardsoptions.html+options.js— settings UI
Typical workflow (also used for manual testing):
- Create an
.xpi(it is just a ZIP with the add-on files at the root). - In Thunderbird: Add-ons Manager → Gear icon → Install Add-on From File…
- Restart Thunderbird if required.
Important for packaging:
manifest.jsonmust be at the root of the XPI (not inside a nested folder).- Avoid including previous
.xpifiles inside the new XPI.
To debug, you’ll typically use:
- The Thunderbird Developer Console / Error Console (for
[NCBG],[NCUI][Talk],[NCUI][Sharing],[NCUI][Options],[NCUI][OpenUrlFallback], and[ncCalToolbar]logs). - The add-on debug view (background + extension pages).
What to look for:
[NCBG]— background logic (calendar monitoring, Talk operations, cleanup)[NCUI][Talk]— Talk wizard UI flow[NCUI][Sharing]— Sharing wizard UI flow[NCUI][Options]— settings/options page flow[NCUI][OpenUrlFallback]— browser-open fallback dialog[ncCalToolbar]— custom editor integration logs (button/context/read-write lifecycle)- The bundled
experiments/calendar/**package remains upstream/as-is; any console output coming from it is outside the add-on debug-channel contract above.
Debug output is gated by the option:
debugEnabledinbrowser.storage.local
Implementation:
- Background uses
L(...)inmodules/bgState.jsand logs as[NCBG] …when enabled. ui/debugForwarder.jsmirrorsdebugEnabledlive into the Talk dialog, Sharing wizard, and attachment prompt UIs.- Those UIs use the shared forwarder/logger helpers instead of maintaining separate local debug-console variants:
installDebugEnabledMirror()keeps the page-local flag in sync withbrowser.storage.localcreateUiDebugLogger()forwards structured UI debug lines throughdebug:log
- Shared helper modules resolve their visible error/debug prefixes through
modules/logContext.js, so active extension pages stay inside the[NCUI][…]family and background stays on[NCBG]. - Actual error paths must still use
console.error(...)directly; they are not allowed to disappear just becausedebugEnabledis off. - Attachment automation adds debug traces for:
- threshold evaluation and prompt decisions in
[NCBG] - attachment-mode wizard/prompt flow in
[NCUI][Sharing]
- threshold evaluation and prompt decisions in
modules/htmlSanitizer.jsadds compact debug summaries for backend Talk/share/email-signature HTML sanitization whendebugEnabledis on:- input/sanitized/normalized lengths
- input/output element + attribute counts
- removed tag/attribute deltas
- anchor
reladjustments added during post-sanitize normalization - summaries stay on the existing channels instead of introducing a separate log path:
- UI sanitization appears through
[NCUI][Talk]/[NCUI][Sharing] - background sanitization appears through
[NCBG]
- UI sanitization appears through
- email signatures deliberately reuse the existing Share/Talk sanitizer rules; no separate HTML allowlist is introduced
ui/debugForwarder.jsalso centralizes debug-flag/runtime behavior for all add-on popup UIs:- live
debugEnabledupdates while the popup stays open - one shared forwarding path for Talk/Sharing/attachment prompt UI logs
- no extra
[NCBG] msg debug:logmeta noise for forwarded UI debug lines
- live
ui/debugForwarder.jsexposes explicit teardown hooks for the popup UIs:markRuntimeContextUnloading()stops new forwarded logs before closeflushPendingDebugLogs()gives already-starteddebug:logsends a short chance to settle beforewindow.close()
Keep in mind:
- Debug logs can contain URLs, tokens, and metadata. Treat logs as sensitive.
Translations live in:
_locales/<locale>/messages.json
UI translation in HTML:
- HTML uses
data-i18n="some_key" ui/domI18n.jsapplies translations on page load.
JS translation:
modules/i18n.jsexposes a translate function used by UI and background.
Rule of thumb:
- Do not hardcode user-visible strings. Add a new i18n key instead.
Checklist:
- Add folder
_locales/<new_locale>/messages.json(copy fromenand translate themessagefields). - Ensure all keys exist (missing keys show up as “Unknown localization message …”).
- Add the locale to
Translations.md. - Update
modules/i18nOverride.js:- Add the locale to
SUPPORTED_LOCALES - Add mapping to
SUPPORTED_BY_LOWERif needed (for region/script normalization)
- Add the locale to
- Verify JSON validity (no trailing commas).
The options UI provides language override selects for generated text blocks:
- Sharing HTML block language (
shareBlockLang) - Talk event description language (
eventDescriptionLang)
Implementation pieces:
options.html+options.jspopulate selects fromNCI18nOverride.supportedLocales.modules/i18nOverride.jsloads_locales/<locale>/messages.jsonand provides:NCI18nOverride.tInLang(lang, key, substitutions)
Design intent:
- The add-on UI follows the Thunderbird UI language (normal WebExtension i18n).
- Generated blocks inserted into mails/events can be forced to a specific language (useful in multi-language environments).
- Backend policy may additionally return
event_description_type = html | plain_text; whenhtmlis active, Thunderbird writes the Talk block into the rich event-description editor as HTML and relies on the editor snapshot/iCal serialization to persist the matching plain-text representation.
Options UI:
options.htmloptions.js
Storage backend:
browser.storage.local
Core:
baseUrl— Nextcloud base URLuser— Nextcloud usernameappPass— app password (or generated via Login Flow)debugEnabled— enable verbose logging
Talk defaults:
talkDefaultTitletalkDefaultLobbytalkDefaultListabletalkDefaultRoomType("event"or"normal")talkPasswordDefaultEnabledtalkDeleteRoomOnEventDeletetalkAddUsersDefaultEnabledtalkAddGuestsDefaultEnabledtalkAddParticipantsDefaultEnabled(legacy; kept for backward compatibility asaddUsers || addGuests)
Sharing defaults (managed by modules/sharingStorage.js):
sharingBasePath(base path)sharingDefaultShareNamesharingDefaultPermCreatesharingDefaultPermWritesharingDefaultPermDeletesharingDefaultPasswordsharingDefaultPasswordSeparatesharingDefaultExpireDayssharingAttachmentsAlwaysConnectorsharingAttachmentsOfferAboveEnabledsharingAttachmentsOfferAboveMb
Advanced language overrides:
shareBlockLang("default"or a supported locale folder name likede,pt_BR,zh_TW, …)eventDescriptionLang("default"or supported locale)
Talk “participants” split (as of 2.2.7):
- Previously: one toggle
X-NCTALK-ADD-PARTICIPANTS - Now: two toggles
X-NCTALK-ADD-USERSX-NCTALK-ADD-GUESTS
Backward compatibility:
- If both new properties are missing, we interpret legacy
ADD-PARTICIPANTSas “users + guests”. - When writing, we still write legacy
ADD-PARTICIPANTSasADD-USERS || ADD-GUESTS.
Reviewer goal:
- Prefer the official calendar experiment API (
experiments/calendar/**) and avoid custom injection.
Reality in Thunderbird ESR 140:
- We need a deterministic editor-targeted contract for dialog + tab editors, including new/unsaved items.
- We must not modify
experiments/calendar/**(reviewer rule), so editor UI integration cannot be solved there.
Current implementation:
experiments/ncCalToolbar/**provides only editor integration:- deterministic click/context bridge for the official
calendar_item_actionbutton in both editor variants - deterministic editor identity (
editorId) - editor-targeted snapshot/write-back (
getCurrent/updateCurrent) - tracked close lifecycle (
onTrackedEditorClosed)
- deterministic click/context bridge for the official
experiments/calendar/**remains untouched and is used only for persisted item monitoring.- Business logic remains in background runtime modules (
modules/bgState.js,modules/bgComposeAttachments.js,modules/bgComposeShareCleanup.js,modules/bgComposeShareInsert.js,modules/bgComposePasswordDispatch.js,modules/bgCompose.js,modules/bgCalendarLifecycle.js,modules/bgCalendar.js,modules/bgRouter.js,modules/talkAddressbook.js,modules/talkcore.js).
Event editors can open as:
- Dialog:
chrome://calendar/content/calendar-event-dialog.xhtml - Tab: inside
chrome://messenger/content/messenger.xhtmlwith acalendarEventtab + iframe
We must support both, without duplicating logic or increasing experiment scope.
Current constraint:
- Thunderbird ESR 140 does not yet provide a stable upstream API contract to resolve the active event-editor iframe context purely via API IDs in all dialog/tab permutations we need.
Current implementation in ncCalToolbar:
- We correlate the editor iframe to its
tabInfoin a scoped way to produce a deterministic opaqueeditorId. - This is intentionally limited to calendar editor surfaces only (
ExtensionSupport.registerWindowListenerwith editor chrome URLs), not generic window scanning. - Reviewer/ATN implementation detail:
ExtensionSupportis consumed as a global experiment symbol (noChromeUtils.importESModule(...)re-import inparent.js).- This matches
docs/ATN_REVIEW_CHECKLIST_INTERNAL.md("globals must be used directly in Experiment scripts"). - Startup listener registration includes a deferred retry when
ExtensionSupportis temporarily unavailable in the first startup tick, preventing intermittent bootstrap failures.
- Historical context:
- Add-on 2.2.7 already contained manual editor mapping (
windowId/dialogOuterId). - In 2.2.7 tab mode,
windowIdidentified the 3-pane host window only; follow-up editor operations could resolve via selectedcurrentTabInfo, which can drift after tab switches or with multiple open editor tabs. - In 3.0.0 this is based on an opaque
editorIdbridge so targeting is deterministic and API-contract oriented.
- Add-on 2.2.7 already contained manual editor mapping (
Upstream direction:
- We track this as a temporary bridge until upstream APIs expose the same deterministic contract.
- Reference: PR #65 (deterministic editor context contract proposal): thunderbird/webext-experiments#65
Entry / UI:
browser.ncCalToolbar.onClicked(snapshot)withsnapshot.editorId(bridge bound to the officialcalendarItemActionbutton)
Snapshot:
browser.ncCalToolbar.getCurrent({ editorId, returnFormat: "ical" })
Write-back:
browser.ncCalToolbar.updateCurrent({ editorId, fields, properties, returnFormat: "ical" })
Lifecycle:
browser.ncCalToolbar.onTrackedEditorClosedwith action payload (persisted,discarded,superseded)
On click, background receives:
browser.ncCalToolbar.onClicked(snapshot)as entrypoint (forwarded fromcalendar_item_action)- snapshot payload includes:
- an iCal snapshot of the currently edited item (
format: "ical",item: "BEGIN:VCALENDAR...") calendarIdandid(note:idcan be empty for new/unsaved items)- an
editorId(opaque identifier for one specific open editor)- contract: add-ons must treat
editorIdas opaque and must not parse it - lifetime: valid only while that editor remains open in the current Thunderbird session
- contract: add-ons must treat
For fresh reads before write-back, background can call:
browser.ncCalToolbar.getCurrent({ editorId, returnFormat: "ical" })
Why we rely on the iCal snapshot:
- New/unsaved items may not have a stable
itemIdyet. - The snapshot allows the wizard to work before the event is saved.
Problem:
- A user can create a Talk room, then close the editor without saving → we must prevent orphan rooms.
Solution:
- Background stores cleanup tracking via
talk:registerCleanupusingeditorId. browser.ncCalToolbar.onTrackedEditorClosed(tracked editors only, aftergetCurrent/updateCurrent) emits:action: "persisted"(saved) /"discarded"(closed/canceled) /"superseded"reason:dialogaccept,dialogextra1,dialogcancel,dialogextra2,unload,re-bound- ordering relative to other calendar item events is intentionally not guaranteed
Background behavior:
- If discarded: delete the room (if it was created during this session and not persisted).
- If persisted: cancel cleanup entry and keep the room.
Entry point:
browser.ncCalToolbar.onClickedlistener inmodules/bgCalendar.js
What happens:
- Create a
contextId(calendar wizard context) - Store:
editorIditem(iCal when Thunderbird exposes it)- live editor fields (
title,location,description,startTimestamp,endTimestamp) when iCal is not available yet - derived
event+metadatasnapshot
- Open
ui/talkDialog.html?contextId=...as a real popup window viabrowser.windows.create({ type: "popup" }) - Run a best-effort popup focus request (
browser.windows.update({ focused: true })) with short retries.- focus requests are intentionally non-fatal
- desktop/window-manager focus-stealing policies can still keep the previous window focused
Wizard initialization:
ui/talkDialog.jsreadscontextId- calls
talk:initDialogandtalk:getEventSnapshotto populate defaults from the snapshot
User clicks “Talk-Raum erstellen”:
- Wizard sends
talk:createRoomto background.
Background:
- uses
modules/talkcore.js+modules/ocs.js+modules/nccore.js - creates the room, applies lobby/listable/password, etc.
- If the user selected an event conversation, runtime performs exactly one event-bound create request.
- Runtime does not fall back from event conversation to standard room and does not fabricate pseudo object ids. Event conversations require a real start timestamp from the opened editor.
After create success, the wizard:
- Sends
talk:applyMetadata→ writeX-NCTALK-*properties into the open editor - Sends
talk:applyEventFields→ write:- title (from wizard)
- location = Talk URL
- description = generated block (localized, may include password + help URL)
- Sends
talk:trackRoom→ store runtime meta for monitoring - Sends
talk:registerCleanup→ enable orphan-room cleanup if the editor is discarded
Persistence model:
- The editor is updated immediately (in-memory).
- The data becomes persistent when the user clicks Save in the editor.
The Talk options include two independent toggles:
- Users: internal Nextcloud users (added via username)
- Guests: external e-mail addresses (added as guests)
Where it is stored:
X-NCTALK-ADD-USERS(TRUE/FALSE)X-NCTALK-ADD-GUESTS(TRUE/FALSE)- legacy:
X-NCTALK-ADD-PARTICIPANTS
When syncing happens:
- Invitee sync is triggered on calendar item upsert (after the item is saved/persisted), not immediately on wizard click.
Why:
- Persisted calendar updates are the stable “truth” and drive delegation/invitee workflows.
Guest e-mail behavior note:
- Whether guests receive a separate invitation e-mail and/or a “personal access link” can depend on Nextcloud server configuration and Talk version.
We expose two room types:
- Event-Unterhaltung (
eventConversation = true)- created with event-object metadata (
objectType,objectId)
- created with event-object metadata (
- Gruppenunterhaltung (
eventConversation = false)- standard public room
In iCal:
X-NCTALK-EVENTis stored as"event"or"standard"
The wizard allows choosing a moderator.
Important design choice:
- Delegation is deferred to the calendar monitoring flow after the event is persisted.
Reason:
- Delegation and participant sync must be robust across editor close/reopen and across machines, so it is driven by persisted calendar updates.
Properties used:
X-NCTALK-DELEGATE(ID)X-NCTALK-DELEGATE-NAME(label)X-NCTALK-DELEGATED(TRUE/FALSE)X-NCTALK-DELEGATE-READY(TRUEwhile pending)
We rely on browser.calendar.items.* from:
experiments/calendar/**(must remain unchanged)
We subscribe to:
browser.calendar.items.onCreatedbrowser.calendar.items.onUpdatedbrowser.calendar.items.onRemoved
We use returnFormat: "ical" so our parsing logic stays consistent.
On create/update (handleCalendarItemUpsert in modules/bgCalendar.js):
- Keep room meta in sync:
- lobby timer updates when event time changes
- store token ↔ event mapping
- Trigger invitee sync if enabled (
ADD-USERSand/orADD-GUESTS) - Trigger delegation flow if pending (
DELEGATE-READY) - If
X-NCTALK-TOKENis missing in the event payload, processing is skipped fail-closed (no token recovery from cached mapping).
On remove:
- Existing saved-event room deletion is opt-in only (
talkDeleteRoomOnEventDeleteor locked backend policytalk_delete_room_on_event_delete). - The removed event must have a trusted token mapping created from NC Connector
X-NCTALK-*properties. Tokens derived from genericLOCATIONorURLfields are never accepted as ownership proof. - If moderation was delegated, deletion may fail (403). This is expected and should be handled gracefully.
Orphan prevention is handled by:
browser.ncCalToolbar.onTrackedEditorClosed(editor saved vs discarded)- background cleanup maps keyed by room token + editor reference
Entry point:
compose_actionbutton opens the sharing wizard (popup window).compose.onAttachmentAddedcan auto-open the sharing wizard in attachment mode based on sharing options (alwaysor threshold-based).- After popup creation, background performs best-effort focus retries. Window-manager policy may still refuse foreground focus.
Responsibilities:
- The sharing wizard UI performs most DAV/OCS actions using shared modules.
- Public-link share creation follows the documented OCS contract:
labelis sent during create, and mutable metadata such asnoteis updated later via form-encoded OCS update arguments. - The background is used for compose insertion, because the compose APIs are executed from the background.
- In attachment mode, background removes selected attachments from compose and passes them as a one-time launch context to the wizard.
Key files:
ui/nextcloudSharingWizard.htmlui/nextcloudSharingWizard.jsui/composeAttachmentPrompt.htmlui/composeAttachmentPrompt.jsmodules/ncSharing.jsmodules/ocs.jsmodules/nccore.jsmodules/sharingStorage.js
Attachment mode specifics:
- Wizard starts in step 3 (files queue), without note step.
- Share label is fixed at create time; note metadata is pushed at finalize time via the documented OCS update endpoint.
- Share name base is fixed to
email_attachmentwith deterministic_1,_2, ... suffix handling. - Compose HTML block for this mode uses ZIP download URL (
/s/<token>/download) and hides permission row. - Recipient permissions are enforced as read-only in this mode (
read=true,create/write/delete=false), independent of sharing defaults. - Queue UI behavior:
- path column shows the best available source path (including file name)
- path text is horizontally scrollable per row (mouse wheel), while type/status columns remain fixed
- currently uploading row is highlighted in accent blue; upload progress and done state use green success styling
- Upload uniqueness behavior:
- local duplicate target paths are resolved before upload (rename prompt)
- no per-file remote preflight checks are executed for each queue entry in newly created share folders
- Share cleanup contract:
- cleanup is armed in background once a share was created and prepared for compose insertion
- cleanup is cleared only after successful
compose.onAfterSend(sendNow/sendLaterwith message id) - if compose tab is closed without successful send, background deletes the share folder on the server
- when send is still pending at tab-close time, cleanup delete is delayed by a short grace timer to avoid send/close races
- wizard-side cleanup is armed by window id in background; if the sharing wizard closes before finalize, background deletes the remote folder on
windows.onRemoved - finalize explicitly clears the wizard cleanup entry before closing the popup
- Password separation:
- Option + wizard toggle can send password in a dedicated follow-up mail.
- This toggle is only active when password protection is enabled.
- Main compose block omits the inline password and shows a dedicated hint when enabled.
- Background tracks live sender switches on
compose.onIdentityChanged, captures the final main-mail envelope oncompose.onBeforeSend, and dispatches password-only mail oncompose.onAfterSend. - The authoritative primary-mail sender is resolved via Thunderbird compose details plus
accountsReadidentity lookup; the password follow-up must use the same Thunderbird identity as the main mail. - The password follow-up itself targets only the primary mail
Torecipients;Cc/Bccare still captured as part of the authoritative main-mail envelope. - Backend custom password templates (
language_share_html_block=custom+share_password_template) are sanitized in the render path before follow-up registration; rich HTML usesNCSharing.buildHtmlBlock(...), plain text usesNCSharing.buildPlainTextBlock(...), and missing sanitizer or empty sanitized output aborts finalize (fail-closed). - Follow-up mail delivery mode mirrors the source compose mode (
isPlainText/deliveryFormat) captured from compose details and refreshed oncompose.onBeforeSend. - Follow-up registration now requires both pre-rendered HTML and pre-rendered plain text; when follow-up is plain text, background uses the provided plain-text block and frames it with a fixed 50-character
#border. - Dispatch path: first warm the freshly created password compose tab until Thunderbird exposes the expected sender/recipient envelope, then try
compose.sendMessage(..., { mode: "sendNow" })with a timeout guard for stuck send attempts. - If sender identity cannot be resolved cleanly, or if immediate send fails (or times out), background opens a prefilled compose draft as explicit manual fallback.
- If a manual fallback draft was opened, a dedicated desktop notification tells the user to send the password mail manually.
- Once the primary mail was sent, password-follow-up problems must never delete the committed remote share.
- If Thunderbird's own big-attachment upload setting is enabled, add-on attachment automation settings are locked and a guidance block is shown in options.
- The same lock is enforced live in background before evaluate/start/prompt-action and again at attachment-mode wizard finish.
- On cancel, attachments are not restored to compose (explicit product decision).
The sharing wizard sends:
browser.runtime.sendMessage({ type: "sharing:armComposeShareCleanup", payload: { tabId, folderInfo, ... } })browser.runtime.sendMessage({ type: "sharing:insertRenderedBlock", payload: { tabId, html, plainText } })
Background:
- arms compose-share cleanup before insertion (for unsent-tab cleanup handling)
- routes
sharing:insertRenderedBlockthroughmodules/bgComposeShareInsert.js. - receives pre-rendered share HTML from
NCSharing.buildHtmlBlock(...). - receives pre-rendered share plain text from
NCSharing.buildPlainTextBlock(...). - requires both render variants as part of the runtime message contract.
- backend custom templates are sanitized in both rendering paths before use; local built-in templates stay on the trusted local render path and are not passed through the backend HTML sanitizer.
- backend custom templates prune empty optional placeholders (
{RIGHTS},{PASSWORD},{EXPIRATIONDATE},{NOTE}) before replacement to reduce orphaned labels/wrappers in arbitrary layouts. - resolves compose mode from
isPlainText+deliveryFormat:- HTML compose mode: inserts source HTML near
<body>. - Plain-text compose mode: prefers the pre-rendered
plainTextblock, normalizes permission markers ([x]/[ ]), compacts permission rows inside explicit add-on-generated rights segments, and frames the block with a fixed 60-character#border. - For HTML editors with plain-text delivery format, inserts an escaped plain-text rendering to preserve deterministic plain-text output.
- HTML compose mode: inserts source HTML near
The language for the generated sharing block can be overridden via:
- options → advanced →
shareBlockLang
Implementation uses:
modules/i18nOverride.jsto translate in a forced locale.
Runtime rules:
customis only offered in the settings UI when the backend endpoint exists.customstays disabled unless the effective backend policy for the respective domain is actuallycustomand provides a template.- Backend templates are only used when the effective language override is
custom. - If
customis selected but the backend template is empty or unavailable, runtime falls back to the local UI-default text block. - Backend-provided rich HTML templates are sanitized client-side with bundled
DOMPurifybefore use. - Backend custom templates use the sanitizer in both rich-HTML rendering and plain-text rendering; local built-in share blocks remain trusted local render output.
- Privileged calendar-editor code does not parse backend HTML via
innerHTML; sanitized markup is imported viaDOMParser+ DOM fragment replacement. - Active UI/runtime paths should avoid legacy
innerHTMLandexecCommand(...)write APIs where ESR-140-compatible DOM/clipboard alternatives exist. - Separate password follow-up dispatch is seat-gated and only available with backend endpoint + active assigned seat.
- Backend attachment-threshold policy uses
attachments_min_size_mbas both value and enable-state: a positive integer enables threshold mode,nulldisables it. - Locked backend attachment-automation policy is enforced in compose runtime, not only in the settings surface.
- Backend email signatures are applied only when
policy.email_signature.email_signature_on_compose=true, a renderedemail_signature_templateexists, andpolicy.email_signature.user_emailmatches the active Thunderbird sender identity email. - For the matching sender identity, enabled compose signature policy also owns the signature slot in replies and forwards: if reply/forward insertion is disabled, Thunderbird/Signature Switch signatures are removed but no backend signature is inserted.
- Non-matching sender identities are left untouched so Thunderbird identity signatures or Signature Switch can continue to manage those identities.
- Share, Talk, and email-signature policies are evaluated per backend policy domain. A backend payload without
policy.email_signaturedisables only central email signatures and shows the backend-update hint; Share/Talk policy remains active when their domains are present. - The signature settings surface stays disabled until the backend endpoint is available, the current user has an active assigned seat, and the
email_signaturepolicy domain exists. Backend/seat hint text reuses the existing policy messages.
Core:
X-NCTALK-TOKEN— Talk room tokenX-NCTALK-URL— full Talk URL
Room settings:
X-NCTALK-LOBBY—TRUE/FALSEX-NCTALK-START— Unix seconds (string)X-NCTALK-EVENT—"event"or"standard"
Lobby timer contract:
X-NCTALK-STARTis the single authoritative source for lobby timer updates.- On calendar upserts,
DTSTARTis parsed through the shared iCal contract and synchronized intoX-NCTALK-STARTwhen the value changed. - If
DTSTARTparsing fails, runtime keeps the currentX-NCTALK-STARTvalue (if present) and logs a contract parse error. - Lobby update is skipped only when no valid
X-NCTALK-STARTis available after metadata merge.
Invitee sync:
X-NCTALK-ADD-USERS—TRUE/FALSEX-NCTALK-ADD-GUESTS—TRUE/FALSEX-NCTALK-ADD-PARTICIPANTS— legacy combined flag (TRUEif either is enabled)
Event conversation binding:
X-NCTALK-OBJECTID— event object identifier used when binding rooms to events
Delegation:
X-NCTALK-DELEGATE— user IDX-NCTALK-DELEGATE-NAME— display labelX-NCTALK-DELEGATED—TRUE/FALSEX-NCTALK-DELEGATE-READY—TRUEwhile pending
Room runtime metadata:
- Key:
nctalkRoomMeta - Used to track lobby start times, delegation status, and other runtime decisions.
Event ↔ token mapping:
- Key:
nctalkEventTokenMap - Used for trusted ownership checks on event deletion and for diagnostics.
- Deletion uses only mappings marked as
source: "x-nctalk". Legacy mappings without a source are ignored fail-closed because older builds could also store tokens from ordinaryLOCATION/URLfields.
Common utility:
debug:log— structured log forwarding (debug-gated)passwordPolicy:fetch— returns active password policy endpoints + min lengthpasswordPolicy:generate— server-side password generation
Options:
options:testConnectionoptions:loginFlowStartoptions:loginFlowComplete
Talk wizard:
talk:initDialogtalk:getEventSnapshottalk:getSystemAddressbookStatustalk:createRoomtalk:searchUserstalk:applyMetadatatalk:applyEventFieldstalk:trackRoomtalk:registerCleanup
Sharing wizard:
sharing:insertRenderedBlocksharing:armComposeShareCleanupsharing:armWizardRemoteCleanupsharing:clearWizardRemoteCleanupsharing:getLaunchContextsharing:resolveAttachmentPromptsharing:checkAttachmentAutomationAllowedsharing:registerSeparatePasswordDispatch
Note:
- Talk-related runtime messaging uses the
talk:*namespace only.
Most messages return one of:
{ ok: true, ... }{ ok: false, error: string, ... }
Always ensure errors are logged (reviewer requirement: no silent failures).
This add-on uses Nextcloud APIs such as:
- Core capabilities:
/ocs/v2.php/cloud/capabilities
- Talk capabilities and room operations:
/ocs/v2.php/apps/spreed/api/v4/...
- Password policy:
/ocs/v2.php/apps/password_policy/api/v1/generate
- Files sharing:
/ocs/v2.php/apps/files_sharing/api/v1/shares
- DAV:
remote.php/dav/...
- Addressbook (system addressbook export):
remote.php/dav/addressbooks/.../?export
All endpoint interaction lives in the shared modules (modules/ocs.js, modules/nccore.js, modules/talkcore.js, modules/ncSharing.js).
Before you ship:
- Bump
manifest.jsonversion. - Update
docs/ATN_REVIEW_NOTES.mdand README “What’s new”. - Run the manual tests (Talk dialog + tab editor, sharing wizard, event move/delete, delegation, invitee sync).
- Run parser contract checks:
node tools/check-review-clean.jsnode tools/ical-contract-check.jsnode tools/share-plaintext-contract-check.jsnode tools/i18n-locale-parity-check.js
- Package the XPI with correct root structure.
- Sanity check:
- add-on installs on ESR 140.*
- button is present in dialog + tab editor by default
- no console spam in non-debug mode
Common symptoms:
-
Button missing in dialog editor
- Verify
experiments/ncCalToolbaris registered inmanifest.json. - Verify
calendar_item_action+calendarItemActionare registered inmanifest.json. - Check
[ncCalToolbar]logs for calendarItemAction binding/context errors.
- Verify
-
Wizard opens but writes nothing
- Verify
contextIdis present in the wizard URL. - Verify
browser.ncCalToolbar.updateCurrentis available and receives a valideditorId.
- Verify
-
Invitees not added
- Invitee sync happens after the event is saved (calendar upsert), not immediately.
- Check that
X-NCTALK-ADD-USERS/X-NCTALK-ADD-GUESTSare set and persisted.
-
Room deletion fails with 403
- Can happen after delegation (moderation transferred). Handle gracefully.
Before changing experiments or calendar integration, read:
docs/ATN_REVIEW_CHECKLIST_INTERNAL.md
Key rules:
- Do not modify
experiments/calendar/**. - Keep experiments minimal, deterministic, and auditable.
- No trial-and-error code paths.
- No broad window/tab monitoring; target only required windows via window listeners.