Skip to content

Conversation

@trevwilson
Copy link
Owner

@trevwilson trevwilson commented Jan 21, 2026

Summary

  • Add CI workflow with backend build/unit tests/integration tests, frontend lint/typecheck/tests, and E2E tests
  • Add CodeQL security scanning for C# and JavaScript/TypeScript
  • Add Dependabot for automated dependency updates (npm, NuGet, GitHub Actions)

Test plan

  • Verify CI workflow triggers on PR
  • Verify all jobs pass (backend, frontend, e2e)
  • Verify CodeQL analysis runs

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Added automated dependency updates, CI and CodeQL workflows; migrated developer scripts to pnpm and updated frontend build/lint tooling and dev dependencies.
  • Bug Fixes / Reliability

    • Stabilized E2E tests with shared servers and guarded migrations; improved form initialization and keyboard confirm reliability.
  • User-facing

    • Improved Electron sign-in/out flow and sign-in UI; introduced a dedicated Electron auth context. Recurrence now preserves unspecified interval/occurrence values.
  • Documentation

    • Updated developer docs and data-flow diagram to reflect pnpm and CI changes.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

Adds GitHub automation (Dependabot, CI, CodeQL); extracts Electron auth into a dedicated context and updates frontend auth flows; stabilizes E2E tests by sharing API/Vite servers and applying migrations once; removes recurrence default fallbacks; several frontend lint, memoization, import, and small logic tweaks.

Changes

Cohort / File(s) Summary
GitHub automation
/.github/dependabot.yml, /.github/workflows/ci.yml, /.github/workflows/codeql.yml
Add Dependabot config; add CI workflow with Backend/Frontend/E2E jobs; add CodeQL workflow (dispatch-only, commented triggers).
Frontend auth extraction
frontend/src/components/auth/useElectronAuth.ts, frontend/src/components/auth/ProtectedLayout.tsx, frontend/src/components/auth/ElectronAuthPage.tsx
Create ElectronAuthContext + useElectronAuth; move Electron auth logic out of ProtectedLayout; short-circuit Electron-specific flows in ElectronAuthPage.
E2E infra: shared servers & migrations
tests/OpenGTD.E2E/GtdAppFixture.cs
Replace per-test startups with shared API and Vite servers, add locks and idempotent migration, centralized DB clearing, and reuse lifecycle across tests.
Recurrence behavior changes
frontend/src/features/recurrence/recurrenceUtils.ts, frontend/src/features/recurrence/useRecurrence.ts
Remove default fallbacks for occurrenceCount and fixedInterval; propagate null/undefined instead of substituting defaults.
Memoization & UI stability
frontend/src/features/contexts/useContexts.ts, frontend/src/features/projects/useProjects.ts, frontend/src/features/processing/ProcessPage.tsx
Add useMemo for stable mapped lists; stabilize confirm handler via ref; prevent redundant form re-initialization; adjust effect deps.
Actions & lint tweaks
frontend/src/features/actions/useActions.ts, frontend/src/features/actions/EditActionModal.tsx
Allow null priority (removed fallback); add ESLint suppression comments around specific useEffect blocks.
Electron API & PowerSync adjustments
frontend/src/lib/electron.ts, frontend/src/lib/powersync/connector.ts, frontend/src/lib/powersync/syncRules.test.ts, frontend/src/lib/powersync/schema.ts
Replace some optional-chaining with direct property access for Electron auth calls; use results.at(0) for selection; change enum detection; remove optional chaining in YAML test; minor import reorder.
Tooling, deps & docs
frontend/eslint.config.js, frontend/package.json, frontend/vite.config.ts, CLAUDE.md, docs/DATA_FLOW.md
Add eslint-plugin-react-compiler/babel-plugin-react-compiler; configure Vite to load the Babel plugin; switch docs/commands to pnpm; add CI/pre-commit snippets; tweak ASCII diagram.
Tests & import tidy
frontend/src/features/recurrence/*, frontend/src/lib/powersync/*
Import reorderings and ESLint suppression in tests; mostly non-functional tidy changes.
Minor reorders & small refactors
multiple frontend/src/... files
Several import reorderings, useMemo additions, effect dependency tweaks, and small stability edits across frontend modules.

Sequence Diagram(s)

sequenceDiagram
  participant Test as TestWorker
  participant Fixture as GtdAppFixture
  participant API as API Server
  participant DB as Database
  participant Vite as Vite Dev Server

  Test->>Fixture: StartAsync()
  alt API not started
    Fixture->>API: acquire _apiServerLock & start API
    API->>DB: apply migrations (guarded by _migrationLock)
    DB-->>API: migrations applied
    Fixture-->>API: release _apiServerLock
  else API already started
    Fixture-->>API: reuse shared API
  end
  alt Vite not started
    Fixture->>Vite: acquire _viteServerLock & start Vite dev server
    Vite-->>Fixture: return base URL
  else
    Fixture-->>Vite: reuse shared Vite
  end
  Fixture->>DB: ClearDatabaseAsync (guarded)
  Fixture-->>Test: return readiness & BaseUrl
  Test->>API: run E2E tests against shared servers
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Feature: recurring tasks #1 — Overlapping recurrence feature changes touching recurrence models, migrations, frontend recurrence components, and related tests.

Poem

🐰 I hopped through pipelines, neat and spry,
Dependabot nibbling updates nearby,
Auth found a burrow, tidy in a hut,
Migrations run once — no duplicate rut,
Tests now share servers — CI carrots, what a delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: adding GitHub Actions CI workflow with testing, linting, and CodeQL security scanning.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@trevwilson trevwilson force-pushed the claude-session/feature-github-actions-ci-20260120-2336 branch from 9931e4e to d135f83 Compare January 21, 2026 05:33
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.github/workflows/ci.yml:
- Around line 97-100: Remove the deprecated dotnet tool installation for
Microsoft.Playwright.CLI: delete the line that runs "dotnet tool install
--global Microsoft.Playwright.CLI" and keep the existing "playwright install
--with-deps chromium" step (or remove it if Playwright is already installed
after the project's "dotnet build"); ensure the workflow relies on the
Playwright CLI provided by the Microsoft.Playwright.MSTest.v4 package made
available after the "dotnet build" step so the CI uses the built-in "playwright"
command instead of installing the deprecated tool.

In @.github/workflows/codeql.yml:
- Around line 30-34: Update the GitHub Actions step that uses
actions/setup-dotnet@v4: change the dotnet-version value from '10.0.x' to a
currently available SDK (e.g., '9.0.x') so the C# matrix job can run; locate the
step named "Setup .NET" and update the dotnet-version input for the
actions/setup-dotnet@v4 action accordingly.
🧹 Nitpick comments (2)
.github/dependabot.yml (1)

1-28: Well-structured Dependabot configuration.

The grouping of minor/patch updates for npm and NuGet is a good practice to reduce PR noise. Consider also adding a group for GitHub Actions updates for consistency:

  # GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
+   groups:
+     actions-updates:
+       patterns: ["*"]
.github/workflows/ci.yml (1)

14-35: Consider adding NuGet caching for faster builds.

The backend job could benefit from NuGet package caching to speed up dependency restoration:

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'
+         cache: true
+         cache-dependency-path: '**/packages.lock.json'

Note: This requires RestorePackagesWithLockFile to be enabled in your .NET projects, or you can use actions/cache with the NuGet packages directory.

@trevwilson trevwilson force-pushed the claude-session/feature-github-actions-ci-20260120-2336 branch from 535c66a to ef931fd Compare January 21, 2026 06:06
trevwilson and others added 6 commits January 21, 2026 10:20
- CI workflow: backend build/tests, frontend lint/typecheck/tests, E2E tests
- CodeQL security scanning for C# and TypeScript
- Dependabot for automated dependency updates (npm, NuGet, Actions)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CI/CD:
- Add CI workflow with backend/frontend/E2E jobs
- Add CodeQL security scanning for C# and TypeScript
- Add Dependabot for npm, NuGet, and Actions updates

Frontend:
- Migrate from npm to pnpm (faster installs, stricter deps)
- Fix ESLint errors: extract useElectronAuth hook, fix import order
- Fix syncRules.test.ts to handle path reference in config

Docs:
- Remove outdated migration status from CLAUDE.md
- Add pre-commit verification section
- Update all npm commands to pnpm

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use built-in Playwright CLI from MSTest package instead of deprecated global tool
- Add grouping for GitHub Actions in Dependabot config
- Add migration lock to prevent parallel test classes from racing
- Skip migration if already applied (for parallel workers)
- Disable CodeQL until Code Scanning is enabled in repo settings
  (kept as workflow_dispatch for future use)
@trevwilson trevwilson force-pushed the claude-session/feature-github-actions-ci-20260120-2336 branch from d56def6 to ec217b9 Compare January 21, 2026 15:21
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@frontend/src/components/auth/ProtectedLayout.tsx`:
- Around line 40-51: The checkAuth function currently awaits
isElectronAuthenticated() without error handling, which can leave isLoading true
if the promise rejects; wrap the await in try/catch/finally inside checkAuth
(catch should setIsAuthenticated(false) or set an error state) and ensure
setIsLoading(false) is called in finally so loading never remains stuck; update
references to checkAuth, isElectronAuthenticated, setIsAuthenticated, and
setIsLoading accordingly.

In `@frontend/src/lib/powersync/syncRules.test.ts`:
- Around line 117-127: The current guard treats an empty string content as
absent and will also throw a generic TypeError if sync_rules is missing; update
the logic in the block that sets syncRulesText (look for parsedYaml.sync_rules,
syncRulesText, projectRoot, parseSyncRulesContent) to first check for a
non-empty content string (e.g., parsedYaml?.sync_rules?.content !== undefined &&
parsedYaml.sync_rules.content.trim() !== ''), use that when present, otherwise
if parsedYaml?.sync_rules?.path is present read the file with
fs.readFileSync(projectRoot + '/powersync/' + parsedYaml.sync_rules.path,
'utf-8'), and finally throw your specific Error('sync_rules must have either
content or path') when neither exists so missing sync_rules yields the custom
error rather than a TypeError.

- ElectronAuthPage: move early return after hooks (rules-of-hooks)
- EditActionModal: add eslint-disable for legitimate state reset/init
- RecurrenceEditor: add eslint-disable for controlled component sync
@trevwilson trevwilson force-pushed the claude-session/feature-github-actions-ci-20260120-2336 branch from cbea61f to 96cd8ea Compare January 21, 2026 16:06
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/auth/ProtectedLayout.tsx (1)

40-55: Clear stale error state on successful auth checks.
If a previous check failed, error can remain visible even when a later checkAuth() succeeds but returns unauthenticated. Consider resetting error on success.

✅ Proposed tweak
   const checkAuth = useCallback(async () => {
     setIsLoading(true)
     try {
       const authenticated = await isElectronAuthenticated()
       setIsAuthenticated(authenticated)
+      setError(null)
     } catch (err) {
       setError(err instanceof Error ? err.message : 'Failed to check authentication')
       setIsAuthenticated(false)
     } finally {
       setIsLoading(false)
     }
   }, [])

trevwilson and others added 4 commits January 21, 2026 11:29
- Fix set-state-in-effect errors with targeted suppressions for legitimate
  modal reset/init patterns in EditActionModal
- Remove unnecessary optional chains on non-nullable values (electron.ts,
  syncRules.test.ts)
- Remove redundant nullish coalescing where types guarantee values
  (fixedInterval, occurrenceCount, templatePriority have schema defaults)
- Use .at(0) for proper undefined typing on array access (connector.ts)
- Use 'key in' operator for proper object key narrowing (connector.ts)
- Fix floating promise with void operator (ElectronAuthPage.tsx)
- Remove dead code after exhaustive status checks (ElectronAuthPage.tsx)
- Auto-fix import ordering (9 files)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
React Compiler:
- Add babel-plugin-react-compiler and eslint-plugin-react-compiler
- Configure Vite to use compiler babel plugin
- Add compiler ESLint rules to catch violations
- Fix ProcessPage.tsx: use ref patterns for intentional stale closures,
  add proper deps to effects and callbacks

Docs:
- Fix DOWNLOAD arrow direction in DATA_FLOW.md (was pointing wrong way)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The React Compiler changes in a2db170 added proper dependencies to
effects in ProcessPage.tsx, but those dependencies (contexts, projects)
were unstable - data.map() creates new arrays on every render.

This caused effects to re-run excessively, which on slower CI runners
led to navigation timeouts (10+ seconds to process all the re-renders).

Fix: Add useMemo to useContexts and useProjects hooks to maintain
stable array references when the underlying data hasn't changed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Each test class was creating its own API server on a random port, but
Vite was shared and configured with the first API's port. This could
cause proxy mismatches when tests run in parallel.

Fix: Share the API server instance like we share the DB container and
Vite server. Each test just clears the database for isolation.

Also adds RecurrencePatterns to the TRUNCATE statement.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/OpenGTD.E2E/GtdAppFixture.cs`:
- Around line 120-198: The code can leak a partially-built WebApplication if an
exception occurs after builder.Build(); change EnsureApiServerAsync to build
into a local variable (e.g., var app = builder.Build()), perform migrations,
UseDeveloperExceptionPage, MapApiEndpoints, StartAsync and resolve the address
against that local app, and only after all steps succeed assign _sharedApp = app
and _sharedApiUrl = resolvedUrl; if any exception occurs while using the local
app, call app.DisposeAsync()/app.Dispose() (and set local to null) before
rethrowing so the partially-initialized instance is not orphaned; ensure locks
(_apiServerLock/_migrationLock) are still released in finally blocks.
🧹 Nitpick comments (3)
tests/OpenGTD.E2E/GtdAppFixture.cs (3)

238-243: Minor: GC.SuppressFinalize without a finalizer.

GC.SuppressFinalize(this) is called, but the class doesn't define a finalizer. This is harmless but technically unnecessary.


286-290: Minor TOCTOU race in port allocation.

The port is allocated by binding to port 0, then released before Vite starts. Another process could theoretically claim the port in between. This is unlikely to cause issues in practice, but for robustness, consider using --port 0 with Vite (if supported) and parsing the actual port from stdout, or retrying on port conflicts.


315-331: Consider fail-fast if Vite process exits unexpectedly.

The polling loop catches all exceptions during startup, which is reasonable for connection-refused errors. However, if the Vite process crashes during startup, the loop will wait the full 30 seconds before timing out.

Suggestion: Check for process exit in polling loop
             for (var i = 0; i < 30; i++) // Max 30 seconds
             {
                 await Task.Delay(100);
+                if (_viteProcess.HasExited)
+                {
+                    throw new InvalidOperationException(
+                        $"Vite dev server exited unexpectedly with code {_viteProcess.ExitCode}");
+                }
                 try
                 {

trevwilson and others added 2 commits January 21, 2026 14:29
The first tests in each class were failing because they hit an
un-warmed React app. Vite HTTP responding doesn't mean the app is
ready - the first real navigation triggers module loading, WASM
initialization, and React app startup.

Fix: After Vite is responding, do a warmup navigation with Playwright
that actually loads the React app and waits for content to render.
This ensures all initialization is complete before tests run.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Instead of hardcoding table names in TRUNCATE (which requires manual
updates when adding new entities), query EF Core's model metadata
for all table names dynamically.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@tests/OpenGTD.E2E/GtdAppFixture.cs`:
- Around line 216-224: The TRUNCATE SQL uses unqualified names from
GetTableName() which can mis-resolve across schemas; update the table name
construction in GtdAppFixture (the dbContext.Model.GetEntityTypes() pipeline) to
call e.GetSchema() and produce schema-qualified identifiers (e.g.
"schema"."table"), handling null/empty schema (use default schema or omit quotes
consistently) and then join those fully-qualified names into the TRUNCATE TABLE
... CASCADE statement before ExecuteSqlRawAsync.
- Around line 347-365: Move Playwright resource allocation into a safe disposal
pattern so partially-constructed resources aren't leaked: either use C# await
using for Playwright.CreateAsync(), playwright.Chromium.LaunchAsync(...) and
browser.NewPageAsync() (preferred) or allocate them inside the try block and
null-check them in the finally block before disposing/closing; update references
to Playwright.CreateAsync, playwright, browser, and page and ensure
playwright.DisposeAsync()/browser.CloseAsync()/page.CloseAsync() (or
DisposeAsync) are called only when the corresponding variable is non-null or via
await using so disposal always runs even if CreateAsync/LaunchAsync/NewPageAsync
throws.
♻️ Duplicate comments (1)
tests/OpenGTD.E2E/GtdAppFixture.cs (1)

160-193: Dispose partially-built API app on failure to avoid orphaned servers.

If any exception occurs after builder.Build() but before _sharedApiUrl is assigned, retries will leak the prior WebApplication. Consider building into a local variable, only assigning to _sharedApp/_sharedApiUrl after success, and disposing on failure.

🔧 Proposed fix
-            _sharedApp = builder.Build();
+            var app = builder.Build();

             // Apply migrations once
             await _migrationLock.WaitAsync();
             try
             {
                 if (!_migrationApplied)
                 {
-                    using var scope = _sharedApp.Services.CreateScope();
+                    using var scope = app.Services.CreateScope();
                     var dbContext = scope.ServiceProvider.GetRequiredService<GTDDbContext>();
                     await dbContext.Database.MigrateAsync();
                     _migrationApplied = true;
                 }
             }
             finally
             {
                 _migrationLock.Release();
             }

             // For E2E tests, use developer exception page
-            _sharedApp.UseDeveloperExceptionPage();
+            app.UseDeveloperExceptionPage();

             // API endpoints only - frontend served by Vite dev server
-            _sharedApp.MapApiEndpoints();
+            app.MapApiEndpoints();

-            await _sharedApp.StartAsync();
+            try
+            {
+                await app.StartAsync();

-            var server = _sharedApp.Services.GetRequiredService<IServer>();
-            var addresses = server.Features.Get<IServerAddressesFeature>()?.Addresses;
-            _sharedApiUrl = addresses?.FirstOrDefault()
+                var server = app.Services.GetRequiredService<IServer>();
+                var addresses = server.Features.Get<IServerAddressesFeature>()?.Addresses;
+                var apiUrl = addresses?.FirstOrDefault()
                 ?? throw new InvalidOperationException("Could not determine API server address");
-
-            return _sharedApiUrl;
+                _sharedApp = app;
+                _sharedApiUrl = apiUrl;
+                return apiUrl;
+            }
+            catch
+            {
+                await app.DisposeAsync();
+                throw;
+            }

trevwilson and others added 2 commits January 21, 2026 14:41
All test classes call DisposeAsync directly, and both methods were
no-ops since shared resources are cleaned up at process exit.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. EnsureApiServerAsync: Build into local variable, only assign to
   static fields after all steps succeed, dispose on failure

2. ClearDatabaseAsync: Use schema-qualified table names from EF Core
   model (e.GetSchema() + e.GetTableName())

3. Playwright warmup: Use try/finally with null checks for proper
   disposal even if exceptions occur during initialization

4. Vite startup: Fail fast if process exits unexpectedly during
   polling loop instead of waiting full 30 second timeout

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@trevwilson trevwilson merged commit 33b77b7 into master Jan 21, 2026
4 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 22, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants