Skip to content

Commit df38963

Browse files
authored
Merge branch 'main' into renovate/npm-happy-dom-vulnerability
2 parents 5b0b95f + c02d3af commit df38963

File tree

87 files changed

+11989
-1065
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+11989
-1065
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ jobs:
77
ci:
88
runs-on: ubuntu-latest
99
steps:
10-
- uses: actions/checkout@v4
11-
- uses: pnpm/action-setup@v4
12-
- uses: actions/setup-node@v4
10+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
11+
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
12+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
1313
with:
14-
node-version: 22
14+
node-version: 24
1515
cache: pnpm
1616
- run: pnpm install --frozen-lockfile
1717
- run: pnpm run typecheck

.github/workflows/deploy.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ jobs:
99
deploy:
1010
runs-on: ubuntu-latest
1111
steps:
12-
- uses: actions/checkout@v4
13-
- uses: pnpm/action-setup@v4
14-
- uses: actions/setup-node@v4
12+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
13+
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
14+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
1515
with:
16-
node-version: 22
16+
node-version: 24
1717
cache: pnpm
1818
- run: pnpm install --frozen-lockfile
1919
- run: pnpm run typecheck
@@ -25,7 +25,7 @@ jobs:
2525
- run: pnpm run build
2626
env:
2727
VITE_GITHUB_CLIENT_ID: ${{ vars.VITE_GITHUB_CLIENT_ID }}
28-
- uses: cloudflare/wrangler-action@v3
28+
- uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
2929
with:
3030
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
3131
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ Dashboard SPA tracking GitHub issues, PRs, and GHA workflow runs across multiple
44

55
## Features
66

7-
- **Issues Tab** — Open issues where you're the creator, assignee, or mentioned. Sortable, filterable, paginated.
7+
- **Issues Tab** — Open issues where you're the creator, assignee, or mentioned. Sortable, filterable, paginated. Dependency Dashboard issues hidden by default (toggleable).
88
- **Pull Requests Tab** — Open PRs with CI check status indicators (green/yellow/red dots). Draft badges, reviewer names.
99
- **Actions Tab** — GHA workflow runs grouped by repo and workflow. Accordion collapse, PR run toggle.
1010
- **Onboarding Wizard** — Two-step org/repo selection with search filtering and bulk select.
11-
- **Settings Page** — Refresh interval, notification preferences, theme (light/dark/system), density, GitHub Actions limits.
11+
- **PAT Authentication** — Optional Personal Access Token login as alternative to OAuth. Client-side format validation, detailed token creation instructions for classic and fine-grained PATs.
12+
- **Settings Page** — Refresh interval, notification preferences, theme (light/dark/system), density, GitHub Actions limits. Shows current auth method and hides OAuth-specific options for PAT users.
1213
- **Desktop Notifications** — New item alerts with per-type toggles and batching.
1314
- **Ignore System** — Hide specific items with an "N ignored" badge and unignore popover.
1415
- **Dark Mode** — System-aware with flash prevention via inline script + CSP SHA-256 hash.
@@ -30,7 +31,7 @@ Dashboard SPA tracking GitHub issues, PRs, and GHA workflow runs across multiple
3031
```sh
3132
pnpm install
3233
pnpm run dev # Start Vite dev server
33-
pnpm test # Run browser tests (130 tests)
34+
pnpm test # Run unit/component tests
3435
pnpm run typecheck # TypeScript check
3536
pnpm run build # Production build (~241KB JS, ~31KB CSS)
3637
```
@@ -57,6 +58,7 @@ src/
5758
config.ts # Zod v4-validated config with localStorage persistence
5859
view.ts # View state (tabs, sorting, ignored items, filters)
5960
lib/
61+
pat.ts # PAT format validation and token creation instruction constants
6062
notifications.ts # Desktop notification permission, detection, and dispatch
6163
worker/
6264
index.ts # OAuth token exchange endpoint, CORS, security headers
@@ -72,6 +74,7 @@ tests/
7274
## Security
7375

7476
- Strict CSP: `script-src 'self'` (SHA-256 exception for dark mode script only)
77+
- PAT tokens stored in `localStorage` (same key as OAuth tokens) — single-user personal dashboard threat model
7578
- OAuth CSRF protection via `crypto.getRandomValues` state parameter
7679
- CORS locked to exact origin (strict equality, no substring matching)
7780
- Access token stored in `localStorage` under app-specific key; CSP prevents XSS token theft

e2e/settings.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ async function setupAuth(page: Page) {
2525
json: {
2626
data: {
2727
search: { issueCount: 0, pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] },
28-
rateLimit: { remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
28+
rateLimit: { limit: 5000, remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
2929
},
3030
},
3131
})

e2e/smoke.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ async function setupAuth(page: Page) {
3434
json: {
3535
data: {
3636
search: { issueCount: 0, pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] },
37-
rateLimit: { remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
37+
rateLimit: { limit: 5000, remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
3838
},
3939
},
4040
})
@@ -112,7 +112,7 @@ test("OAuth callback flow completes and redirects", async ({ page }) => {
112112
json: {
113113
data: {
114114
search: { issueCount: 0, pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] },
115-
rateLimit: { remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
115+
rateLimit: { limit: 5000, remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
116116
},
117117
},
118118
})

package.json

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,33 @@
1616
"test:waf": "bash scripts/waf-smoke-test.sh"
1717
},
1818
"dependencies": {
19-
"@kobalte/core": "^0.13.11",
20-
"@octokit/core": "^7.0.6",
21-
"@octokit/plugin-paginate-rest": "^14.0.0",
22-
"@octokit/plugin-retry": "^8.1.0",
23-
"@octokit/plugin-throttling": "^11.0.3",
24-
"@sentry/solid": "^10.46.0",
25-
"@solidjs/router": "^0.16.1",
26-
"corvu": "^0.7.2",
27-
"idb": "^8.0.3",
28-
"solid-js": "^1.9.11",
29-
"zod": "^4.3.6"
19+
"@kobalte/core": "0.13.11",
20+
"@octokit/core": "7.0.6",
21+
"@octokit/plugin-paginate-rest": "14.0.0",
22+
"@octokit/plugin-retry": "8.1.0",
23+
"@octokit/plugin-throttling": "11.0.3",
24+
"@sentry/solid": "10.46.0",
25+
"@solidjs/router": "0.16.1",
26+
"corvu": "0.7.2",
27+
"idb": "8.0.3",
28+
"solid-js": "1.9.11",
29+
"zod": "4.3.6"
3030
},
3131
"devDependencies": {
32-
"@cloudflare/vite-plugin": "^1.30.0",
33-
"@cloudflare/vitest-pool-workers": "^0.13.3",
34-
"@playwright/test": "^1.58.2",
35-
"@solidjs/testing-library": "^0.8.10",
36-
"@tailwindcss/vite": "^4.2.2",
37-
"@testing-library/user-event": "^14.6.1",
38-
"daisyui": "^5.5.19",
39-
"fake-indexeddb": "^6.2.5",
32+
"@cloudflare/vite-plugin": "1.30.0",
33+
"@cloudflare/vitest-pool-workers": "0.13.3",
34+
"@playwright/test": "1.58.2",
35+
"@solidjs/testing-library": "0.8.10",
36+
"@tailwindcss/vite": "4.2.2",
37+
"@testing-library/user-event": "14.6.1",
38+
"daisyui": "5.5.19",
39+
"fake-indexeddb": "6.2.5",
4040
"happy-dom": "^20.8.4",
41-
"tailwindcss": "^4.2.2",
42-
"typescript": "^5.9.3",
43-
"vite": "^8.0.1",
44-
"vite-plugin-solid": "^2.11.11",
45-
"vitest": "^4.1.0",
46-
"wrangler": "^4.76.0"
41+
"tailwindcss": "4.2.2",
42+
"typescript": "5.9.3",
43+
"vite": "8.0.1",
44+
"vite-plugin-solid": "2.11.11",
45+
"vitest": "4.1.0",
46+
"wrangler": "4.76.0"
4747
}
4848
}

pnpm-lock.yaml

Lines changed: 27 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/_headers

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
2-
Content-Security-Policy: default-src 'none'; script-src 'self' 'sha256-uEFqyYCMaNy1Su5VmWLZ1hOCRBjkhm4+ieHHxQW6d3Y='; style-src-elem 'self'; style-src-attr 'unsafe-inline'; img-src 'self' data: https://avatars.githubusercontent.com; connect-src 'self' https://api.github.com; font-src 'self'; worker-src 'self'; manifest-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'none'; upgrade-insecure-requests
2+
Content-Security-Policy: default-src 'none'; script-src 'self' 'sha256-uEFqyYCMaNy1Su5VmWLZ1hOCRBjkhm4+ieHHxQW6d3Y='; style-src-elem 'self'; style-src-attr 'unsafe-inline'; img-src 'self' data: https://avatars.githubusercontent.com; connect-src 'self' https://api.github.com; font-src 'self'; worker-src 'self'; manifest-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'none'; upgrade-insecure-requests; report-uri /api/csp-report; report-to csp-endpoint
3+
Reporting-Endpoints: csp-endpoint="/api/csp-report"
34
X-Content-Type-Options: nosniff
45
Referrer-Policy: strict-origin-when-cross-origin
56
Permissions-Policy: geolocation=(), microphone=(), camera=()

src/app/App.tsx

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,43 @@
1-
import { createSignal, createEffect, onMount, Show, type JSX } from "solid-js";
1+
import { createSignal, createEffect, onMount, Show, ErrorBoundary, Suspense, lazy, type JSX } from "solid-js";
22
import { Router, Route, Navigate, useNavigate } from "@solidjs/router";
3-
import { isAuthenticated, validateToken } from "./stores/auth";
3+
import { isAuthenticated, validateToken, AUTH_STORAGE_KEY } from "./stores/auth";
44
import { config, initConfigPersistence, resolveTheme } from "./stores/config";
55
import { initViewPersistence } from "./stores/view";
66
import { evictStaleEntries } from "./stores/cache";
77
import { initClientWatcher } from "./services/github";
88
import LoginPage from "./pages/LoginPage";
99
import OAuthCallback from "./pages/OAuthCallback";
10-
import DashboardPage from "./components/dashboard/DashboardPage";
11-
import OnboardingWizard from "./components/onboarding/OnboardingWizard";
12-
import SettingsPage from "./components/settings/SettingsPage";
1310
import PrivacyPage from "./pages/PrivacyPage";
1411

12+
const DashboardPage = lazy(() => import("./components/dashboard/DashboardPage"));
13+
const OnboardingWizard = lazy(() => import("./components/onboarding/OnboardingWizard"));
14+
const SettingsPage = lazy(() => import("./components/settings/SettingsPage"));
15+
16+
function handleRouteError(err: unknown) {
17+
console.error("[app] Route render failed:", err);
18+
return <ChunkErrorFallback />;
19+
}
20+
21+
function ChunkErrorFallback() {
22+
return (
23+
<div class="min-h-screen flex items-center justify-center bg-base-200">
24+
<div class="card bg-base-100 shadow-md p-8 flex flex-col items-center gap-4 max-w-sm">
25+
<p class="text-error font-medium">Failed to load page</p>
26+
<p class="text-sm text-base-content/60 text-center">
27+
A new version may have been deployed. Reloading should fix this.
28+
</p>
29+
<button
30+
type="button"
31+
class="btn btn-neutral"
32+
onClick={() => window.location.reload()}
33+
>
34+
Reload page
35+
</button>
36+
</div>
37+
</div>
38+
);
39+
}
40+
1541
// Auth guard: redirects unauthenticated users to /login.
1642
// On page load, validates the localStorage token with GitHub API.
1743
function AuthGuard(props: { children: JSX.Element }) {
@@ -138,17 +164,33 @@ export default function App() {
138164
evictStaleEntries(24 * 60 * 60 * 1000).catch(() => {
139165
// Non-fatal — stale eviction failure is acceptable
140166
});
167+
168+
// Preload dashboard chunk in parallel with token validation to avoid
169+
// a sequential waterfall (validateToken → chunk fetch)
170+
if (localStorage.getItem?.(AUTH_STORAGE_KEY)) {
171+
import("./components/dashboard/DashboardPage").catch(() => {
172+
console.warn("[app] Dashboard chunk preload failed");
173+
});
174+
}
141175
});
142176

143177
return (
144-
<Router>
145-
<Route path="/" component={RootRedirect} />
146-
<Route path="/login" component={LoginPage} />
147-
<Route path="/oauth/callback" component={OAuthCallback} />
148-
<Route path="/onboarding" component={() => <AuthGuard><OnboardingWizard /></AuthGuard>} />
149-
<Route path="/dashboard" component={() => <AuthGuard><DashboardPage /></AuthGuard>} />
150-
<Route path="/settings" component={() => <AuthGuard><SettingsPage /></AuthGuard>} />
151-
<Route path="/privacy" component={PrivacyPage} />
152-
</Router>
178+
<ErrorBoundary fallback={handleRouteError}>
179+
<Suspense fallback={
180+
<div class="min-h-screen flex items-center justify-center bg-base-200">
181+
<span class="loading loading-spinner loading-lg" aria-label="Loading" />
182+
</div>
183+
}>
184+
<Router>
185+
<Route path="/" component={RootRedirect} />
186+
<Route path="/login" component={LoginPage} />
187+
<Route path="/oauth/callback" component={OAuthCallback} />
188+
<Route path="/onboarding" component={() => <AuthGuard><OnboardingWizard /></AuthGuard>} />
189+
<Route path="/dashboard" component={() => <AuthGuard><DashboardPage /></AuthGuard>} />
190+
<Route path="/settings" component={() => <AuthGuard><SettingsPage /></AuthGuard>} />
191+
<Route path="/privacy" component={PrivacyPage} />
192+
</Router>
193+
</Suspense>
194+
</ErrorBoundary>
153195
);
154196
}

0 commit comments

Comments
 (0)