5.2 KiB
Analytics in TidGi Desktop
This document explains how analytics works in TidGi Desktop, what is intentionally tracked, what is intentionally blocked, and how plugin authors should integrate with the analytics service.
Goals
TidGi uses analytics to understand coarse product usage without collecting user content.
The current design goals are:
- Keep all network delivery in the Electron main process
- Never expose the analytics API key to renderer or plugin code
- Track only coarse, product-level behavior
- Reject free-form content, note text, file paths, URLs, tokens, and other sensitive payloads
- Give plugin authors a stable way to emit custom product events without bypassing privacy guardrails
Architecture
TidGi does not initialize a browser-side analytics SDK.
Instead:
- Renderer code and TiddlyWiki plugins call the TidGi analytics service through the existing IPC proxy layer
- The analytics service runs in the main process
- The main process sends events to Rybbit over HTTP
Relevant files:
src/services/analytics/interface.tssrc/services/analytics/index.tssrc/preload/common/services.tssrc/preload/common/exportServices.tssrc/services/wiki/plugin/ipcSyncAdaptor/Startup/mount-tidgi-service.ts
Delivery model
- Analytics is enabled only when all of the following are true:
analyticsEnabledpreference istrueanalyticsHostis configuredanalyticsSiteIdis configured- a main-process-only analytics API key is configured
- Unsent events may be queued temporarily in memory
- The queue is dropped immediately when the user disables analytics
- The first-run disclosure is tracked separately from normal product events
Privacy constraints
TidGi analytics must never contain:
- note titles or note bodies
- workspace names
- filesystem paths
- raw URLs
- access tokens, OAuth codes, cookies, or API keys
- free-form user text copied from the UI
If a proposed event depends on any of the above, do not add it to analytics.
Built-in events
The application currently emits built-in events such as:
app.launchedanalytics.disclosure_dismissedworkspace.createdworkspace.activatedpreferences.analytics_updatedsettings.openedtheme.changeddeep_link.openedsync.triggeredsync.completedsync.failedupdater.check_startedupdater.update_availableupdater.update_not_availableupdater.check_failederror.report_requested
Built-in events use an allowlist. For each built-in event, only explicitly approved property keys are retained.
That allowlist lives in src/services/analytics/index.ts.
Plugin-defined custom events
Plugin authors must not emit arbitrary event names through the low-level built-in event contract.
Instead, plugins should call:
await window.service.analytics.trackPluginEvent(pluginId, eventName, properties);
or inside a TiddlyWiki plugin:
await $tw.tidgi.service.analytics.trackPluginEvent(pluginId, eventName, properties);
Final event name format
The service converts plugin calls into this final event name:
plugin.<pluginId>.<eventName>
Example:
await window.service.analytics.trackPluginEvent('kanban-board', 'card_created', {
source: 'toolbar',
has_due_date: true,
});
Emits:
plugin.kanban-board.card_created
Validation rules
Plugin event names are intentionally restricted.
pluginIdmust match:^[a-z0-9]+(?:[-_][a-z0-9]+)*$eventNamemust match:^[a-z0-9]+(?:[-_][a-z0-9]+)*$- Property keys must match:
^[a-z][a-z0-9_]{0,39}$ - Property values must be
string | number | boolean - String values are truncated to 120 characters
If pluginId or eventName is invalid, the event is rejected.
If all properties are invalid, the event is still allowed to be sent without properties.
What plugin authors should track
Good examples:
- whether a plugin feature was used
- which plugin surface triggered an action (
toolbar,context_menu,shortcut) - coarse booleans like
has_filter,used_template,has_due_date - bounded enums represented as short strings
Bad examples:
- card title text
- search query text
- raw wiki URL
- tiddler title
- workspace name
- exported content
When to add a built-in event instead of a plugin event
Use a built-in event when the behavior is part of TidGi core product behavior and should be governed by a fixed property allowlist in source control.
Use trackPluginEvent() when the event belongs to a plugin or extension and needs a stable but bounded custom namespace.
Rybbit usage in TidGi today
Today TidGi only uses Rybbit's event ingestion path for coarse custom events.
The current implementation sends HTTP requests to the configured Rybbit host using the server-side API key stored only in the main process.
Verification checklist for analytics changes
When editing analytics behavior:
- Confirm the event does not include content or identifiers from user data
- If it is a built-in event, update the property allowlist
- If it is a plugin event, prefer
trackPluginEvent()instead of widening built-in types - Run
pnpm check - Run eslint on changed files
- Update this document and
docs/TidGiServiceAPI.mdif the callable surface changed