Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b1e9638
feat(sentry): initialize Sentry SDK with environment-based sampling
Just-Insane Mar 15, 2026
a84c227
feat(sentry): developer settings panel and debug log attachment
Just-Insane Mar 15, 2026
1a19c05
feat(sentry): crash page and bug report Sentry integration
Just-Insane Mar 15, 2026
df2693b
feat(sentry): performance instrumentation across sync and messaging
Just-Insane Mar 15, 2026
d9bffa6
docs(sentry): integration guide and CI environment configuration
Just-Insane Mar 15, 2026
7766aa8
feat(sentry): expand settings tags and add matrix client context
Just-Insane Mar 15, 2026
cb5686f
feat(sentry): component names, login span, scoped boundaries, structu…
Just-Insane Mar 15, 2026
ff7868b
feat(sentry): spans and metrics for sliding sync lifecycle
Just-Insane Mar 15, 2026
580ac53
feat(sentry): orphan/leak/long-task monitoring
Just-Insane Mar 15, 2026
47c6462
feat(sentry): improve event enrichment
Just-Insane Mar 15, 2026
d5f61b3
feat(sentry): monitor sync errors, forced logouts, and key backup fai…
Just-Insane Mar 15, 2026
5af8399
feat(sentry): UTD failure reason, store wipe, send failures, login fa…
Just-Insane Mar 15, 2026
103b904
feat(sentry): capture client load/start failures, background client e…
Just-Insane Mar 15, 2026
ce3aa2f
chore(sentry): fix lint errors in PR files; expand privacy and integr…
Just-Insane Mar 15, 2026
df88263
fix(sentry): move syncDuration declaration before use to fix TS use-b…
Just-Insane Mar 15, 2026
99bd215
feat: add Sentry→GitHub Issues triage for PR preview builds
Just-Insane Mar 15, 2026
870a4b8
fix: lint and formatting fixes across modified files
Just-Insane Mar 15, 2026
55fbc0c
fix: scrub room IDs and media paths from Sentry spans and breadcrumbs
Just-Insane Mar 15, 2026
28c011d
fix: scrub user IDs from profile URLs and key backup paths in Sentry …
Just-Insane Mar 15, 2026
3ca6314
privacy: comprehensive scrubbing of Matrix IDs across Sentry
Just-Insane Mar 15, 2026
f0b5619
docs: add self-hosting with Docker section to Sentry integration guide
Just-Insane Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/error_page_with_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: minor
---

added error page making it easier to report errors when they occur in the field
5 changes: 5 additions & 0 deletions .changeset/feat-sentry-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'default': minor
---

Add Sentry integration for error tracking and bug reporting
10 changes: 8 additions & 2 deletions .github/actions/prepare-tofu/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ runs:
steps:
- name: Setup app and build
uses: ./.github/actions/setup
env:
VITE_IS_RELEASE_TAG: ${{ inputs.is_release_tag }}
with:
build: 'true'
env:
VITE_IS_RELEASE_TAG: ${{ inputs.is_release_tag }}
VITE_SENTRY_DSN: ${{ env.VITE_SENTRY_DSN }}
VITE_SENTRY_ENVIRONMENT: ${{ env.VITE_SENTRY_ENVIRONMENT }}
VITE_APP_VERSION: ${{ env.VITE_APP_VERSION }}
SENTRY_AUTH_TOKEN: ${{ env.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ env.SENTRY_ORG }}
SENTRY_PROJECT: ${{ env.SENTRY_PROJECT }}

- name: Setup OpenTofu
uses: opentofu/setup-opentofu@9d84900f3238fab8cd84ce47d658d25dd008be2f # v1.0.8
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/cloudflare-web-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ jobs:
uses: ./.github/actions/prepare-tofu
with:
is_release_tag: ${{ startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.git_tag != '') }}
env:
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
VITE_SENTRY_ENVIRONMENT: production
VITE_APP_VERSION: ${{ github.ref_name }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Comment PR plan
uses: dflook/tofu-plan@3f5dc358343fb58cd60f83b019e810315aa8258f # v2.2.3
Expand All @@ -82,6 +89,13 @@ jobs:
uses: ./.github/actions/prepare-tofu
with:
is_release_tag: ${{ startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.git_tag != '') }}
env:
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
VITE_SENTRY_ENVIRONMENT: production
VITE_APP_VERSION: ${{ github.ref_name }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Plan infrastructure
run: tofu plan -input=false -no-color
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/cloudflare-web-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ jobs:
echo EOF
} >> "$GITHUB_OUTPUT"

- name: Set Sentry build environment for PR preview
if: github.event_name == 'pull_request'
env:
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
shell: bash
run: |
echo "VITE_SENTRY_DSN=$VITE_SENTRY_DSN" >> "$GITHUB_ENV"
echo "VITE_SENTRY_ENVIRONMENT=preview" >> "$GITHUB_ENV"
echo "VITE_SENTRY_PR=${{ github.event.pull_request.number }}" >> "$GITHUB_ENV"
echo "SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN" >> "$GITHUB_ENV"
echo "SENTRY_ORG=$SENTRY_ORG" >> "$GITHUB_ENV"
echo "SENTRY_PROJECT=$SENTRY_PROJECT" >> "$GITHUB_ENV"

- name: Setup app and build
uses: ./.github/actions/setup
with:
Expand Down
231 changes: 231 additions & 0 deletions .github/workflows/sentry-preview-issues.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
name: Sentry Preview Error Triage

on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'src/**'
- 'index.html'
- 'package.json'
- 'vite.config.ts'
- 'tsconfig.json'
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to triage'
required: true
type: number

jobs:
triage:
# Only run for PRs from the same repo (not forks) or manual dispatch
if: >
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository) ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: Triage Sentry preview errors
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const sentryToken = process.env.SENTRY_AUTH_TOKEN;
const sentryOrg = process.env.SENTRY_ORG;
const sentryProject = process.env.SENTRY_PROJECT;
const prNumber = Number(process.env.PR_NUMBER);
if (!prNumber) {
core.info('No PR number available — skipping triage.');
return;
}
if (!sentryToken || !sentryOrg || !sentryProject) {
core.warning('Sentry credentials not configured — skipping triage.');
return;
}
const COMMENT_MARKER = '<!-- sentry-preview-triage -->';
const { owner, repo } = context.repo;
// Create a label if it doesn't already exist
async function ensureLabel(name, description, color) {
try {
await github.rest.issues.getLabel({ owner, repo, name });
} catch {
try {
await github.rest.issues.createLabel({ owner, repo, name, description, color });
} catch (err) {
core.warning(`Could not create label "${name}": ${err.message}`);
}
}
}
// Find an existing GitHub issue that tracks a given Sentry issue ID
async function findExistingGhIssue(sentryIssueId) {
const marker = `sentry-id:${sentryIssueId}`;
const result = await github.rest.search.issuesAndPullRequests({
q: `repo:${owner}/${repo} is:issue label:sentry-preview "${marker}" in:body`,
});
return result.data.total_count > 0 ? result.data.items[0] : null;
}
// Create or update the sticky PR comment with the triage summary table
async function upsertPrComment(rows) {
const now = new Date().toUTCString().replace(':00 GMT', ' UTC');
let body;
if (rows.length === 0) {
body = [
COMMENT_MARKER,
'## Sentry Preview Error Triage',
'',
`No Sentry errors found for this PR's preview deployment as of ${now}.`,
'',
'_This comment updates automatically after each push._',
].join('\n');
} else {
const tableRows = rows.map(
(r) =>
`| [${r.title.slice(0, 70)}](${r.permalink}) | ${r.count} | ${new Date(r.firstSeen).toLocaleDateString()} | #${r.ghIssueNumber} |`
);
body = [
COMMENT_MARKER,
'## Sentry Preview Error Triage',
'',
`**${rows.length} error type(s)** detected in this PR's preview deployment:`,
'',
'| Error | Events | First seen | Issue |',
'| ----- | ------ | ---------- | ----- |',
...tableRows,
'',
`_Last checked: ${now}. Exclude these from your issues view with \`-label:sentry-preview\`._`,
].join('\n');
}
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: prNumber,
});
const existing = comments.find(
(c) => c.user.type === 'Bot' && c.body.includes(COMMENT_MARKER)
);
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body,
});
}
}
// Query Sentry for unresolved issues tagged with this PR number in the preview env
const query = encodeURIComponent(`is:unresolved pr:${prNumber}`);
const sentryUrl =
`https://sentry.io/api/0/projects/${sentryOrg}/${sentryProject}/issues/` +
`?query=${query}&environment=preview&limit=100`;
let sentryIssues;
try {
const resp = await fetch(sentryUrl, {
headers: { Authorization: `Bearer ${sentryToken}` },
});
if (!resp.ok) {
const msg = await resp.text();
core.warning(`Sentry API returned ${resp.status}: ${msg.slice(0, 200)}`);
return;
}
sentryIssues = await resp.json();
} catch (err) {
core.warning(`Sentry API unreachable: ${err.message}`);
return;
}
if (!Array.isArray(sentryIssues) || sentryIssues.length === 0) {
await upsertPrComment([]);
return;
}
// Ensure the shared and PR-specific labels exist
await ensureLabel('sentry-preview', 'Automated Sentry preview error', 'e4e669');
await ensureLabel(`pr-${prNumber}`, `Preview errors from PR #${prNumber}`, 'fbca04');
const rows = [];
for (const issue of sentryIssues) {
const {
id: sentryId,
title,
culprit,
permalink,
count,
userCount,
firstSeen,
lastSeen,
} = issue;
const displayTitle = (title || culprit || 'Unknown error').trim();
const sentryMarker = `sentry-id:${sentryId}`;
const existing = await findExistingGhIssue(sentryId);
let ghIssueNumber;
if (existing) {
ghIssueNumber = existing.number;
// Reopen if it was closed (e.g. after a previous fix that regressed)
if (existing.state === 'closed') {
await github.rest.issues.update({
owner,
repo,
issue_number: ghIssueNumber,
state: 'open',
});
core.info(`Reopened GH issue #${ghIssueNumber} for Sentry issue ${sentryId}`);
}
} else {
const issueBody = [
`<!-- ${sentryMarker} -->`,
`## Sentry Error — PR #${prNumber} Preview`,
'',
`**Error:** [${displayTitle}](${permalink})`,
`**First seen:** ${new Date(firstSeen).toUTCString()}`,
`**Last seen:** ${new Date(lastSeen).toUTCString()}`,
`**Events:** ${count} | **Affected users:** ${userCount}`,
'',
`This issue was automatically created from a Sentry error detected in the preview deployment for PR #${prNumber}.`,
'',
'> [!NOTE]',
'> To exclude automated preview issues from your issues view, filter with: `-label:sentry-preview`',
].join('\n');
const created = await github.rest.issues.create({
owner,
repo,
title: `[Sentry] ${displayTitle.slice(0, 120)}`,
body: issueBody,
labels: ['sentry-preview', `pr-${prNumber}`],
});
ghIssueNumber = created.data.number;
core.info(`Created GH issue #${ghIssueNumber} for Sentry issue ${sentryId}`);
}
rows.push({ title: displayTitle, permalink, count, firstSeen, ghIssueNumber });
}
await upsertPrComment(rows);
core.info(`Triage complete: ${rows.length} Sentry issue(s) processed for PR #${prNumber}.`);
4 changes: 3 additions & 1 deletion Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@
}

try_files {path} /index.html
}

# Required for Sentry browser profiling (JS Self-Profiling API)
header Document-Policy "js-profiling"
3 changes: 3 additions & 0 deletions contrib/nginx/cinny.domain.tld.conf
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ server {
location / {
root /opt/cinny/dist/;

# Required for Sentry browser profiling (JS Self-Profiling API)
add_header Document-Policy "js-profiling" always;

rewrite ^/config.json$ /config.json break;
rewrite ^/manifest.json$ /manifest.json break;

Expand Down
Loading
Loading