Skip to content
/ loom Public

Commit c2ccc6d

Browse files
ghuntleyclaude
andcommitted
Add analytics tracking pattern documentation to specs
- Add section 9 to analytics-system.md documenting loom-web tracking patterns: helper functions, signatures, usage examples, property conventions, and navigation tracking - Add section 12 to loom-web.md documenting analytics integration: setup with AnalyticsProvider, tracking helper functions, usage patterns, event naming conventions, and identity management - Renumber subsequent sections in both files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4aa2dfb commit c2ccc6d

File tree

2 files changed

+258
-26
lines changed

2 files changed

+258
-26
lines changed

specs/analytics-system.md

Lines changed: 142 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,133 @@ Both SDKs use shared HTTP client libraries:
565565

566566
---
567567

568-
## 9. Database Schema
568+
## 9. loom-web Tracking Patterns
569+
570+
The loom-web application uses helper functions to ensure consistent event naming and properties across all tracked interactions.
571+
572+
### 9.1 Tracking Helper Functions
573+
574+
Import from `$lib/analytics`:
575+
576+
```typescript
577+
import {
578+
capture,
579+
trackLinkClick,
580+
trackButtonClick,
581+
trackFormSubmit,
582+
trackModalOpen,
583+
trackModalClose,
584+
trackFilterChange,
585+
trackAction
586+
} from '$lib/analytics';
587+
```
588+
589+
### 9.2 Function Signatures
590+
591+
| Function | Parameters | Event Name |
592+
|----------|------------|------------|
593+
| `trackLinkClick` | `linkName: string, href: string, properties?` | `link_clicked` |
594+
| `trackButtonClick` | `buttonName: string, properties?` | `button_clicked` |
595+
| `trackFormSubmit` | `formName: string, properties?` | `form_submitted` |
596+
| `trackModalOpen` | `modalName: string, properties?` | `modal_opened` |
597+
| `trackModalClose` | `modalName: string, properties?` | `modal_closed` |
598+
| `trackFilterChange` | `filterName: string, value: unknown, properties?` | `filter_changed` |
599+
| `trackAction` | `action: string, resourceType: string, resourceId: string, properties?` | `action_performed` |
600+
601+
### 9.3 Usage Examples
602+
603+
**Link tracking:**
604+
```svelte
605+
<a href="/threads" onclick={() => trackLinkClick('nav_threads', '/threads')}>
606+
Threads
607+
</a>
608+
```
609+
610+
**Button tracking:**
611+
```svelte
612+
<button onclick={() => { trackButtonClick('create_weaver'); handleCreate(); }}>
613+
New Weaver
614+
</button>
615+
```
616+
617+
**Modal tracking:**
618+
```svelte
619+
<script>
620+
function openDeleteModal(weaver: Weaver) {
621+
trackModalOpen('delete_weaver', { weaver_id: weaver.id });
622+
deleteModal = weaver;
623+
}
624+
625+
function closeDeleteModal() {
626+
trackModalClose('delete_weaver');
627+
deleteModal = null;
628+
}
629+
</script>
630+
```
631+
632+
**Filter tracking:**
633+
```svelte
634+
<select onchange={(e) => {
635+
const value = e.currentTarget.value;
636+
trackFilterChange('org_filter', value);
637+
selectedOrg = value;
638+
}}>
639+
```
640+
641+
**Action tracking:**
642+
```svelte
643+
<button onclick={() => {
644+
trackAction('resolve', 'issue', issue.id);
645+
handleResolve();
646+
}}>
647+
Resolve
648+
</button>
649+
```
650+
651+
**Form tracking:**
652+
```svelte
653+
<form onsubmit={(e) => {
654+
trackFormSubmit('create_monitor', { org_id: selectedOrg });
655+
handleSubmit(e);
656+
}}>
657+
```
658+
659+
### 9.4 Event Property Conventions
660+
661+
| Property | Description | Example |
662+
|----------|-------------|---------|
663+
| `link_name` | Descriptive link identifier | `'project_card'`, `'back_button'` |
664+
| `href` | Destination URL | `'/crashes/proj-123'` |
665+
| `button_name` | Descriptive button identifier | `'create_weaver'`, `'delete_monitor'` |
666+
| `form_name` | Form identifier | `'create_org'`, `'profile_settings'` |
667+
| `modal_name` | Modal identifier | `'delete_confirmation'`, `'logs_viewer'` |
668+
| `filter_name` | Filter identifier | `'status'`, `'time_range'`, `'org'` |
669+
| `filter_value` | Selected filter value | `'active'`, `'7d'`, `'org-123'` |
670+
| `action` | Action performed | `'resolve'`, `'delete'`, `'pause'` |
671+
| `resource_type` | Type of resource | `'issue'`, `'monitor'`, `'weaver'` |
672+
| `resource_id` | Resource identifier | `'issue-abc'`, `'mon-xyz'` |
673+
674+
### 9.5 Navigation Tracking
675+
676+
Navigation paths are automatically tracked via the `nav_clicked` event in the app layout:
677+
678+
```svelte
679+
<script>
680+
function trackNavClick(item: string, path: string) {
681+
capture('nav_clicked', { item, path });
682+
}
683+
</script>
684+
685+
<nav>
686+
<a href="/threads" onclick={() => trackNavClick('threads', '/threads')}>
687+
Threads
688+
</a>
689+
</nav>
690+
```
691+
692+
---
693+
694+
## 10. Database Schema
569695

570696
### 9.1 Migration: `XXX_analytics.sql`
571697

@@ -652,16 +778,16 @@ CREATE INDEX idx_analytics_api_keys_key_hash ON analytics_api_keys(key_hash);
652778

653779
---
654780

655-
## 10. API Key Management
781+
## 11. API Key Management
656782

657-
### 10.1 Key Types
783+
### 11.1 Key Types
658784

659785
| Type | Prefix | Use Case | Capabilities |
660786
|------|--------|----------|--------------|
661787
| Write | `loom_analytics_write_` | Client-side, public | Capture, identify, alias |
662788
| ReadWrite | `loom_analytics_rw_` | Server-side, secret | All write + query/export |
663789

664-
### 10.2 Key Format
790+
### 11.2 Key Format
665791

666792
```
667793
loom_analytics_{type}_{random}
@@ -671,7 +797,7 @@ loom_analytics_write_7a3b9f2e1c4d8a5b6e0f3c2d1a4b5c6d7e8f9a0b
671797
loom_analytics_rw_8b4c0g3f2d5e9a6c7f1g4d3e2b5a6c7d8e9f0a1b
672798
```
673799

674-
### 10.3 Authentication
800+
### 11.3 Authentication
675801

676802
SDK requests include:
677803

@@ -688,9 +814,9 @@ Server validates:
688814

689815
---
690816

691-
## 11. Configuration
817+
## 12. Configuration
692818

693-
### 11.1 Environment Variables
819+
### 12.1 Environment Variables
694820

695821
| Variable | Type | Description | Default |
696822
|----------|------|-------------|---------|
@@ -701,7 +827,7 @@ Server validates:
701827

702828
---
703829

704-
## 12. Audit Events
830+
## 13. Audit Events
705831

706832
Analytics operations logged via `loom-server-audit`:
707833

@@ -714,17 +840,17 @@ Analytics operations logged via `loom-server-audit`:
714840

715841
---
716842

717-
## 13. Permissions
843+
## 14. Permissions
718844

719-
### 13.1 API Key Management
845+
### 14.1 API Key Management
720846

721847
| Action | Org Admin | Org Member | Super Admin |
722848
|--------|-----------|------------|-------------|
723849
| List API keys ||| ✓ (all) |
724850
| Create API key ||||
725851
| Revoke API key ||||
726852

727-
### 13.2 Query Access
853+
### 14.2 Query Access
728854

729855
| Action | Write Key | ReadWrite Key |
730856
|--------|-----------|---------------|
@@ -736,9 +862,9 @@ Analytics operations logged via `loom-server-audit`:
736862

737863
---
738864

739-
## 14. Security Considerations
865+
## 15. Security Considerations
740866

741-
### 14.1 IP Address Handling
867+
### 15.1 IP Address Handling
742868

743869
IP addresses are sensitive data. Use `loom-secret::Secret`:
744870

@@ -756,14 +882,14 @@ This ensures:
756882
- Explicit `.expose()` required to access
757883
- Serialization can be controlled
758884

759-
### 14.2 Write Key Safety
885+
### 15.2 Write Key Safety
760886

761887
Write-only keys are safe for client-side because:
762888
- Cannot read any data back
763889
- Cannot query other users
764890
- Scoped to single org
765891

766-
### 14.3 Event Validation
892+
### 15.3 Event Validation
767893

768894
Validate incoming events:
769895
- `event_name`: Max 200 chars, alphanumeric + underscore + `$` prefix
@@ -772,7 +898,7 @@ Validate incoming events:
772898

773899
---
774900

775-
## 15. Rust Dependencies
901+
## 16. Rust Dependencies
776902

777903
```toml
778904
# loom-analytics-core

0 commit comments

Comments
 (0)