Data Models & Protobuf Schemas
MongoDB Collections
events (Time-Series Collection)
{
_id: ObjectId,
eventId: string, // UUID
appId: ObjectId,
profileId: ObjectId,
sessionId: string,
timestampMs: number,
eventType: string, // page_view, click, scroll, hover, custom
pageUrl: string,
elementTid: string, // data-t value
metadata: Record<string, string>,
pageContext: {
path: string,
title: string,
referrer: string,
viewportWidth: number,
viewportHeight: number,
loadTimeMs: number,
},
elementContext: {
tag: string,
text: string,
x: number,
y: number,
scrollDepthPct: number,
},
}
// Indexes: { appId, timestampMs }, { appId, eventType }, { sessionId }
// TTL: 90 days (configurable per app)heatmap_events (Time-Series Collection)
{
_id: ObjectId,
appId: ObjectId,
profileId: ObjectId,
sessionId: string,
pageUrl: string,
timestampMs: number,
x: number,
y: number,
viewportWidth: number,
viewportHeight: number,
}
// Indexes: { appId, pageUrl, timestampMs }
// TTL: 30 dayserrors
{
_id: ObjectId,
errorId: string,
appId: ObjectId,
groupFingerprint: string, // for grouping
profileId: ObjectId,
sessionId: string,
timestampMs: number,
errorType: string, // runtime, unhandled_rejection, network, render
message: string,
stackTrace: string,
sourceFile: string,
line: number,
column: number,
severity: 'info' | 'warning' | 'error' | 'fatal',
userComment: string | null,
tags: Record<string, string>,
breadcrumbs: Array<{
timestampMs: number,
category: string,
message: string,
data: Record<string, string>,
}>,
pageContext: { /* same as events */ },
browserContext: {
userAgent: string,
browser: string,
browserVersion: string,
os: string,
deviceType: string,
screenWidth: number,
screenHeight: number,
},
}
// Indexes: { appId, groupFingerprint }, { appId, timestampMs }, { appId, severity }error_groups
{
_id: ObjectId,
appId: ObjectId,
fingerprint: string,
message: string, // representative message
firstSeen: Date,
lastSeen: Date,
occurrenceCount: number,
affectedProfiles: number,
status: 'new' | 'investigating' | 'resolved' | 'ignored',
assignee: ObjectId | null,
comments: Array<{
authorId: ObjectId,
text: string,
createdAt: Date,
}>,
}feature_flags
{
_id: ObjectId,
appId: ObjectId,
key: string,
description: string,
type: 'boolean' | 'string' | 'number' | 'json',
defaultValue: any,
enabled: boolean,
rules: Array<{
ruleId: string,
priority: number,
conditions: Array<{
attribute: string,
operator: string,
values: string[],
}>,
value: any,
rolloutPercentage: number, // 0-100
experimentId: string | null,
}>,
createdAt: Date,
updatedAt: Date,
createdBy: ObjectId,
}
// Indexes: { appId, key } (unique)user_flag_sets (Pre-Computed)
{
_id: ObjectId,
profileId: ObjectId,
appId: ObjectId,
flags: Record<string, any>, // { "new-checkout-ui": true, "checkout-limit": 100 }
computedAt: Date,
}
// Indexes: { profileId, appId } (unique)translations
{
_id: ObjectId,
appId: ObjectId,
locale: string, // e.g. "fr", "de", "es"
key: string, // e.g. "checkout.button"
sourceText: string, // original (source locale)
translatedText: string,
status: 'pending' | 'suggested' | 'approved' | 'overridden',
suggestedText: string | null, // LLM suggestion
translatedBy: ObjectId | null, // who approved/overrode
updatedAt: Date,
}
// Indexes: { appId, locale, key } (unique)translation_config
{
_id: ObjectId,
appId: ObjectId,
sourceLocale: string,
targetLocales: string[],
sourceVersion: string, // last seen version
webhookSecret: string,
}feedback
{
_id: ObjectId,
appId: ObjectId,
profileId: ObjectId,
type: 'bug' | 'feature' | 'comment' | 'praise',
text: string,
screenshotUrl: string | null,
pageUrl: string,
browserContext: { /* same as errors */ },
status: 'new' | 'acknowledged' | 'in_progress' | 'resolved' | 'wont_fix',
upvotes: number,
upvotedBy: ObjectId[],
tags: string[],
thread: Array<{
authorId: ObjectId,
authorType: 'user' | 'developer',
text: string,
createdAt: Date,
}>,
createdAt: Date,
updatedAt: Date,
}
// Indexes: { appId, status }, { appId, type }, { appId, upvotes }themes
{
_id: ObjectId,
authorProfileId: ObjectId,
name: string,
description: string,
isPublic: boolean,
isOfficial: boolean,
category: string[], // ['dark', 'minimal', 'professional']
installCount: number,
rating: { avg: number, count: number },
definition: AdvancedTheme, // Full theme definition (see Theming section)
createdAt: Date,
updatedAt: Date,
}
// Indexes: { isPublic, installCount }, { authorProfileId }app_registrations
{
_id: ObjectId,
appId: string,
name: string,
ownerProfileId: ObjectId,
apiKey: string, // exp_xxxxxxxxxxxx (hashed in DB)
apiKeyPrefix: string, // exp_xxxx (for display)
config: {
eventRetentionDays: number,
heatmapRetentionDays: number,
maxEventsPerDay: number,
},
createdAt: Date,
}Protobuf Schemas
All .proto files live in shellapps-js/packages/experience-proto/.
events.proto
syntax = "proto3";
package shellapps.experience;
message TrackEvent {
string event_id = 1;
string app_id = 2;
string profile_id = 3;
string session_id = 4;
int64 timestamp_ms = 5;
string event_type = 6;
string page_url = 7;
string element_tid = 8;
map<string, string> metadata = 9;
PageContext page_context = 10;
ElementContext element_context = 11;
}
message PageContext {
string path = 1;
string title = 2;
string referrer = 3;
int32 viewport_width = 4;
int32 viewport_height = 5;
int64 load_time_ms = 6;
}
message ElementContext {
string tag = 1;
string text = 2;
int32 x = 3;
int32 y = 4;
int32 scroll_depth_pct = 5;
}
message HeatmapEvent {
string app_id = 1;
string profile_id = 2;
string session_id = 3;
string page_url = 4;
int64 timestamp_ms = 5;
int32 x = 6;
int32 y = 7;
int32 viewport_width = 8;
int32 viewport_height = 9;
}
message EventBatch {
repeated TrackEvent events = 1;
repeated HeatmapEvent heatmap_events = 2;
}
message IngestResponse {
bool success = 1;
int32 events_accepted = 2;
int32 events_rejected = 3;
}errors.proto
syntax = "proto3";
package shellapps.experience;
message ErrorEvent {
string error_id = 1;
string app_id = 2;
string profile_id = 3;
string session_id = 4;
int64 timestamp_ms = 5;
string error_type = 6;
string message = 7;
string stack_trace = 8;
string source_file = 9;
int32 line = 10;
int32 column = 11;
PageContext page_context = 12;
BrowserContext browser_context = 13;
string user_comment = 14;
Severity severity = 15;
map<string, string> tags = 16;
repeated Breadcrumb breadcrumbs = 17;
}
enum Severity {
INFO = 0;
WARNING = 1;
ERROR = 2;
FATAL = 3;
}
message Breadcrumb {
int64 timestamp_ms = 1;
string category = 2;
string message = 3;
map<string, string> data = 4;
}
message BrowserContext {
string user_agent = 1;
string browser = 2;
string browser_version = 3;
string os = 4;
string device_type = 5;
int32 screen_width = 6;
int32 screen_height = 7;
}
message ErrorBatch {
repeated ErrorEvent errors = 1;
}flags.proto
syntax = "proto3";
package shellapps.experience;
message FeatureFlag {
string flag_id = 1;
string app_id = 2;
string key = 3;
string description = 4;
FlagType type = 5;
FlagValue default_value = 6;
repeated FlagRule rules = 7;
bool enabled = 8;
int64 created_at = 9;
int64 updated_at = 10;
}
enum FlagType {
BOOLEAN = 0;
STRING = 1;
NUMBER = 2;
JSON = 3;
}
message FlagValue {
oneof value {
bool bool_value = 1;
string string_value = 2;
double number_value = 3;
string json_value = 4;
}
}
message FlagRule {
string rule_id = 1;
int32 priority = 2;
repeated FlagCondition conditions = 3;
FlagValue value = 4;
int32 rollout_percentage = 5;
string experiment_id = 6;
}
message FlagCondition {
string attribute = 1;
string operator = 2;
repeated string values = 3;
}
message UserFlagSet {
string profile_id = 1;
string app_id = 2;
map<string, FlagValue> flags = 3;
int64 computed_at = 4;
}