ReviewOS

also looking at this

stacks/ts-analytics

Migrate x-text/:text bindings to {⁠{ }} interpolation (stx 0.2.72)

#120
Merged glennmichael123 wants to merge chore/migrate-text-to-mustache into main
31 files +179 -171
bun.lockmodified+22-12
Changes to bun.lock
@@ -7,9 +7,9 @@
77 "dependencies": {
88 "@cwcss/crosswind": "^0.2.6",
99 "@stacksjs/bun-router": "^0.0.16",
10 "@stacksjs/components": "^0.2.67",
11 "@stacksjs/stx": "^0.2.70",
12 "bun-plugin-stx": "^0.2.70",
10 "@stacksjs/components": "^0.2.72",
11 "@stacksjs/stx": "^0.2.72",
12 "bun-plugin-stx": "^0.2.72",
1313 "bun-query-builder": "^0.1.13",
1414 "dynamodb-tooling": "^0.3.2",
1515 "ts-countries": "^0.1.2",
@@ -41,9 +41,9 @@
4141
4242 "@stacksjs/clarity": ["@stacksjs/clarity@0.3.28", "", { "dependencies": { "bunfig": "^0.15.6" }, "bin": { "clarity": "./dist/bin/cli.js" } }, "sha512-SElzGeDewEbp0c4C8PTfdvi/Xtr1HGAjG6b0prHvkVxyYGM+2R0BFU0otUmy+DbR7wsfZ4rNU3YN1+JK/CzoTw=="],
4343
44 "@stacksjs/components": ["@stacksjs/components@0.2.70", "", { "dependencies": { "@cwcss/crosswind": "^0.2.0", "@stacksjs/stx": "0.2.70", "bun-plugin-stx": "0.2.70", "ts-syntax-highlighter": "^0.2.1" } }, "sha512-1yDmqRwKxNY073Rw2RiESWMHplXNP6ITrERquDjcCNCoS3vPqHsnItrJ9XEuYOnKtfAHNEhIVuGM738d1Km/PQ=="],
44 "@stacksjs/components": ["@stacksjs/components@0.2.72", "", { "dependencies": { "@cwcss/crosswind": "^0.2.0", "@stacksjs/stx": "0.2.72", "bun-plugin-stx": "0.2.72", "ts-syntax-highlighter": "^0.2.1" } }, "sha512-g1F7UYVSD13vQHx1U+/U2bor10OqUmC/C6Yh+uwrCmh+l6Vt4T52VLWPNPETQow8zCjqX5/Xme4HZqYm3HC/xw=="],
4545
46 "@stacksjs/desktop": ["@stacksjs/desktop@0.2.70", "", { "peerDependencies": { "craft": "*" }, "optionalPeers": ["craft"] }, "sha512-tBwE98HT+bajYzm4oY2NZtXz5+c9DjX6rUVXs0d8vr3Kv/4pof6tx+GTw64Z+lqvdB8wgCISRduDIVZiZjyDWQ=="],
46 "@stacksjs/desktop": ["@stacksjs/desktop@0.2.72", "", { "peerDependencies": { "craft": "*" }, "optionalPeers": ["craft"] }, "sha512-R436Ag4EdDhzM96idRHF61R+FkhwbtRbalQzEV/TRSYCMwJpVwJju5gQA08u+5elEZgCGATjtUia4GBbnJKWCw=="],
4747
4848 "@stacksjs/dtsx": ["@stacksjs/dtsx@0.9.26", "", { "dependencies": { "@stacksjs/clapp": "^0.2.0" }, "peerDependencies": { "typescript": ">=5.9.3" }, "bin": { "dtsx": "./dist/bin/cli.js" } }, "sha512-MOUxZn8BhXpFYM+vh7cnQLUOLjZ4G6q76V+4OypkRiu/Gvpek9ywBQLbmv8iWLjT6gXlFMpEdw33o3GKKLpMCw=="],
4949
@@ -51,9 +51,9 @@
5151
5252 "@stacksjs/logsmith": ["@stacksjs/logsmith@0.2.3", "", { "dependencies": { "@stacksjs/clapp": "^0.2.0", "bunfig": "^0.15.6", "pickier": "^0.1.9" }, "bin": { "@stacksjs/logsmith": "./dist/bin/cli.js", "logsmith": "./dist/bin/cli.js" } }, "sha512-Jy2AHVdEQwDtjlOVSG4xOmIMSy/D2iZcPK7KaoDOmKfZbMmfmbhveK16w1TWKCY9duhMZigdpeFx39ZzXGARVg=="],
5353
54 "@stacksjs/sanitizer": ["@stacksjs/sanitizer@0.2.70", "", {}, "sha512-jRuRn0VyCmlqM9H2yOuChV1dkxBozW+THBAnGcraYzPjf5CD1x/tRz9PGzS8h6PlEUSBfBi2zr+wPEwUKj1GoA=="],
54 "@stacksjs/sanitizer": ["@stacksjs/sanitizer@0.2.72", "", {}, "sha512-66a3QSfuSAgDDZJ3Z/mgqHEtkYwfag1m+9636I4X5cveTA69O92/0aEp6+f5MgGfPA+Gv4gkl3ArP8FBslR0BQ=="],
5555
56 "@stacksjs/stx": ["@stacksjs/stx@0.2.70", "", { "dependencies": { "@cwcss/crosswind": "^0.2.0", "@stacksjs/clapp": "^0.2.0", "@stacksjs/desktop": "0.2.70", "@stacksjs/sanitizer": "0.2.70", "@stacksjs/ts-craft": "^0.0.2", "bun-plugin-stx": "0.2.70", "bunfig": "^0.15.6", "stx-router": "0.2.70", "ts-broadcasting": "^0.0.4", "ts-images": "^0.1.8", "ts-syntax-highlighter": "^0.2.1" }, "bin": { "stx": "./dist/cli.js" } }, "sha512-dPOj5qvaZLQGKd3PlZawH/qL9IiUFUaSIKVpjUFp4/fqeOpdA7QbPC5vWhyYCeVa+tHyQwetBndwwjCYAHPjOw=="],
56 "@stacksjs/stx": ["@stacksjs/stx@0.2.72", "", { "dependencies": { "@cwcss/crosswind": "^0.2.0", "@stacksjs/clapp": "^0.2.0", "@stacksjs/desktop": "0.2.72", "@stacksjs/sanitizer": "0.2.72", "@stacksjs/ts-craft": "^0.0.2", "@stacksjs/ts-i18n": "^0.1.11", "bun-plugin-stx": "0.2.72", "bunfig": "^0.15.6", "stx-router": "0.2.72", "ts-broadcasting": "^0.0.4", "ts-images": "^0.1.8", "ts-syntax-highlighter": "^0.2.1" }, "bin": { "stx": "./dist/cli.js" } }, "sha512-aW6w0epktzFz0Jai9vCctBXEkPcri42jYNrojHyEm0oaRxyEgq9BLn6K5GUljLoJprr6wzfbDHZQp4FCopakaw=="],
5757
5858 "@stacksjs/ts-avif": ["@stacksjs/ts-avif@0.1.0", "", {}, "sha512-ytcta6ZhZeZwXJTcWlTraNLBsqL4imOfruSkJ2PwEp5RjHb4eSoqb21W4u7HLILRcILr7B1nOkIMeQct4Pozbw=="],
5959
@@ -67,6 +67,8 @@
6767
6868 "@stacksjs/ts-faker": ["@stacksjs/ts-faker@0.1.8", "", { "dependencies": { "@stacksjs/clapp": "^0.2.0" }, "bin": { "@stacksjs/ts-faker": "dist/bin/cli.js", "faker": "dist/bin/cli.js", "fake": "dist/bin/cli.js", "mock": "dist/bin/cli.js" } }, "sha512-YEOIr9mlMeXodcl8FmX3Rq7fqZDcubi7nwdl7lCKr682GIimOQjdN8miQmVEgbBYlpwP1cOn8eO6DhVotMwWnw=="],
6969
70 "@stacksjs/ts-i18n": ["@stacksjs/ts-i18n@0.1.11", "", { "dependencies": { "@stacksjs/clapp": "^0.2.9", "bunfig": "^0.15.6" }, "bin": { "i18n": "./dist/cli.js" } }, "sha512-33FM0gKdxBy6EUsUZLGAMe0D7QUZ4f/BQXRsQIyzkZgs2+juruUTevPuJGCjWSZsgJuiaBfEJn8Oq8ctuo1OWQ=="],
71
7072 "@stacksjs/ts-md": ["@stacksjs/ts-md@0.1.1", "", { "bin": { "md": "./bin/cli.ts" } }, "sha512-kvsSg7S3vHq65fnHIRggTLudN4n1sjkos86xLB62V6PVRa8iosYAmFKVDTGaJk+0MdsIoRwQbJ2vLWVcNQsgGw=="],
7173
7274 "@stacksjs/ts-png": ["@stacksjs/ts-png@0.1.2", "", {}, "sha512-NT9AVCAlZiOMzcBH+OlAAUAbfTzakm/6Ibp5S31JInrpTDd+Qe3arEfatPLuMasFR9tyO+NMAFsF84MT6TK1vA=="],
@@ -135,7 +137,7 @@
135137
136138 "bun-plugin-dtsx": ["bun-plugin-dtsx@0.9.26", "", { "dependencies": { "@stacksjs/dtsx": "0.9.26" } }, "sha512-dOlA+gx3mju+Be0uoZqsuQ3U+GPu08wv7QQwA1k569tw8Bv9GwVWJz0jjQw6tzJi9Rvwfl31MVXu9rHYQXSKow=="],
137139
138 "bun-plugin-stx": ["bun-plugin-stx@0.2.70", "", { "dependencies": { "@stacksjs/stx": "0.2.70", "stx-router": "0.2.70" }, "bin": { "serve": "./dist/serve.js", "stx-serve": "./dist/serve.js" } }, "sha512-7bJ6uzQLWqNM5p9Pgha9fNhVTz0gBT4LJyXEEmkMOIljevp7zKiWBa0r2F1CM0eXqSlUfqgI659mtfkhB9MiSA=="],
140 "bun-plugin-stx": ["bun-plugin-stx@0.2.72", "", { "dependencies": { "@stacksjs/stx": "0.2.72", "stx-router": "0.2.72" }, "bin": { "serve": "./dist/serve.js", "stx-serve": "./dist/serve.js" } }, "sha512-vkvFCmAm+w1YpCCBD5EImLaGFxgJmKZWDdi4mBvO9lxMFXBZiDyGcFxPnWGTdXcw2S085+lEG32BHwCV9kiqmw=="],
139141
140142 "bun-query-builder": ["bun-query-builder@0.1.37", "", { "dependencies": { "@stacksjs/clapp": "^0.2.0", "@stacksjs/ts-faker": "^0.1.8", "@stacksjs/ts-validation": "^0.5.0", "dynamodb-tooling": "^0.3.2" }, "bin": { "query-builder": "./dist/bin/cli.js", "qbx": "./dist/bin/cli.js", "qb": "./dist/bin/cli.js" } }, "sha512-IWgItHagukC4lf/11YmvccNrSEeX1wKeMiNTbUuw71c29Ru+3G2RD4kAPAVjZxkqLglrkyHRUM0iZ1c3yTdT1w=="],
141143
@@ -249,7 +251,7 @@
249251
250252 "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
251253
252 "stx-router": ["stx-router@0.2.70", "", {}, "sha512-b0tF8pmqVsgC0I0epav9NURHJk48vVe62QfQFK0jQiVBnAYTJREgjRK9gdrCxIIX/DAmoyI39zsYoKzv/tyiAQ=="],
254 "stx-router": ["stx-router@0.2.72", "", {}, "sha512-WUr5XaZVWp4JifqqOzU7YhmxIAiEPO8iRFO81VhKaGlF+FHrTPzhh3O0AmkAN+kmHv+IajbJ1UpMIcPTa5nFEg=="],
253255
254256 "tar-stream": ["tar-stream@3.2.0", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg=="],
255257
@@ -291,9 +293,7 @@
291293
292294 "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="],
293295
294 "@stacksjs/components/@cwcss/crosswind": ["@cwcss/crosswind@0.2.4", "", { "dependencies": { "@stacksjs/clapp": "^0.2.0", "bunfig": "^0.15.0" }, "bin": { "crosswind": "./dist/cli.js" } }, "sha512-LKcLryQl7Ue4LTbS3l1pBgcr2t4wIXdBtEJB1nlW5uJmS1o+cwR1uMASb2r9b1mtmfkp4xIouSRUvRRGAGbxww=="],
295
296 "@stacksjs/stx/@cwcss/crosswind": ["@cwcss/crosswind@0.2.4", "", { "dependencies": { "@stacksjs/clapp": "^0.2.0", "bunfig": "^0.15.0" }, "bin": { "crosswind": "./dist/cli.js" } }, "sha512-LKcLryQl7Ue4LTbS3l1pBgcr2t4wIXdBtEJB1nlW5uJmS1o+cwR1uMASb2r9b1mtmfkp4xIouSRUvRRGAGbxww=="],
296 "@stacksjs/bunpress/@stacksjs/stx": ["@stacksjs/stx@0.2.70", "", { "dependencies": { "@cwcss/crosswind": "^0.2.0", "@stacksjs/clapp": "^0.2.0", "@stacksjs/desktop": "0.2.70", "@stacksjs/sanitizer": "0.2.70", "@stacksjs/ts-craft": "^0.0.2", "bun-plugin-stx": "0.2.70", "bunfig": "^0.15.6", "stx-router": "0.2.70", "ts-broadcasting": "^0.0.4", "ts-images": "^0.1.8", "ts-syntax-highlighter": "^0.2.1" }, "bin": { "stx": "./dist/cli.js" } }, "sha512-dPOj5qvaZLQGKd3PlZawH/qL9IiUFUaSIKVpjUFp4/fqeOpdA7QbPC5vWhyYCeVa+tHyQwetBndwwjCYAHPjOw=="],
297297
298298 "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
299299
@@ -311,6 +311,16 @@
311311
312312 "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
313313
314 "@stacksjs/bunpress/@stacksjs/stx/@cwcss/crosswind": ["@cwcss/crosswind@0.2.4", "", { "dependencies": { "@stacksjs/clapp": "^0.2.0", "bunfig": "^0.15.0" }, "bin": { "crosswind": "./dist/cli.js" } }, "sha512-LKcLryQl7Ue4LTbS3l1pBgcr2t4wIXdBtEJB1nlW5uJmS1o+cwR1uMASb2r9b1mtmfkp4xIouSRUvRRGAGbxww=="],
315
316 "@stacksjs/bunpress/@stacksjs/stx/@stacksjs/desktop": ["@stacksjs/desktop@0.2.70", "", { "peerDependencies": { "craft": "*" }, "optionalPeers": ["craft"] }, "sha512-tBwE98HT+bajYzm4oY2NZtXz5+c9DjX6rUVXs0d8vr3Kv/4pof6tx+GTw64Z+lqvdB8wgCISRduDIVZiZjyDWQ=="],
317
318 "@stacksjs/bunpress/@stacksjs/stx/@stacksjs/sanitizer": ["@stacksjs/sanitizer@0.2.70", "", {}, "sha512-jRuRn0VyCmlqM9H2yOuChV1dkxBozW+THBAnGcraYzPjf5CD1x/tRz9PGzS8h6PlEUSBfBi2zr+wPEwUKj1GoA=="],
319
320 "@stacksjs/bunpress/@stacksjs/stx/bun-plugin-stx": ["bun-plugin-stx@0.2.70", "", { "dependencies": { "@stacksjs/stx": "0.2.70", "stx-router": "0.2.70" }, "bin": { "serve": "./dist/serve.js", "stx-serve": "./dist/serve.js" } }, "sha512-7bJ6uzQLWqNM5p9Pgha9fNhVTz0gBT4LJyXEEmkMOIljevp7zKiWBa0r2F1CM0eXqSlUfqgI659mtfkhB9MiSA=="],
321
322 "@stacksjs/bunpress/@stacksjs/stx/stx-router": ["stx-router@0.2.70", "", {}, "sha512-b0tF8pmqVsgC0I0epav9NURHJk48vVe62QfQFK0jQiVBnAYTJREgjRK9gdrCxIIX/DAmoyI39zsYoKzv/tyiAQ=="],
323
314324 "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
315325
316326 "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
package.jsonmodified+3-3
Changes to package.json
@@ -66,9 +66,9 @@
6666 "dependencies": {
6767 "@cwcss/crosswind": "^0.2.6",
6868 "@stacksjs/bun-router": "^0.0.16",
69 "@stacksjs/components": "^0.2.67",
70 "@stacksjs/stx": "^0.2.70",
71 "bun-plugin-stx": "^0.2.70",
69 "@stacksjs/components": "^0.2.72",
70 "@stacksjs/stx": "^0.2.72",
71 "bun-plugin-stx": "^0.2.72",
7272 "bun-query-builder": "^0.1.13",
7373 "dynamodb-tooling": "^0.3.2",
7474 "ts-countries": "^0.1.2"
resources/components/dashboard/AccountSettings.stxmodified+8-8
Changes to resources/components/dashboard/AccountSettings.stx
@@ -107,8 +107,8 @@ onMount(() => {
107107<div class="col-span-full max-w-[640px]">
108108 <h3 class="text-base font-semibold text-text mb-1">Account</h3>
109109 <p class="text-xs text-muted mb-6">
110 Signed in as <span class="text-text" x-text="email()"></span>
111 <span class="text-[0.625rem] font-semibold py-0.5 px-[7px] rounded uppercase tracking-wide bg-bg-3 text-text-2 ml-1" x-text="plan()"></span>
110 Signed in as <span class="text-text">{{ email() }}</span>
111 <span class="text-[0.625rem] font-semibold py-0.5 px-[7px] rounded uppercase tracking-wide bg-bg-3 text-text-2 ml-1">{{ plan() }}</span>
112112 <span x-show="!emailVerified()" class="text-warning ml-1">— email not verified</span>
113113 </p>
114114
@@ -121,8 +121,8 @@ onMount(() => {
121121 </div>
122122 <button type="submit" class="btn btn-primary text-[0.8125rem] py-2 px-4">Save</button>
123123 </form>
124 <p x-show="profileMsg()" class="text-xs text-success mt-2" x-text="profileMsg()"></p>
125 <p x-show="profileErr()" class="text-xs text-error mt-2" x-text="profileErr()"></p>
124 <p x-show="profileMsg()" class="text-xs text-success mt-2">{{ profileMsg() }}</p>
125 <p x-show="profileErr()" class="text-xs text-error mt-2">{{ profileErr() }}</p>
126126 </div>
127127
128128 <div class="panel mb-4">
@@ -138,8 +138,8 @@ onMount(() => {
138138 </div>
139139 <button type="submit" class="btn btn-primary text-[0.8125rem] py-2 px-4 self-start">Change password</button>
140140 </form>
141 <p x-show="passwordMsg()" class="text-xs text-success mt-2" x-text="passwordMsg()"></p>
142 <p x-show="passwordErr()" class="text-xs text-error mt-2" x-text="passwordErr()"></p>
141 <p x-show="passwordMsg()" class="text-xs text-success mt-2">{{ passwordMsg() }}</p>
142 <p x-show="passwordErr()" class="text-xs text-error mt-2">{{ passwordErr() }}</p>
143143 </div>
144144
145145 <div class="panel mb-4">
@@ -155,8 +155,8 @@ onMount(() => {
155155 </div>
156156 <button type="submit" class="btn btn-primary text-[0.8125rem] py-2 px-4 self-start">Change email</button>
157157 </form>
158 <p x-show="emailMsg()" class="text-xs text-success mt-2" x-text="emailMsg()"></p>
159 <p x-show="emailErr()" class="text-xs text-error mt-2" x-text="emailErr()"></p>
158 <p x-show="emailMsg()" class="text-xs text-success mt-2">{{ emailMsg() }}</p>
159 <p x-show="emailErr()" class="text-xs text-error mt-2">{{ emailErr() }}</p>
160160 </div>
161161
162162 <div class="panel">
resources/components/dashboard/BrowsersPanel.stxmodified+3-3
Changes to resources/components/dashboard/BrowsersPanel.stx
@@ -35,9 +35,9 @@ onMount(() => {
3535 </thead>
3636 <tbody>
3737 <tr x-for="browser in analytics.browsers" @loading="analytics.loading()">
38 <td class="cell-name" x-text="browser.name ? browser.name : 'Unknown'"></td>
39 <td class="cell-value" x-text="fmt(browser.visitors)"></td>
40 <td class="cell-value" x-text="(browser.percentage ? browser.percentage : 0) + '%'"></td>
38 <td class="cell-name">{{ browser.name ? browser.name : 'Unknown' }}</td>
39 <td class="cell-value">{{ fmt(browser.visitors) }}</td>
40 <td class="cell-value">{{ (browser.percentage ? browser.percentage : 0) + '%' }}</td>
4141 </tr>
4242 <tr @for-loading>
4343 <td colspan="3" class="text-center text-muted py-4">Loading...</td>
resources/components/dashboard/CampaignsPanel.stxmodified+3-3
Changes to resources/components/dashboard/CampaignsPanel.stx
@@ -53,9 +53,9 @@ onMount(() => {
5353 </thead>
5454 <tbody>
5555 <tr x-for="campaign in campaigns" @loading="loading()">
56 <td class="cell-name" x-text="campaign.name ? campaign.name : (campaign.source ? campaign.source : 'Unknown')"></td>
57 <td class="cell-value" x-text="fmt(campaign.visitors ? campaign.visitors : 0)"></td>
58 <td class="cell-value" x-text="fmt(campaign.views ? campaign.views : 0)"></td>
56 <td class="cell-name">{{ campaign.name ? campaign.name : (campaign.source ? campaign.source : 'Unknown') }}</td>
57 <td class="cell-value">{{ fmt(campaign.visitors ? campaign.visitors : 0) }}</td>
58 <td class="cell-value">{{ fmt(campaign.views ? campaign.views : 0) }}</td>
5959 </tr>
6060 <tr @for-loading>
6161 <td colspan="3" class="text-center text-muted py-4">Loading...</td>
resources/components/dashboard/ChannelsPanel.stxmodified+3-3
Changes to resources/components/dashboard/ChannelsPanel.stx
@@ -62,13 +62,13 @@ effect(() => {
6262 <div class="flex flex-col gap-3">
6363 <div x-for="ch in channels" @loading="loading()" class="flex flex-col gap-1">
6464 <div class="flex justify-between items-center">
65 <span class="text-[0.8125rem] text-text font-medium" x-text="ch.channel"></span>
66 <span class="text-xs text-muted shrink-0 ml-2" x-text="pct(ch.visitors ? ch.visitors : 0) + '%'"></span>
65 <span class="text-[0.8125rem] text-text font-medium">{{ ch.channel }}</span>
66 <span class="text-xs text-muted shrink-0 ml-2">{{ pct(ch.visitors ? ch.visitors : 0) + '%' }}</span>
6767 </div>
6868 <div class="h-1.5 bg-bg-3 rounded-sm overflow-hidden">
6969 <div class="h-full bg-accent rounded-sm transition-[width] duration-300" x-style="'width: ' + pct(ch.visitors ? ch.visitors : 0) + '%'"></div>
7070 </div>
71 <div class="text-[0.6875rem] text-muted text-right" x-text="fmtNum(ch.visitors) + ' visitors'"></div>
71 <div class="text-[0.6875rem] text-muted text-right">{{ fmtNum(ch.visitors) + ' visitors' }}</div>
7272 </div>
7373 <div @for-loading class="text-center text-muted py-4 text-[0.8125rem]">Loading...</div>
7474 <div @for-empty class="text-center text-muted py-4 text-[0.8125rem]">No channel data</div>
resources/components/dashboard/ControlsBar.stxmodified+3-3
Changes to resources/components/dashboard/ControlsBar.stx
@@ -116,7 +116,7 @@ provide('controlsBar', {
116116
117117<div id="controls-bar" class="flex justify-between items-center py-3 px-6 bg-bg border-b border-border gap-4 flex-wrap max-sm:flex-col max-sm:items-stretch" x-show="dashboard.controlsVisible()">
118118 <div class="flex items-center gap-1 max-sm:justify-center">
119 <button x-for="range in ranges" x-class="rangeBtnClass(range)" @click="setRange(range.id)" x-text="range.label"></button>
119 <button x-for="range in ranges" x-class="rangeBtnClass(range)" @click="setRange(range.id)">{{ range.label }}</button>
120120 <span class="border-l border-border h-6 mx-2"></span>
121121 <button id="compare-btn" x-class="compareBtnClass()" @click="toggleComparison()" title="Compare with previous period">
122122 <Icon name="bar-chart-2" size="14" />
@@ -126,9 +126,9 @@ provide('controlsBar', {
126126 <div class="flex items-center gap-3 max-sm:justify-between">
127127 <div class="realtime-badge flex items-center gap-2 py-1.5 px-3 rounded-full text-xs font-medium text-success">
128128 <span class="pulse"></span>
129 <span id="realtime-count" x-text="realtimeLabel()"></span>
129 <span id="realtime-count">{{ realtimeLabel() }}</span>
130130 </div>
131 <span id="last-updated" class="text-xs text-muted" x-show="lastUpdated()" x-text="lastUpdated()"></span>
131 <span id="last-updated" class="text-xs text-muted" x-show="lastUpdated()">{{ lastUpdated() }}</span>
132132 <button id="refresh-btn" x-class="refreshBtnClass()" @click="refresh()" title="Refresh data">
133133 <Icon name="refresh-cw" size="16" />
134134 </button>
resources/components/dashboard/CountriesPanel.stxmodified+3-3
Changes to resources/components/dashboard/CountriesPanel.stx
@@ -52,10 +52,10 @@ onMount(() => {
5252 <tbody>
5353 <tr x-for="country in analytics.countries" @loading="analytics.loading()">
5454 <td class="cell-name flex items-center gap-1.5">
55 <span class="text-base" x-text="getFlag(country.name ? country.name : country.code)"></span>
56 <span x-text="country.name ? country.name : (country.code ? country.code : 'Unknown')"></span>
55 <span class="text-base">{{ getFlag(country.name ? country.name : country.code) }}</span>
56 <span>{{ country.name ? country.name : (country.code ? country.code : 'Unknown') }}</span>
5757 </td>
58 <td class="cell-value" x-text="fmt(country.visitors)"></td>
58 <td class="cell-value">{{ fmt(country.visitors) }}</td>
5959 </tr>
6060 <tr @for-loading>
6161 <td colspan="2" class="text-center text-muted py-4">Loading...</td>
resources/components/dashboard/DashboardHeader.stxmodified+3-3
Changes to resources/components/dashboard/DashboardHeader.stx
@@ -156,7 +156,7 @@ provide('dashboardHeader', {
156156 <button class="icon-btn" @click="handleBack()" title="Back to sites">
157157 <Icon name="chevron-left" size="20" />
158158 </button>
159 <span id="current-site-name" class="font-semibold text-base text-text" x-text="siteName()"></span>
159 <span id="current-site-name" class="font-semibold text-base text-text">{{ siteName() }}</span>
160160 </div>
161161
162162 <nav id="header-nav" class="header-nav flex gap-1 flex-wrap justify-center">
@@ -165,7 +165,7 @@ provide('dashboardHeader', {
165165 x-href="tabHref(tab)"
166166 x-data-tab="tab.id"
167167 data-stx-prefetch
168 x-text="tab.label"></a>
168>{{ tab.label }}</a>
169169 </nav>
170170
171171 <div class="flex items-center gap-2">
@@ -181,7 +181,7 @@ provide('dashboardHeader', {
181181 </button>
182182 <a x-href="'/dashboard/account' + (siteId() ? '?siteId=' + encodeURIComponent(siteId()) : '')" x-show="userEmail()" class="flex items-center gap-1.5 icon-btn no-underline" title="Account settings" data-stx-prefetch>
183183 <Icon name="user" size="18" />
184 <span class="text-xs text-muted max-w-[160px] truncate hidden md:inline" x-text="userEmail()"></span>
184 <span class="text-xs text-muted max-w-[160px] truncate hidden md:inline">{{ userEmail() }}</span>
185185 </a>
186186 <form method="POST" action="/logout" class="inline">
187187 <button type="submit" class="icon-btn hover:text-error" title="Logout">
resources/components/dashboard/DevicesPanel.stxmodified+3-3
Changes to resources/components/dashboard/DevicesPanel.stx
@@ -54,10 +54,10 @@ onMount(() => {
5454 <svg class="text-muted shrink-0" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
5555 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="getIconPath(device.type)"/>
5656 </svg>
57 <span x-text="capitalize(device.type ? device.type : 'Unknown')"></span>
57 <span>{{ capitalize(device.type ? device.type : 'Unknown') }}</span>
5858 </td>
59 <td class="cell-value" x-text="fmt(device.visitors)"></td>
60 <td class="cell-value" x-text="(device.percentage ? device.percentage : 0) + '%'"></td>
59 <td class="cell-value">{{ fmt(device.visitors) }}</td>
60 <td class="cell-value">{{ (device.percentage ? device.percentage : 0) + '%' }}</td>
6161 </tr>
6262 <tr @for-loading>
6363 <td colspan="3" class="text-center text-muted py-4">Loading...</td>
resources/components/dashboard/EngagementPanel.stxmodified+5-5
Changes to resources/components/dashboard/EngagementPanel.stx
@@ -61,11 +61,11 @@ effect(() => {
6161 <div class="flex gap-8 mb-4">
6262 <div class="flex flex-col">
6363 <span class="text-[0.6875rem] uppercase tracking-wide text-muted">Avg. time on page</span>
64 <span class="text-lg font-semibold text-text" x-text="fmtTime(avgSeconds())"></span>
64 <span class="text-lg font-semibold text-text">{{ fmtTime(avgSeconds()) }}</span>
6565 </div>
6666 <div class="flex flex-col">
6767 <span class="text-[0.6875rem] uppercase tracking-wide text-muted">Avg. scroll depth</span>
68 <span class="text-lg font-semibold text-text" x-text="avgScroll() + '%'"></span>
68 <span class="text-lg font-semibold text-text">{{ avgScroll() + '%' }}</span>
6969 </div>
7070 </div>
7171
@@ -79,9 +79,9 @@ effect(() => {
7979 </thead>
8080 <tbody>
8181 <tr x-for="p in pages" @loading="loading()">
82 <td class="cell-name truncate" x-text="p.path"></td>
83 <td class="cell-value" x-text="fmtTime(p.avgSeconds)"></td>
84 <td class="cell-value" x-text="p.avgScrollDepth + '%'"></td>
82 <td class="cell-name truncate">{{ p.path }}</td>
83 <td class="cell-value">{{ fmtTime(p.avgSeconds) }}</td>
84 <td class="cell-value">{{ p.avgScrollDepth + '%' }}</td>
8585 </tr>
8686 <tr @for-loading>
8787 <td colspan="3" class="text-center text-muted py-4">Loading...</td>
resources/components/dashboard/ErrorDetail.stxmodified+18-18
Changes to resources/components/dashboard/ErrorDetail.stx
@@ -83,16 +83,16 @@ finally {
8383 </a>
8484
8585 <div x-show="loading()" class="text-center text-muted py-12">Loading issue…</div>
86 <div x-show="!loading() && loadError()" class="text-center text-error py-12" x-text="loadError()"></div>
86 <div x-show="!loading() && loadError()" class="text-center text-error py-12">{{ loadError() }}</div>
8787
8888 <div x-show="!loading() && !loadError()">
8989 <!-- Header -->
9090 <div class="flex items-start justify-between gap-4 mb-4">
91 <h1 class="text-lg font-semibold text-text break-words" x-text="issue().message"></h1>
91 <h1 class="text-lg font-semibold text-text break-words">{{ issue().message }}</h1>
9292 <div class="flex gap-2 shrink-0">
93 <span class="text-[0.625rem] font-semibold py-0.5 px-2 rounded uppercase tracking-wide border" x-class="statusColor()" x-text="statusLabel()"></span>
94 <span class="text-[0.625rem] font-semibold py-0.5 px-2 rounded uppercase tracking-wide border border-border text-text-2" x-text="severityLabel()"></span>
95 <span x-show="issue().status === 'open' && issue().trend" class="text-[0.625rem] font-semibold py-0.5 px-2 rounded uppercase tracking-wide border border-border text-text-2" x-text="issue().trend"></span>
93 <span class="text-[0.625rem] font-semibold py-0.5 px-2 rounded uppercase tracking-wide border" x-class="statusColor()">{{ statusLabel() }}</span>
94 <span class="text-[0.625rem] font-semibold py-0.5 px-2 rounded uppercase tracking-wide border border-border text-text-2">{{ severityLabel() }}</span>
95 <span x-show="issue().status === 'open' && issue().trend" class="text-[0.625rem] font-semibold py-0.5 px-2 rounded uppercase tracking-wide border border-border text-text-2">{{ issue().trend }}</span>
9696 </div>
9797 </div>
9898
@@ -100,19 +100,19 @@ finally {
100100 <div class="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-6">
101101 <div class="panel py-3">
102102 <div class="text-xs text-muted">Events</div>
103 <div class="text-base font-semibold text-text" x-text="fmtNum(issue().count)"></div>
103 <div class="text-base font-semibold text-text">{{ fmtNum(issue().count) }}</div>
104104 </div>
105105 <div class="panel py-3">
106106 <div class="text-xs text-muted">Affected</div>
107 <div class="text-base font-semibold text-text" x-text="fmtNum(issue().affectedVisitors)"></div>
107 <div class="text-base font-semibold text-text">{{ fmtNum(issue().affectedVisitors) }}</div>
108108 </div>
109109 <div class="panel py-3">
110110 <div class="text-xs text-muted">First seen</div>
111 <div class="text-[0.8125rem] font-medium text-text" x-text="when(issue().firstSeen)"></div>
111 <div class="text-[0.8125rem] font-medium text-text">{{ when(issue().firstSeen) }}</div>
112112 </div>
113113 <div class="panel py-3">
114114 <div class="text-xs text-muted">Last seen</div>
115 <div class="text-[0.8125rem] font-medium text-text" x-text="when(issue().lastSeen)"></div>
115 <div class="text-[0.8125rem] font-medium text-text">{{ when(issue().lastSeen) }}</div>
116116 </div>
117117 </div>
118118
@@ -122,10 +122,10 @@ finally {
122122 <div class="panel-title m-0">Stack trace</div>
123123 <span x-show="latest().symbolicatedStack" class="text-[0.625rem] font-semibold py-0.5 px-[7px] rounded uppercase tracking-wide border border-success text-success">Symbolicated</span>
124124 </div>
125 <pre class="text-xs text-text-2 overflow-x-auto whitespace-pre-wrap break-words bg-bg-3 rounded-md p-3 m-0" x-text="latest().symbolicatedStack || latest().stack || 'No stack trace captured'"></pre>
125 <pre class="text-xs text-text-2 overflow-x-auto whitespace-pre-wrap break-words bg-bg-3 rounded-md p-3 m-0">{{ latest().symbolicatedStack || latest().stack || 'No stack trace captured' }}</pre>
126126 <details x-show="latest().symbolicatedStack" class="mt-2">
127127 <summary class="text-[0.6875rem] text-muted cursor-pointer">Show raw (minified) stack</summary>
128 <pre class="text-xs text-text-2 overflow-x-auto whitespace-pre-wrap break-words bg-bg-3 rounded-md p-3 mt-2 mb-0" x-text="latest().stack"></pre>
128 <pre class="text-xs text-text-2 overflow-x-auto whitespace-pre-wrap break-words bg-bg-3 rounded-md p-3 mt-2 mb-0">{{ latest().stack }}</pre>
129129 </details>
130130 </div>
131131
@@ -134,8 +134,8 @@ finally {
134134 <div class="panel-title mb-2">Breadcrumbs</div>
135135 <div class="flex flex-col gap-1">
136136 <div x-for="c in breadcrumbs" class="flex items-center gap-2 text-xs border-b border-bg-3 py-1.5 last:border-0">
137 <span class="text-muted font-mono shrink-0 w-28 truncate" x-text="c.category || 'log'"></span>
138 <span class="text-text-2 truncate" x-text="c.message || ''"></span>
137 <span class="text-muted font-mono shrink-0 w-28 truncate">{{ c.category || 'log' }}</span>
138 <span class="text-text-2 truncate">{{ c.message || '' }}</span>
139139 </div>
140140 </div>
141141 </div>
@@ -154,10 +154,10 @@ finally {
154154 </thead>
155155 <tbody>
156156 <tr x-for="o in occurrences" class="border-b border-bg-3">
157 <td class="py-1.5 text-text-2 whitespace-nowrap" x-text="when(o.timestamp)"></td>
158 <td class="py-1.5 text-text-2" x-text="o.browser || '—'"></td>
159 <td class="py-1.5 text-text-2" x-text="o.os || '—'"></td>
160 <td class="py-1.5 text-text-2 truncate max-w-[24rem]" x-text="o.url || '—'"></td>
157 <td class="py-1.5 text-text-2 whitespace-nowrap">{{ when(o.timestamp) }}</td>
158 <td class="py-1.5 text-text-2">{{ o.browser || '—' }}</td>
159 <td class="py-1.5 text-text-2">{{ o.os || '—' }}</td>
160 <td class="py-1.5 text-text-2 truncate max-w-[24rem]">{{ o.url || '—' }}</td>
161161 </tr>
162162 </tbody>
163163 </table>
@@ -167,7 +167,7 @@ finally {
167167 <div x-show="browsers().length > 0" class="panel">
168168 <div class="panel-title mb-2">Affected browsers</div>
169169 <div class="flex flex-wrap gap-2">
170 <span x-for="b in browsers" class="text-xs text-text-2 border border-border rounded px-2 py-0.5" x-text="b"></span>
170 <span x-for="b in browsers" class="text-xs text-text-2 border border-border rounded px-2 py-0.5">{{ b }}</span>
171171 </div>
172172 </div>
173173 </div>
resources/components/dashboard/EventPropertiesPanel.stxmodified+5-5
Changes to resources/components/dashboard/EventPropertiesPanel.stx
@@ -74,21 +74,21 @@ effect(() => {
7474
7575 <div x-show="eventChips().length > 0" class="flex flex-wrap gap-2 mb-3">
7676 <span x-for="chip in eventChips" class="inline-flex items-center gap-1 rounded-full bg-bg-3 px-2 py-0.5 text-[0.6875rem] text-muted">
77 <span x-text="chip.name"></span>
78 <span class="font-medium text-text" x-text="fmtNum(chip.count)"></span>
77 <span>{{ chip.name }}</span>
78 <span class="font-medium text-text">{{ fmtNum(chip.count) }}</span>
7979 </span>
8080 </div>
8181
8282 <div class="flex flex-col gap-3">
8383 <div x-for="r in rows" @loading="loading()" class="flex flex-col gap-1">
8484 <div class="flex justify-between items-center gap-2">
85 <span class="text-[0.8125rem] text-text font-medium truncate" x-text="rowLabel(r)"></span>
86 <span class="text-xs text-muted shrink-0 ml-2" x-text="fmtNum(r.count)"></span>
85 <span class="text-[0.8125rem] text-text font-medium truncate">{{ rowLabel(r) }}</span>
86 <span class="text-xs text-muted shrink-0 ml-2">{{ fmtNum(r.count) }}</span>
8787 </div>
8888 <div class="h-1.5 bg-bg-3 rounded-sm overflow-hidden">
8989 <div class="h-full bg-accent rounded-sm transition-[width] duration-300" x-style="'width: ' + barWidth(r.count ? r.count : 0) + '%'"></div>
9090 </div>
91 <div class="text-[0.6875rem] text-muted text-right" x-text="fmtNum(r.visitors) + ' visitors'"></div>
91 <div class="text-[0.6875rem] text-muted text-right">{{ fmtNum(r.visitors) + ' visitors' }}</div>
9292 </div>
9393 <div @for-loading class="text-center text-muted py-4 text-[0.8125rem]">Loading...</div>
9494 <div @for-empty class="text-center text-muted py-4 text-[0.8125rem]">No event properties tracked yet</div>
resources/components/dashboard/EventsPanel.stxmodified+3-3
Changes to resources/components/dashboard/EventsPanel.stx
@@ -60,13 +60,13 @@ onMount(() => {
6060 <div class="flex flex-col gap-3">
6161 <div x-for="event in events" @loading="loading()" class="flex flex-col gap-1">
6262 <div class="flex justify-between items-center">
63 <span class="text-[0.8125rem] text-text font-medium truncate" x-text="event.name"></span>
64 <span class="text-xs text-muted shrink-0 ml-2" x-text="getPercentage(event.count ? event.count : 0) + '%'"></span>
63 <span class="text-[0.8125rem] text-text font-medium truncate">{{ event.name }}</span>
64 <span class="text-xs text-muted shrink-0 ml-2">{{ getPercentage(event.count ? event.count : 0) + '%' }}</span>
6565 </div>
6666 <div class="h-1.5 bg-bg-3 rounded-sm overflow-hidden">
6767 <div class="h-full bg-accent rounded-sm transition-[width] duration-300" x-style="'width: ' + getBarWidth(event.count ? event.count : 0) + '%'"></div>
6868 </div>
69 <div class="text-[0.6875rem] text-muted text-right" x-text="fmt(event.count)"></div>
69 <div class="text-[0.6875rem] text-muted text-right">{{ fmt(event.count) }}</div>
7070 </div>
7171 <div @for-loading class="text-center text-muted py-4 text-[0.8125rem]">Loading...</div>
7272 <div @for-empty class="text-center text-muted py-4 text-[0.8125rem]">No custom events tracked</div>
resources/components/dashboard/GoalModal.stxmodified+3-3
Changes to resources/components/dashboard/GoalModal.stx
@@ -128,10 +128,10 @@ onMount(() => {
128128
129129<div x-show="showModal()" class="modal-overlay" @click.self="closeModal">
130130 <div class="modal-content bg-bg-2 max-w-[420px] p-6">
131 <h3 class="text-lg font-semibold text-text mb-6" x-text="modalTitle()"></h3>
131 <h3 class="text-lg font-semibold text-text mb-6">{{ modalTitle() }}</h3>
132132
133133 <form @submit="handleSubmit">
134 <div x-show="error()" class="error-message" x-text="error()"></div>
134 <div x-show="error()" class="error-message">{{ error() }}</div>
135135
136136 <label class="block mb-4 text-[0.8125rem] text-text-2">
137137 Name
@@ -200,7 +200,7 @@ onMount(() => {
200200 <button type="button" class="btn btn-secondary" @click="closeModal" x-disabled="saving()">
201201 Cancel
202202 </button>
203 <button type="submit" class="btn btn-primary" x-disabled="saving()" x-text="saving() ? 'Saving...' : 'Save Goal'"></button>
203 <button type="submit" class="btn btn-primary" x-disabled="saving()">{{ saving() ? 'Saving...' : 'Save Goal' }}</button>
204204 </div>
205205 </form>
206206 </div>
resources/components/dashboard/GoalsPanel.stxmodified+4-4
Changes to resources/components/dashboard/GoalsPanel.stx
@@ -92,13 +92,13 @@ window.refreshGoalsPanel = loadGoals
9292 </thead>
9393 <tbody>
9494 <tr x-for="goal in goals" class="goal-row">
95 <td class="font-medium" x-text="goal.name"></td>
95 <td class="font-medium">{{ goal.name }}</td>
9696 <td>
97 <span x-class="getTypeBadgeClass(goal.type)" x-text="goal.type"></span>
97 <span x-class="getTypeBadgeClass(goal.type)">{{ goal.type }}</span>
9898 </td>
99 <td class="text-right tabular-nums" x-text="fmt(goal.conversions ? goal.conversions : 0)"></td>
99 <td class="text-right tabular-nums">{{ fmt(goal.conversions ? goal.conversions : 0) }}</td>
100100 <td class="text-right tabular-nums">
101 <span x-show="goal.value" x-text="formatCurrency(goal.value)"></span>
101 <span x-show="goal.value">{{ formatCurrency(goal.value) }}</span>
102102 <span x-show="!goal.value">-</span>
103103 </td>
104104 <td class="text-right whitespace-nowrap">
resources/components/dashboard/LinkClicksPanel.stxmodified+5-5
Changes to resources/components/dashboard/LinkClicksPanel.stx
@@ -74,21 +74,21 @@ effect(() => {
7474
7575 <div x-show="kindChips().length > 0" class="flex flex-wrap gap-2 mb-3">
7676 <span x-for="chip in kindChips" class="inline-flex items-center gap-1 rounded-full bg-bg-3 px-2 py-0.5 text-[0.6875rem] text-muted">
77 <span x-text="chip.label"></span>
78 <span class="font-medium text-text" x-text="fmtNum(chip.count)"></span>
77 <span>{{ chip.label }}</span>
78 <span class="font-medium text-text">{{ fmtNum(chip.count) }}</span>
7979 </span>
8080 </div>
8181
8282 <div class="flex flex-col gap-3">
8383 <div x-for="c in clicks" @loading="loading()" class="flex flex-col gap-1">
8484 <div class="flex justify-between items-center gap-2">
85 <span class="text-[0.8125rem] text-text font-medium truncate" x-text="clickLabel(c)"></span>
86 <span class="shrink-0 rounded-sm bg-bg-3 px-1.5 py-0.5 text-[0.625rem] uppercase tracking-wide text-muted" x-text="c.kind"></span>
85 <span class="text-[0.8125rem] text-text font-medium truncate">{{ clickLabel(c) }}</span>
86 <span class="shrink-0 rounded-sm bg-bg-3 px-1.5 py-0.5 text-[0.625rem] uppercase tracking-wide text-muted">{{ c.kind }}</span>
8787 </div>
8888 <div class="h-1.5 bg-bg-3 rounded-sm overflow-hidden">
8989 <div class="h-full bg-accent rounded-sm transition-[width] duration-300" x-style="'width: ' + barWidth(c.count ? c.count : 0) + '%'"></div>
9090 </div>
91 <div class="text-[0.6875rem] text-muted text-right" x-text="fmtNum(c.count) + ' clicks'"></div>
91 <div class="text-[0.6875rem] text-muted text-right">{{ fmtNum(c.count) + ' clicks' }}</div>
9292 </div>
9393 <div @for-loading class="text-center text-muted py-4 text-[0.8125rem]">Loading...</div>
9494 <div @for-empty class="text-center text-muted py-4 text-[0.8125rem]">No link clicks tracked yet</div>
resources/components/dashboard/NoDataMessage.stxmodified+1-1
Changes to resources/components/dashboard/NoDataMessage.stx
@@ -40,7 +40,7 @@ effect(() => {
4040 <Icon name="bar-chart-2" size="48" class="text-warning mb-4 mx-auto" />
4141 <h3 class="mb-2 text-warning">No analytics data yet</h3>
4242 <p class="text-text-2 mb-4">Add the tracking script to your website to start collecting data.</p>
43 <code id="tracking-script" class="block bg-bg p-4 rounded-md text-xs mt-4 text-accent-2 break-all text-left" x-text="trackingSnippet()"></code>
43 <code id="tracking-script" class="block bg-bg p-4 rounded-md text-xs mt-4 text-accent-2 break-all text-left">{{ trackingSnippet() }}</code>
4444 <div class="flex items-start gap-4 text-left mt-6 pt-6 border-t border-border">
4545 <div class="bg-accent text-white w-6 h-6 rounded-full inline-flex items-center justify-center text-xs font-semibold flex-shrink-0">1</div>
4646 <div class="flex-1">
resources/components/dashboard/PagesPanel.stxmodified+4-4
Changes to resources/components/dashboard/PagesPanel.stx
@@ -38,13 +38,13 @@ onMount(() => {
3838 <tr x-for="page in analytics.pages" @loading="analytics.loading()">
3939 <td class="cell-name" :title="page.path">
4040 <a x-href="page.path" target="_blank" rel="noopener" class="inline-flex items-center gap-1 hover:text-accent">
41 <span x-text="page.path"></span>
41 <span>{{ page.path }}</span>
4242 <Icon name="external-link" size="10" class="opacity-50" />
4343 </a>
4444 </td>
45 <td class="cell-value" x-text="fmt(page.entries)"></td>
46 <td class="cell-value" x-text="fmt(page.visitors)"></td>
47 <td class="cell-value" x-text="fmt(page.views)"></td>
45 <td class="cell-value">{{ fmt(page.entries) }}</td>
46 <td class="cell-value">{{ fmt(page.visitors) }}</td>
47 <td class="cell-value">{{ fmt(page.views) }}</td>
4848 </tr>
4949 <tr @for-loading>
5050 <td colspan="4" class="text-center text-muted py-4">Loading...</td>
resources/components/dashboard/ReferrersPanel.stxmodified+4-4
Changes to resources/components/dashboard/ReferrersPanel.stx
@@ -83,13 +83,13 @@ onMount(() => {
8383 class="align-middle rounded-sm"
8484 @error="e => e.target.style.display = 'none'"
8585 />
86 <span x-text="ref.source ? ref.source : 'Direct'"></span>
86 <span>{{ ref.source ? ref.source : 'Direct' }}</span>
8787 <Icon name="external-link" size="10" class="opacity-50" />
8888 </a>
89 <span x-show="!isExternalLink(ref.source ? ref.source : 'Direct')" x-text="ref.source ? ref.source : 'Direct'"></span>
89 <span x-show="!isExternalLink(ref.source ? ref.source : 'Direct')">{{ ref.source ? ref.source : 'Direct' }}</span>
9090 </td>
91 <td class="cell-value" x-text="fmt(ref.visitors)"></td>
92 <td class="cell-value" x-text="fmt(ref.views)"></td>
91 <td class="cell-value">{{ fmt(ref.visitors) }}</td>
92 <td class="cell-value">{{ fmt(ref.views) }}</td>
9393 </tr>
9494 <tr @for-loading>
9595 <td colspan="3" class="text-center text-muted py-4">Loading...</td>
resources/components/dashboard/SiteSelector.stxmodified+8-8
Changes to resources/components/dashboard/SiteSelector.stx
@@ -205,7 +205,7 @@ onMount(() => {
205205
206206 <div x-show="!loading() && error()" class="site-status">
207207 <Icon name="alert-triangle" size="32" class="text-error" style="opacity:0.7;margin-bottom:0.75rem" />
208 <p x-text="error()"></p>
208 <p>{{ error() }}</p>
209209 <button class="retry-btn" @click="fetchSites()">Retry</button>
210210 </div>
211211
@@ -219,9 +219,9 @@ onMount(() => {
219219 <input type="text" placeholder="Domain (optional)"
220220 x-value="newDomain()" @input="newDomain.set($event.target.value)"
221221 class="form-input" />
222 <button type="submit" class="create-btn" x-disabled="creating()" x-text="creating() ? 'Creating...' : 'Create'"></button>
222 <button type="submit" class="create-btn" x-disabled="creating()">{{ creating() ? 'Creating...' : 'Create' }}</button>
223223 </form>
224 <p x-show="createError()" class="create-error" x-text="createError()"></p>
224 <p x-show="createError()" class="create-error">{{ createError() }}</p>
225225 </div>
226226
227227 <div x-show="sites().length === 0" class="site-empty">
@@ -237,8 +237,8 @@ onMount(() => {
237237 <Icon name="clock" size="24" />
238238 </div>
239239 <div class="site-info">
240 <span class="site-name" x-text="site.name || 'Unnamed'"></span>
241 <span class="site-domain" x-text="site.domains?.[0] || site.id"></span>
240 <span class="site-name">{{ site.name || 'Unnamed' }}</span>
241 <span class="site-domain">{{ site.domains?.[0] || site.id }}</span>
242242 </div>
243243 <Icon name="chevron-right" size="20" class="site-arrow" />
244244 </button>
@@ -254,12 +254,12 @@ onMount(() => {
254254 <Icon name="globe" size="24" />
255255 </div>
256256 <div class="site-info">
257 <span class="site-name" x-text="site.name || 'Unnamed'"></span>
258 <span class="site-domain" x-text="site.domains?.[0] || site.id"></span>
257 <span class="site-name">{{ site.name || 'Unnamed' }}</span>
258 <span class="site-domain">{{ site.domains?.[0] || site.id }}</span>
259259 </div>
260260 <Icon name="chevron-right" size="20" class="site-arrow" />
261261 </button>
262 <p x-show="searchQuery() && filteredSites().length === 0" class="text-xs text-muted mt-2">No projects match "<span x-text="searchQuery()"></span>"</p>
262 <p x-show="searchQuery() && filteredSites().length === 0" class="text-xs text-muted mt-2">No projects match "<span></span>"</p>
263263 </div>
264264 </div>
265265 </div>
resources/components/dashboard/StatsRow.stxmodified+1-1
Changes to resources/components/dashboard/StatsRow.stx
@@ -145,7 +145,7 @@ onMount(() => {
145145 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="stat.icon"/>
146146 </svg>
147147 <div class="text-2xl font-bold text-text leading-tight" x-text="statValue(stat.id)">—</div>
148 <div class="text-xs text-text-2 mt-1 uppercase tracking-wide" x-text="stat.label"></div>
148 <div class="text-xs text-text-2 mt-1 uppercase tracking-wide">{{ stat.label }}</div>
149149 </div>
150150</div>
151151
resources/components/shared/DetailView.stxmodified+2-2
Changes to resources/components/shared/DetailView.stx
@@ -181,12 +181,12 @@ effect(() => {
181181 <table class="data-table w-full">
182182 <thead>
183183 <tr>
184 <th x-for="key in columnKeys" class="text-left p-3 text-muted font-medium text-sm border-b border-border" x-text="formatKey(key)"></th>
184 <th x-for="key in columnKeys" class="text-left p-3 text-muted font-medium text-sm border-b border-border">{{ formatKey(key) }}</th>
185185 </tr>
186186 </thead>
187187 <tbody>
188188 <tr x-for="item in paginatedData" class="hover:bg-bg-3">
189 <td x-for="key in columnKeys" class="p-3 text-sm border-b border-bg-3" x-text="formatValue(item[key], key)"></td>
189 <td x-for="key in columnKeys" class="p-3 text-sm border-b border-bg-3">{{ formatValue(item[key], key) }}</td>
190190 </tr>
191191 </tbody>
192192 </table>
resources/components/tabs/ErrorsTab.stxmodified+11-13
Changes to resources/components/tabs/ErrorsTab.stx
@@ -207,9 +207,7 @@ onMount(() => {
207207 <div class="flex gap-1 mb-4">
208208 <button x-for="r in dateRangeOptions"
209209 x-class="rangeBtnClass(r.id)"
210 @click="setErrorDateRange(r.id)"
211 x-text="r.label">
212 </button>
210 @click="setErrorDateRange(r.id)">{{ r.label }}</button>
213211 </div>
214212
215213 <div x-show="loading()" class="text-center text-text-2 py-10 px-8 bg-bg-2 border border-border rounded-lg flex flex-col items-center gap-2">
@@ -231,8 +229,8 @@ onMount(() => {
231229 <div x-show="!loading() && !error() && errors().length > 0">
232230 <div class="severity-grid grid grid-cols-4 gap-2 mb-3">
233231 <div x-for="sev in severityKeys" x-class="sevStatClass(sev)">
234 <span class="text-lg font-bold text-text" x-text="severityCounts()[sev]"></span>
235 <span class="text-[0.6875rem] text-muted uppercase tracking-wider" x-text="severityLabels[sev]"></span>
232 <span class="text-lg font-bold text-text">{{ severityCounts()[sev] }}</span>
233 <span class="text-[0.6875rem] text-muted uppercase tracking-wider">{{ severityLabels[sev] }}</span>
236234 </div>
237235 </div>
238236
@@ -275,24 +273,24 @@ onMount(() => {
275273 <div class="flex-1 min-w-0 py-3 px-4 flex flex-col gap-1.5">
276274 <div class="flex items-center justify-between gap-2">
277275 <div class="flex gap-1.5 items-center flex-wrap">
278 <span x-class="badgeSeverityClass(e)" x-text="severityLabels[e.severity] || 'Unknown'"></span>
279 <span class="text-[0.625rem] font-semibold py-0.5 px-[7px] rounded uppercase tracking-wide whitespace-nowrap bg-bg-3 text-text-2" x-text="e.category || 'Error'"></span>
280 <span x-class="badgeStatusClass(e)" x-text="statusLabels[getErrorStatus(e.fingerprint)] || 'New'"></span>
276 <span x-class="badgeSeverityClass(e)">{{ severityLabels[e.severity] || 'Unknown' }}</span>
277 <span class="text-[0.625rem] font-semibold py-0.5 px-[7px] rounded uppercase tracking-wide whitespace-nowrap bg-bg-3 text-text-2">{{ e.category || 'Error' }}</span>
278 <span x-class="badgeStatusClass(e)">{{ statusLabels[getErrorStatus(e.fingerprint)] || 'New' }}</span>
281279 <span x-show="isRegressed(e.fingerprint)" class="text-[0.625rem] font-semibold py-0.5 px-[7px] rounded uppercase tracking-wide whitespace-nowrap border border-error text-error">Regressed</span>
282280 </div>
283281 <span class="inline-flex items-center gap-1 text-[0.6875rem] font-semibold text-text-2 whitespace-nowrap">
284282 <Icon name="zap" size="12" class="text-muted" />
285 <span x-text="e.count"></span>
283 <span>{{ e.count }}</span>
286284 </span>
287285 </div>
288 <div class="text-[0.8125rem] text-text leading-relaxed overflow-hidden text-ellipsis line-clamp-2 break-words" x-text="e.message || 'Unknown error'"></div>
286 <div class="text-[0.8125rem] text-text leading-relaxed overflow-hidden text-ellipsis line-clamp-2 break-words">{{ e.message || 'Unknown error' }}</div>
289287 <div class="flex gap-3 items-center flex-wrap">
290288 <span class="inline-flex items-center gap-1 text-[0.6875rem] text-muted">
291289 <Icon name="clock" size="11" class="opacity-60" />
292 <span x-text="timeAgo(e.lastSeen)"></span>
290 <span>{{ timeAgo(e.lastSeen) }}</span>
293291 </span>
294 <span x-show="e.source" class="inline-flex items-center gap-1 text-[0.6875rem] text-muted font-mono text-text-2" x-text="e.source.split('/').pop() + (e.line ? ':' + e.line : '')"></span>
295 <span class="inline-flex items-center gap-1 text-[0.6875rem] text-muted" x-text="(e.browsers || []).join(', ') || 'server'"></span>
292 <span x-show="e.source" class="inline-flex items-center gap-1 text-[0.6875rem] text-muted font-mono text-text-2">{{ e.source.split('/').pop() + (e.line ? ':' + e.line : '') }}</span>
293 <span class="inline-flex items-center gap-1 text-[0.6875rem] text-muted">{{ (e.browsers || []).join(', ') || 'server' }}</span>
296294 </div>
297295 </div>
298296 <div class="error-actions flex flex-col gap-0.5 py-2 px-2 opacity-0 transition-opacity duration-150 items-center justify-center group-hover:opacity-100" @click.stop>
resources/components/tabs/FlowTab.stxmodified+8-8
Changes to resources/components/tabs/FlowTab.stx
@@ -61,8 +61,8 @@ onMount(() => {
6161 <h4 class="text-[0.6875rem] uppercase text-muted mb-3 font-medium tracking-wider">Entry Pages</h4>
6262 <div class="flex flex-col gap-2">
6363 <div x-for="page in entryPages" class="bg-bg border border-border py-2 px-3 rounded-md">
64 <div class="text-[0.8125rem] text-text truncate" x-text="page.path"></div>
65 <div class="text-[0.6875rem] text-muted" x-text="page.count + ' session' + (page.count !== 1 ? 's' : '')"></div>
64 <div class="text-[0.8125rem] text-text truncate">{{ page.path }}</div>
65 <div class="text-[0.6875rem] text-muted">{{ page.count + ' session' + (page.count !== 1 ? 's' : '') }}</div>
6666 </div>
6767 </div>
6868 </div>
@@ -71,10 +71,10 @@ onMount(() => {
7171 <h4 class="text-[0.6875rem] uppercase text-muted mb-3 font-medium tracking-wider">Top Flows</h4>
7272 <div class="flex flex-col gap-2">
7373 <div x-for="flow in topFlows" class="flex items-center gap-2 text-xs">
74 <span class="bg-bg py-1 px-2 rounded truncate max-w-[120px] text-text" x-text="flow.source"></span>
74 <span class="bg-bg py-1 px-2 rounded truncate max-w-[120px] text-text">{{ flow.source }}</span>
7575 <span class="text-accent">→</span>
76 <span class="bg-bg py-1 px-2 rounded truncate max-w-[120px] text-text" x-text="flow.target"></span>
77 <span class="text-muted ml-auto" x-text="flow.count"></span>
76 <span class="bg-bg py-1 px-2 rounded truncate max-w-[120px] text-text">{{ flow.target }}</span>
77 <span class="text-muted ml-auto">{{ flow.count }}</span>
7878 </div>
7979 </div>
8080 </div>
@@ -83,9 +83,9 @@ onMount(() => {
8383 <h4 class="text-[0.6875rem] uppercase text-muted mb-3 font-medium tracking-wider">Most Visited</h4>
8484 <div class="flex flex-col gap-2">
8585 <div x-for="(page, index) in mostVisited" class="flex items-center gap-2">
86 <span class="w-[18px] h-[18px] bg-accent text-white rounded-full text-[0.6875rem] flex items-center justify-center shrink-0" x-text="index + 1"></span>
87 <span class="text-xs truncate flex-1 text-text" x-text="page.path"></span>
88 <span class="text-[0.6875rem] text-muted" x-text="page.count"></span>
86 <span class="w-[18px] h-[18px] bg-accent text-white rounded-full text-[0.6875rem] flex items-center justify-center shrink-0">{{ index + 1 }}</span>
87 <span class="text-xs truncate flex-1 text-text">{{ page.path }}</span>
88 <span class="text-[0.6875rem] text-muted">{{ page.count }}</span>
8989 </div>
9090 </div>
9191 </div>
resources/components/tabs/FunnelsTab.stxmodified+6-6
Changes to resources/components/tabs/FunnelsTab.stx
@@ -193,11 +193,11 @@ onMount(() => {
193193 <div x-for="(step, index) in (currentAnalysis?.steps || [])" class="analysis-step-wrapper flex items-center gap-2 flex-1 min-w-[120px]">
194194 <div class="flex-1 text-center">
195195 <div class="bg-bg-2 border border-border rounded-lg p-4 mb-2">
196 <div class="text-2xl font-semibold text-text" x-text="step.visitors"></div>
197 <div class="text-[0.6875rem] text-muted mt-1"><span x-text="step.rate"></span>%</div>
196 <div class="text-2xl font-semibold text-text">{{ step.visitors }}</div>
197 <div class="text-[0.6875rem] text-muted mt-1"><span>{{ step.rate }}</span>%</div>
198198 </div>
199 <div class="text-xs font-medium text-text" x-text="step.path"></div>
200 <div x-show="step.dropoff > 0" class="text-[0.6875rem] text-error mt-1"><span x-text="'-' + step.dropoff + ' dropped'"></span></div>
199 <div class="text-xs font-medium text-text">{{ step.path }}</div>
200 <div x-show="step.dropoff > 0" class="text-[0.6875rem] text-error mt-1"><span>{{ '-' + step.dropoff + ' dropped' }}</span></div>
201201 </div>
202202 <div x-show="index < currentAnalysis().steps.length - 1" class="text-muted text-2xl">&rarr;</div>
203203 </div>
@@ -217,7 +217,7 @@ onMount(() => {
217217
218218 <div x-for="funnel in funnels" class="bg-bg-2 border border-border rounded-lg p-4 mb-3">
219219 <div class="flex justify-between items-center mb-3">
220 <h4 class="text-sm font-semibold text-text m-0" x-text="funnel.name"></h4>
220 <h4 class="text-sm font-semibold text-text m-0">{{ funnel.name }}</h4>
221221 <div class="flex gap-1">
222222 <button class="icon-btn" @click="analyzeFunnel(funnel.id)" title="View analysis">
223223 <Icon name="bar-chart-2" size="16" />
@@ -229,7 +229,7 @@ onMount(() => {
229229 </div>
230230 <div class="flex gap-2 flex-wrap items-center">
231231 <span x-for="(step, idx) in funnel.steps" class="text-[0.6875rem] py-1 px-2 bg-bg rounded text-text">
232 <span x-text="step"></span>
232 <span>{{ step }}</span>
233233 <span x-show="idx < funnel.steps.length - 1" class="text-accent">&rarr;</span>
234234 </span>
235235 </div>
resources/components/tabs/InsightsTab.stxmodified+2-2
Changes to resources/components/tabs/InsightsTab.stx
@@ -101,8 +101,8 @@ onMount(() => {
101101 </svg>
102102 </div>
103103 <div class="flex-1 min-w-0">
104 <div class="text-sm font-semibold text-text mb-1" x-text="insight.title"></div>
105 <div class="text-[0.8125rem] text-muted leading-normal" x-text="insight.description"></div>
104 <div class="text-sm font-semibold text-text mb-1">{{ insight.title }}</div>
105 <div class="text-[0.8125rem] text-muted leading-normal">{{ insight.description }}</div>
106106 </div>
107107 </div>
108108 </div>
resources/components/tabs/LiveTab.stxmodified+3-3
Changes to resources/components/tabs/LiveTab.stx
@@ -56,12 +56,12 @@ onMount(() => {
5656 <div x-for="activity in activities" @loading="loading()" class="panel flex items-center gap-4 py-3 px-4">
5757 <div class="w-2 h-2 bg-success rounded-full flex-shrink-0"></div>
5858 <div class="flex-1 min-w-0">
59 <div class="text-sm text-text truncate" x-text="activity.path ? activity.path : '/'"></div>
59 <div class="text-sm text-text truncate">{{ activity.path ? activity.path : '/' }}</div>
6060 <div class="text-[0.6875rem] text-muted">
61 <span x-text="(activity.country ? activity.country : 'Unknown') + ' • ' + (activity.device ? activity.device : 'Unknown') + ' • ' + (activity.browser ? activity.browser : 'Unknown')"></span>
61 <span>{{ (activity.country ? activity.country : 'Unknown') + ' • ' + (activity.device ? activity.device : 'Unknown') + ' • ' + (activity.browser ? activity.browser : 'Unknown') }}</span>
6262 </div>
6363 </div>
64 <div class="text-[0.6875rem] text-muted whitespace-nowrap" x-text="timeAgo(activity.timestamp)"></div>
64 <div class="text-[0.6875rem] text-muted whitespace-nowrap">{{ timeAgo(activity.timestamp) }}</div>
6565 </div>
6666 <div @for-loading class="panel flex items-center justify-center gap-2 text-muted">
6767 <Spinner size="sm" color="primary" />
resources/components/tabs/SessionsTab.stxmodified+6-6
Changes to resources/components/tabs/SessionsTab.stx
@@ -140,14 +140,14 @@ onMount(() => {
140140 <div x-show="!loading() && !error() && sessions().length === 0" class="empty-state">No sessions recorded yet.</div>
141141 <div x-for="session in sessions" class="session-card" @click="viewSession(session.sessionId || session.id)">
142142 <div class="flex justify-between items-center mb-2">
143 <span class="font-medium text-text" x-text="session.entryPage || session.entryPath || '/'"></span>
144 <span class="text-xs text-muted" x-text="timeAgo(session.startTime || session.startedAt)"></span>
143 <span class="font-medium text-text">{{ session.entryPage || session.entryPath || '/' }}</span>
144 <span class="text-xs text-muted">{{ timeAgo(session.startTime || session.startedAt) }}</span>
145145 </div>
146146 <div class="flex gap-4 text-xs text-muted">
147 <span x-text="(session.pageViews || session.pageViewCount || 1) + ' page' + ((session.pageViews || session.pageViewCount || 1) !== 1 ? 's' : '')"></span>
148 <span x-class="bouncedClass(session)" x-text="(session.bounced || session.isBounce) ? 'Bounced' : formatDuration(session.duration)"></span>
149 <span x-text="session.browser || 'Unknown'"></span>
150 <span x-text="session.country || 'Unknown'"></span>
147 <span>{{ (session.pageViews || session.pageViewCount || 1) + ' page' + ((session.pageViews || session.pageViewCount || 1) !== 1 ? 's' : '') }}</span>
148 <span x-class="bouncedClass(session)">{{ (session.bounced || session.isBounce) ? 'Bounced' : formatDuration(session.duration) }}</span>
149 <span>{{ session.browser || 'Unknown' }}</span>
150 <span>{{ session.country || 'Unknown' }}</span>
151151 </div>
152152 </div>
153153 </div>
resources/components/tabs/SettingsTab.stxmodified+20-20
Changes to resources/components/tabs/SettingsTab.stx
@@ -542,7 +542,7 @@ onMount(() => {
542542 </button>
543543 </div>
544544 </div>
545 <p class="text-xs text-muted mb-2">Paste this one line into the <code class="text-text">&lt;head&gt;</code> of every page you want to track (site <code class="text-text" x-text="siteId()"></code>). No cookies, no consent banner needed — visitors are never personally identified.</p>
545 <p class="text-xs text-muted mb-2">Paste this one line into the <code class="text-text">&lt;head&gt;</code> of every page you want to track (site <code class="text-text">{{ siteId() }}</code>). No cookies, no consent banner needed — visitors are never personally identified.</p>
546546 <div class="flex flex-wrap gap-1.5 mb-3">
547547 <span class="text-[0.6875rem] px-2 py-0.5 rounded-full bg-bg border border-border text-muted">Page views</span>
548548 <span class="text-[0.6875rem] px-2 py-0.5 rounded-full bg-bg border border-border text-muted">SPA route changes</span>
@@ -551,7 +551,7 @@ onMount(() => {
551551 <span class="text-[0.6875rem] px-2 py-0.5 rounded-full bg-bg border border-border text-muted">Web vitals</span>
552552 <span class="text-[0.6875rem] px-2 py-0.5 rounded-full bg-bg border border-border text-muted">JS errors</span>
553553 </div>
554 <pre class="bg-bg border border-border rounded-md p-3 text-[0.75rem] text-text overflow-x-auto whitespace-pre-wrap break-all font-mono leading-relaxed" x-text="installSnippet()"></pre>
554 <pre class="bg-bg border border-border rounded-md p-3 text-[0.75rem] text-text overflow-x-auto whitespace-pre-wrap break-all font-mono leading-relaxed">{{ installSnippet() }}</pre>
555555 <p x-show="!installSnippet()" class="text-xs text-muted italic mt-2">Loading snippet…</p>
556556 <p x-show="verifyResult() === 'pass'" class="text-xs text-success mt-2">Test event received — ingestion is working end to end.</p>
557557 <p x-show="verifyResult() === 'fail'" class="text-xs text-error mt-2">No event received. Check that the API is reachable from your browser, then try again.</p>
@@ -564,7 +564,7 @@ onMount(() => {
564564 </button>
565565 </div>
566566 <p class="text-xs text-muted mb-2">Sends beacons to a short, unlisted path that generic blocklists miss. For full resilience, serve it from your own domain — see the <a class="text-primary underline" href="https://github.com/stacksjs/ts-analytics/blob/main/docs/guide/tracking-script.md#ad-blocker-resilience-first-party-proxy" target="_blank" rel="noopener">first-party proxy guide</a>.</p>
567 <pre class="bg-bg border border-border rounded-md p-3 text-[0.75rem] text-text overflow-x-auto whitespace-pre-wrap break-all font-mono leading-relaxed" x-text="stealthSnippet()"></pre>
567 <pre class="bg-bg border border-border rounded-md p-3 text-[0.75rem] text-text overflow-x-auto whitespace-pre-wrap break-all font-mono leading-relaxed">{{ stealthSnippet() }}</pre>
568568 </div>
569569 </div>
570570
@@ -580,17 +580,17 @@ onMount(() => {
580580 <span x-show="!sdkCopied()">Copy snippet</span>
581581 <span x-show="sdkCopied()" class="text-success">Copied!</span>
582582 </button>
583 <button class="btn btn-secondary text-[0.6875rem] py-1 px-2 hover:text-error" @click="regenerateDsn()" x-disabled="regeneratingDsn()" x-text="regeneratingDsn() ? 'Regenerating…' : 'Regenerate key'"></button>
583 <button class="btn btn-secondary text-[0.6875rem] py-1 px-2 hover:text-error" @click="regenerateDsn()" x-disabled="regeneratingDsn()">{{ regeneratingDsn() ? 'Regenerating…' : 'Regenerate key' }}</button>
584584 </div>
585585 </div>
586586 <p class="text-xs text-muted mb-3">The analytics snippet above already reports uncaught errors. Use this DSN with the standalone SDK when you want error tracking without analytics, or richer capture (breadcrumbs, releases, <code class="text-text">captureException</code>) in your app code.</p>
587587 <div class="mb-3">
588588 <label class="text-xs font-medium text-muted mb-1 block">DSN</label>
589 <code class="block bg-bg border border-border rounded-md p-3 text-[0.75rem] text-text break-all font-mono" x-text="dsn()"></code>
589 <code class="block bg-bg border border-border rounded-md p-3 text-[0.75rem] text-text break-all font-mono">{{ dsn() }}</code>
590590 </div>
591591 <div>
592592 <label class="text-xs font-medium text-muted mb-1 block">SDK snippet</label>
593 <pre class="bg-bg border border-border rounded-md p-3 text-[0.75rem] text-text overflow-x-auto whitespace-pre-wrap break-all font-mono leading-relaxed" x-text="sdkSnippet()"></pre>
593 <pre class="bg-bg border border-border rounded-md p-3 text-[0.75rem] text-text overflow-x-auto whitespace-pre-wrap break-all font-mono leading-relaxed">{{ sdkSnippet() }}</pre>
594594 </div>
595595 <p x-show="!dsn()" class="text-xs text-muted italic mt-2">Loading DSN…</p>
596596 </div>
@@ -619,8 +619,8 @@ onMount(() => {
619619 <div class="flex flex-col gap-2">
620620 <div x-show="settings().apiKey" class="flex justify-between items-center p-2 bg-bg rounded">
621621 <div class="flex-1 min-w-0">
622 <div class="text-[0.8125rem] font-medium text-text" x-text="settings().apiKey ? settings().apiKey.name : ''"></div>
623 <code class="text-[0.6875rem] text-muted break-all" x-text="settings().apiKey ? settings().apiKey.key : ''"></code>
622 <div class="text-[0.8125rem] font-medium text-text">{{ settings().apiKey ? settings().apiKey.name : '' }}</div>
623 <code class="text-[0.6875rem] text-muted break-all">{{ settings().apiKey ? settings().apiKey.key : '' }}</code>
624624 </div>
625625 <div class="flex items-center gap-1">
626626 <button class="icon-btn" @click="copyApiKey()" title="Copy key">
@@ -643,8 +643,8 @@ onMount(() => {
643643 <p x-show="settings().alerts.length === 0" class="text-xs text-muted italic">No alerts configured.</p>
644644 <div x-for="alert in settings.alerts" class="flex justify-between items-center p-2 bg-bg rounded">
645645 <div class="flex-1 min-w-0">
646 <div class="text-[0.8125rem] font-medium text-text" x-text="alert.metric"></div>
647 <div class="text-[0.6875rem] text-muted" x-text="'Threshold: ' + alert.threshold + ' → ' + alert.email"></div>
646 <div class="text-[0.8125rem] font-medium text-text">{{ alert.metric }}</div>
647 <div class="text-[0.6875rem] text-muted">{{ 'Threshold: ' + alert.threshold + ' → ' + alert.email }}</div>
648648 </div>
649649 <button class="icon-btn hover:bg-error/15 hover:text-error" @click="deleteAlert(alert.id)" title="Delete">×</button>
650650 </div>
@@ -661,8 +661,8 @@ onMount(() => {
661661 <p x-show="settings().emailReports.length === 0" class="text-xs text-muted italic">No email reports scheduled.</p>
662662 <div x-for="report in settings.emailReports" class="flex justify-between items-center p-2 bg-bg rounded">
663663 <div class="flex-1 min-w-0">
664 <div class="text-[0.8125rem] font-medium text-text" x-text="report.email"></div>
665 <div class="text-[0.6875rem] text-muted" x-text="report.frequency"></div>
664 <div class="text-[0.8125rem] font-medium text-text">{{ report.email }}</div>
665 <div class="text-[0.6875rem] text-muted">{{ report.frequency }}</div>
666666 </div>
667667 <button class="icon-btn hover:bg-error/15 hover:text-error" @click="deleteEmailReport(report.id)" title="Delete">×</button>
668668 </div>
@@ -679,8 +679,8 @@ onMount(() => {
679679 <p x-show="settings().uptimeMonitors.length === 0" class="text-xs text-muted italic">No uptime monitors configured.</p>
680680 <div x-for="monitor in settings.uptimeMonitors" class="flex justify-between items-center p-2 bg-bg rounded">
681681 <div class="flex-1 min-w-0">
682 <code class="text-[0.6875rem] text-muted break-all" x-text="monitor.url"></code>
683 <div class="text-[0.6875rem] text-muted" x-text="'Every ' + monitor.interval + 'min • ' + (monitor.status || 'Pending')"></div>
682 <code class="text-[0.6875rem] text-muted break-all">{{ monitor.url }}</code>
683 <div class="text-[0.6875rem] text-muted">{{ 'Every ' + monitor.interval + 'min • ' + (monitor.status || 'Pending') }}</div>
684684 </div>
685685 <button class="icon-btn hover:bg-error/15 hover:text-error" @click="deleteUptimeMonitor(monitor.id)" title="Delete">×</button>
686686 </div>
@@ -700,8 +700,8 @@ onMount(() => {
700700 <p x-show="settings().teamMembers.length === 0" class="text-xs text-muted italic">No team members added.</p>
701701 <div x-for="member in settings.teamMembers" class="flex justify-between items-center p-2 bg-bg rounded">
702702 <div class="flex-1 min-w-0">
703 <div class="text-[0.8125rem] font-medium text-text" x-text="member.email"></div>
704 <div class="text-[0.6875rem] text-muted" x-text="member.role"></div>
703 <div class="text-[0.8125rem] font-medium text-text">{{ member.email }}</div>
704 <div class="text-[0.6875rem] text-muted">{{ member.role }}</div>
705705 </div>
706706 <button class="icon-btn hover:bg-error/15 hover:text-error" @click="removeTeamMember(member.id)" title="Remove">×</button>
707707 </div>
@@ -718,8 +718,8 @@ onMount(() => {
718718 <p x-show="settings().webhooks.length === 0" class="text-xs text-muted italic">No webhooks configured.</p>
719719 <div x-for="webhook in settings.webhooks" class="flex justify-between items-center p-2 bg-bg rounded">
720720 <div class="flex-1 min-w-0">
721 <code class="text-[0.6875rem] text-muted break-all" x-text="webhook.url"></code>
722 <div class="text-[0.6875rem] text-muted" x-text="(webhook.events || []).join(', ')"></div>
721 <code class="text-[0.6875rem] text-muted break-all">{{ webhook.url }}</code>
722 <div class="text-[0.6875rem] text-muted">{{ (webhook.events || []).join(', ') }}</div>
723723 </div>
724724 <button class="icon-btn hover:bg-error/15 hover:text-error" @click="deleteWebhook(webhook.id)" title="Delete">×</button>
725725 </div>
@@ -736,8 +736,8 @@ onMount(() => {
736736 <p x-show="settings().perfBudgets.length === 0" class="text-xs text-muted italic">No performance budgets set.</p>
737737 <div x-for="budget in settings.perfBudgets" class="flex justify-between items-center p-2 bg-bg rounded">
738738 <div class="flex-1 min-w-0">
739 <div class="text-[0.8125rem] font-medium text-text" x-text="budget.metric"></div>
740 <div class="text-[0.6875rem] text-muted" x-text="'Threshold: ' + budget.threshold + (budget.metric === 'CLS' ? '' : 'ms')"></div>
739 <div class="text-[0.8125rem] font-medium text-text">{{ budget.metric }}</div>
740 <div class="text-[0.6875rem] text-muted">{{ 'Threshold: ' + budget.threshold + (budget.metric === 'CLS' ? '' : 'ms') }}</div>
741741 </div>
742742 <button class="icon-btn hover:bg-error/15 hover:text-error" @click="deletePerfBudget(budget.id)" title="Delete">×</button>
743743 </div>
resources/components/tabs/VitalsTab.stxmodified+6-6
Changes to resources/components/tabs/VitalsTab.stx
@@ -67,18 +67,18 @@ onMount(() => {
6767 <h4 class="text-sm font-semibold text-error mb-2">Performance Budget Violations</h4>
6868 <div class="flex flex-col gap-2">
6969 <div x-for="v in violations" class="text-[0.8125rem] flex gap-2 items-center">
70 <span class="font-semibold text-text" x-text="v.metric"></span>
71 <span class="text-error" x-text="v.currentValue + (v.metric === 'CLS' ? '' : 'ms')"></span>
72 <span class="text-muted" x-text="'(exceeds ' + v.threshold + (v.metric === 'CLS' ? '' : 'ms') + ' by ' + v.exceededBy + (v.metric === 'CLS' ? '' : 'ms') + ')'"></span>
70 <span class="font-semibold text-text">{{ v.metric }}</span>
71 <span class="text-error">{{ v.currentValue + (v.metric === 'CLS' ? '' : 'ms') }}</span>
72 <span class="text-muted">{{ '(exceeds ' + v.threshold + (v.metric === 'CLS' ? '' : 'ms') + ' by ' + v.exceededBy + (v.metric === 'CLS' ? '' : 'ms') + ')' }}</span>
7373 </div>
7474 </div>
7575 </div>
7676
7777 <div class="vitals-grid">
7878 <div x-for="v in vitals" class="vital-card">
79 <div class="text-xs font-semibold text-muted uppercase tracking-wider mb-1" x-text="v.metric"></div>
80 <div x-class="vitalValueClass(v)" x-text="v.samples > 0 ? formatValue(v.metric, v.p75) : '—'"></div>
81 <div class="text-[0.6875rem] text-muted mb-2" x-text="(v.samples || 0) + ' samples'"></div>
79 <div class="text-xs font-semibold text-muted uppercase tracking-wider mb-1">{{ v.metric }}</div>
80 <div x-class="vitalValueClass(v)">{{ v.samples > 0 ? formatValue(v.metric, v.p75) : '—' }}</div>
81 <div class="text-[0.6875rem] text-muted mb-2">{{ (v.samples || 0) + ' samples' }}</div>
8282 <div x-show="v.samples > 0" class="h-1 rounded-sm flex overflow-hidden bg-bg">
8383 <div class="good" x-style="'width: ' + v.good + '%'"></div>
8484 <div class="needs-improvement" x-style="'width: ' + v.needsImprovement + '%'"></div>