Skip to content

fix(ui): standardize conditional swr query enabling#1816

Merged
yottahmd merged 12 commits intomainfrom
fix-sse-bug
Mar 21, 2026
Merged

fix(ui): standardize conditional swr query enabling#1816
yottahmd merged 12 commits intomainfrom
fix-sse-bug

Conversation

@yottahmd
Copy link
Copy Markdown
Collaborator

@yottahmd yottahmd commented Mar 21, 2026

Summary

  • standardize conditional swr-openapi query enabling with a shared helper
  • replace repeated inline null-key gating in cockpit, DAG details, DAG-run details, and shared log/message views
  • add regression tests for helper behavior and root vs sub-DAG query branch selection

Testing

  • pnpm vitest run src/hooks/tests/queryUtils.test.ts src/features/cockpit/components/tests/DAGPreviewModal.test.tsx src/features/cockpit/components/tests/TemplateSelector.test.tsx src/features/cockpit/components/tests/DateKanbanList.test.tsx src/features/cockpit/hooks/tests/useInfiniteKanban.test.tsx src/features/dags/components/dag-details/tests/DAGDetailsSidePanel.test.tsx src/features/dag-runs/components/dag-run-details/tests/DAGRunDetailsPanel.test.tsx src/features/dags/components/chat-history/tests/StepMessagesTable.test.tsx
  • pnpm exec tsc --noEmit (still fails on pre-existing unrelated fixture typing errors in DAGRunActions.test.tsx and DAGActions.test.tsx)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added real-time event streaming for DAGs, runs, queues, and documentation changes with improved fallback handling.
    • Introduced workspace selection in the Cockpit view.
    • Enhanced template selector with open/close state callbacks and tag filtering.
    • Improved DAG run details modal with initial tab control and dynamic toolbar hints.
    • Added load-more suspension capability to DAG run kanban list.
  • Tests

    • Added comprehensive test coverage for new live connection, workspace, template selector, and kanban components.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 21, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f5a64aa6-349a-4a3d-8757-b5dac533905d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a new app-stream SSE service for real-time file-system and state change notifications, replaces SSE-based update hooks across the UI with a centralized live-connection manager, adds workspace initial state to frontend configuration, and refactors query gating from isPaused callbacks to conditional whenEnabled pattern.

Changes

Cohort / File(s) Summary
Backend App Stream Service
internal/service/frontend/sse/app_stream.go, internal/service/frontend/sse/app_stream_test.go
Implements new AppStreamService with recursive filesystem watchers for DAG/docs/queues, event coalescing, buffered pub/sub hub, SSE event framing, and optional remote node proxying. Tests verify frame writing, idempotent shutdown, and 503 unavailable response.
Backend Server Wiring
internal/service/frontend/server.go
Added appStream field, initialized in setupSSERoute, registered dedicated SSE fetchers (run/step logs only), updated shutdown logic, and wired WorkspaceStore into funcsConfig.
Frontend Configuration
internal/service/frontend/templates.go, internal/service/frontend/templates/base.gohtml, ui/index.html, ui/src/contexts/ConfigContext.tsx
Added initialWorkspacesJSON template function, extended frontend base config to include parsed workspace array, and updated TypeScript Config type with WorkspaceResponse[] field.
Frontend Template Tests
internal/service/frontend/templates_test.go
Added stub workspace store and test verifying initialWorkspacesJSON correctly serializes workspace list with created/updated timestamps.
Live Connection Infrastructure
ui/src/hooks/AppLiveManager.ts, ui/src/hooks/useAppLive.ts, ui/src/hooks/queryUtils.ts, ui/src/hooks/__tests__/queryUtils.test.ts, ui/src/hooks/api.ts
Introduced AppLiveManager for centralized EventSource lifecycle management with exponential backoff reconnection, liveFallbackOptions and domain-specific hooks (useLiveDAG, useLiveDAGRuns, etc.) for query fallback and invalidation, and whenEnabled utility for conditional query initialization.
Workspace State Management
ui/src/features/cockpit/hooks/useCockpitState.ts
Replaced remote /workspaces fetch with local state sourced from config.initialWorkspaces, added persistent update via updateConfig, and removed network-dependent error.
Cockpit Kanban Refactor
ui/src/features/cockpit/hooks/useDateKanbanData.ts, ui/src/features/cockpit/hooks/useCockpitDagRuns.ts
Replaced useCockpitDagRuns (removed entirely) with hook composition: useDateKanbanData now uses useLiveDAGRuns instead of SSE cache sync and exposes error/retry for error handling.
Cockpit Components
ui/src/features/cockpit/components/CockpitToolbar.tsx, ui/src/features/cockpit/components/DateKanbanList.tsx, ui/src/features/cockpit/components/DateKanbanSection.tsx, ui/src/features/cockpit/components/DAGPreviewModal.tsx, ui/src/features/cockpit/components/TemplateSelector.tsx
DateKanbanList added suspendLoadMore prop with IntersectionObserver-based infinite scroll; DateKanbanSection now derives data via hook instead of props; TemplateSelector restructured fetch logic to gate /dags and /dags/tags queries based on UI state and added onOpenChange callback; CockpitToolbar and DAGPreviewModal updated for new props/behavior.
Cockpit Component Tests
ui/src/features/cockpit/components/__tests__/DAGPreviewModal.test.tsx, ui/src/features/cockpit/components/__tests__/DateKanbanList.test.tsx, ui/src/features/cockpit/components/__tests__/TemplateSelector.test.tsx, ui/src/features/cockpit/hooks/__tests__/useInfiniteKanban.test.tsx
Added/updated tests mocking useQuery, useLiveConnection, useInfiniteKanban to verify query gating, load-more behavior, filter reset on open/close, and template selector state reporting.
DAG Runs Components
ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsModal.tsx, ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsPanel.tsx, ui/src/features/dag-runs/components/dag-run-list/__tests__/DAGRunTable.test.tsx
Replaced SSE-based sync with live-connection hooks; queries now use whenEnabled instead of isPaused; test fixture extended with initialWorkspaces.
DAG Run Details Test
ui/src/features/dag-runs/components/dag-run-details/__tests__/DAGRunDetailsPanel.test.tsx
New test suite verifying conditional query initialization (sub-DAG vs regular DAG) based on URL parameters, mocking useAppLive and asserting panel content.
DAG Components
ui/src/features/dags/components/dag-details/DAGDetailsContent.tsx, ui/src/features/dags/components/dag-details/DAGDetailsPanel.tsx, ui/src/features/dags/components/dag-details/DAGDetailsSidePanel.tsx, ui/src/features/dags/components/dag-editor/DAGSpec.tsx, ui/src/features/dags/components/dag-execution/DAGExecutionHistory.tsx, ui/src/features/dags/components/dag-execution/ExecutionLog.tsx, ui/src/features/dags/components/dag-execution/ParallelExecutionModal.tsx, ui/src/features/dags/components/dag-execution/StepLog.tsx
Removed sseResult props, replaced useDAGSSE/useSSECacheSync with useLiveConnection/useLiveDAG/useLiveDAGRuns, updated query options to use liveFallbackOptions and whenEnabled gating.
DAG Component Tests
ui/src/features/dags/components/chat-history/__tests__/StepMessagesTable.test.tsx, ui/src/features/dags/components/dag-details/__tests__/DAGDetailsSidePanel.test.tsx
New/updated test suites mocking useQuery, useLiveConnection, verifying conditional query initialization and enqueue flow with live updates.
DAG List Component
ui/src/features/dags/components/chat-history/StepMessagesTable.tsx, ui/src/features/dags/components/common/InlineLogViewer.tsx, ui/src/features/dags/components/dag-details/SubDAGRunsList.tsx
Replaced isPaused callbacks with whenEnabled gating for query initialization based on sub-DAG run context.
Page Components
ui/src/pages/cockpit/index.tsx, ui/src/pages/dag-runs/dag-run/index.tsx, ui/src/pages/dag-runs/index.tsx, ui/src/pages/dags/dag/index.tsx, ui/src/pages/dags/index.tsx, ui/src/pages/docs/components/DocEditor.tsx, ui/src/pages/docs/index.tsx, ui/src/pages/index.tsx, ui/src/pages/queues/index.tsx
Removed all useDAG*SSE, sseFallbackOptions, useSSECacheSync usage; replaced with useLiveConnection, liveFallbackOptions, and domain-specific live hooks; added template selector open-state tracking for load-more suspension.
Test Results
ui/test-results/.last-run.json, ui/test-results/.../error-context.md
Added test result artifacts capturing failed test status and Cockpit UI snapshot.
Deprecated Hook
ui/src/features/cockpit/hooks/useCockpitDagRuns.ts
Entire file removed; functionality replaced by useDateKanbanData with per-date data fetching.

Sequence Diagram(s)

sequenceDiagram
    participant Component as Component
    participant LiveHook as useLiveDAG(s)
    participant Manager as AppLiveManager
    participant EventSource as EventSource
    participant Mutate as Query Mutate

    Note over Component,Mutate: Initial Setup
    Component->>LiveHook: useEffect: subscribe
    LiveHook->>Manager: subscribe(remoteNode, apiURL, callbacks)
    Manager->>EventSource: EventSource(url + token)
    EventSource-->>Manager: open
    Manager->>Manager: emit 'connected' + optional 'reset'

    Note over Component,Mutate: Live Event Flow
    EventSource->>Manager: event: 'dag.changed' (JSON)
    Manager->>Manager: parse + match event type
    Manager->>LiveHook: callback(AppLiveEvent)
    LiveHook->>LiveHook: debounce matched events
    LiveHook->>Mutate: call mutate() (debounced)
    Mutate->>Component: refresh query data

    Note over Component,Mutate: Reconnection
    EventSource->>Manager: onerror (disconnect)
    Manager->>Manager: schedule reconnect (exponential backoff)
    Manager->>EventSource: new EventSource after delay
    EventSource-->>Manager: open
    Manager->>Manager: emit 'reset' on reconnect
    Manager->>LiveHook: callback(reset)
    LiveHook->>Mutate: mutate() to refresh all
Loading
sequenceDiagram
    participant Page as Page Component
    participant Selector as TemplateSelector
    participant Query as useQuery('/dags')
    participant TagQuery as useQuery('/dags/tags')
    participant API as REST API

    Note over Page,API: Template Selector Open/Close Flow
    Page->>Selector: onOpenChange callback
    Selector->>Selector: setIsOpen(true)
    Selector->>Query: whenEnabled(isOpen, {...init})
    Query->>API: GET /dags
    Selector->>TagQuery: whenEnabled(isOpen && isTagFilterOpen, {...})
    
    Note over Selector,API: Filter Reset on Open
    Selector->>Selector: handleOpen() → resetFilters()
    Selector->>Selector: clear selectedDag, filters
    Selector->>Page: onOpenChange(true)
    
    Note over Selector,API: Close and Cleanup
    Selector->>Selector: handleClose() → resetFilters()
    Selector->>Query: whenEnabled(false, {...}) → null
    Selector->>TagQuery: whenEnabled(false, {...}) → null
    Selector->>Page: onOpenChange(false)
Loading
sequenceDiagram
    participant FileSystem as Filesystem
    participant Watcher as recursiveWatcher
    participant Coalescer as appEventCoalescer
    participant Hub as AppHub
    participant Subscriber as Client (SSE)

    Note over FileSystem,Subscriber: File Change → Event → Subscribers
    FileSystem->>Watcher: file change event
    Watcher->>Watcher: match DAG/doc/queue file
    Watcher->>Coalescer: enqueue(AppEvent)
    Coalescer->>Coalescer: debounce (coalsce multiple)
    Coalescer->>Hub: broadcast(event)
    Hub->>Hub: send to all buffered channels
    Hub->>Subscriber: SSE frame: event: 'dag.changed'

    Note over FileSystem,Subscriber: Watcher Error → Reset
    FileSystem->>Watcher: onerror
    Watcher->>Coalescer: enqueue(reset event)
    Coalescer->>Hub: broadcast(reset)
    Hub->>Subscriber: SSE frame: event: 'reset'
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

This PR spans significant architectural changes across backend and frontend layers with high heterogeneity. The backend introduces a new service with filesystem watching and pub/sub logic (~600 lines); the frontend establishes a new centralized live-connection manager (~340 lines) and refactors ~15+ components across multiple feature areas from SSE to live patterns. While individual component refactors follow a consistent pattern, the diversity of files, the density of logic in the new managers, and the interconnected dependency changes require careful reasoning across multiple layers and subsystems.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% 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 'fix(ui): standardize conditional swr query enabling' directly and concisely describes the main change—introducing a shared helper to standardize conditional enabling of SWR queries across the UI.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-sse-bug

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@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: 11

🧹 Nitpick comments (6)
internal/service/frontend/sse/app_stream_test.go (2)

37-56: Idempotency tests rely on implicit "no panic" assertion.

The idempotency tests verify that calling Stop()/Shutdown() twice doesn't panic, which is valid. Consider adding a comment clarifying the intent.

♻️ Add clarifying comments
 func TestRecursiveWatcherStopIsIdempotent(t *testing.T) {
+	// Calling Stop() multiple times should not panic
 	watcher := &recursiveWatcher{
 		done: make(chan struct{}),
 	}
 
 	watcher.Stop()
 	watcher.Stop()
 }
 
 func TestAppStreamServiceShutdownIsIdempotent(t *testing.T) {
+	// Calling Shutdown() multiple times should not panic
 	service := &AppStreamService{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/service/frontend/sse/app_stream_test.go` around lines 37 - 56, Add
an inline comment to each idempotency test clarifying that the test asserts "no
panic" by invoking the method twice: in TestRecursiveWatcherStopIsIdempotent,
above the two watcher.Stop() calls note that the test ensures
recursiveWatcher.Stop() is safe to call multiple times (no panic); and in
TestAppStreamServiceShutdownIsIdempotent, add a similar comment above the two
service.Shutdown() calls stating the test verifies AppStreamService.Shutdown()
is idempotent (no panic) so readers understand the implicit assertion.

13-35: Consider using stretchr/testify assertions.

Per coding guidelines, Go test files should use stretchr/testify assertions. This would improve readability and provide more descriptive failure messages.

♻️ Example refactor using testify
+import (
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	...
+)

 func TestWriteAppEventFrame(t *testing.T) {
 	recorder := httptest.NewRecorder()
 
 	err := writeAppEventFrame(recorder, AppEvent{...})
-	if err != nil {
-		t.Fatalf("writeAppEventFrame() error = %v", err)
-	}
+	require.NoError(t, err)
 
 	body := recorder.Body.String()
-	if !strings.Contains(body, "event: dag.changed\n") {
-		t.Fatalf("expected SSE event frame, got %q", body)
-	}
+	assert.Contains(t, body, "event: dag.changed\n")
+	assert.Contains(t, body, `"fileName":"example.yaml"`)
+	assert.Contains(t, body, `"reason":"updated"`)
-	...
 }

As per coding guidelines: "Use stretchr/testify assertions for testing in Go"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/service/frontend/sse/app_stream_test.go` around lines 13 - 35,
Replace manual t.Fatalf checks in TestWriteAppEventFrame with stretchr/testify
assertions: import "github.com/stretchr/testify/require" (or "assert"), call
require.NoError(t, err) after writeAppEventFrame, and use require.Contains(t,
body, "event: dag.changed\n"), require.Contains(t, body,
`"fileName":"example.yaml"`), and require.Contains(t, body,
`"reason":"updated"`) instead of the three if/ t.Fatalf blocks; update the test
file imports accordingly and remove the now-unnecessary t.Fatalf branches in
TestWriteAppEventFrame.
ui/src/features/cockpit/hooks/useCockpitState.ts (1)

88-91: workspaceError: null removes error visibility.

By always returning null for workspaceError, consumers lose the ability to display workspace-related API errors. If the prior implementation exposed query errors, consider whether the UI still needs this capability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/features/cockpit/hooks/useCockpitState.ts` around lines 88 - 91, The
return value always sets workspaceError to null, removing upstream error
visibility; update useCockpitState to return the actual error from the workspace
query (e.g., replace workspaceError: null with workspaceError:
workspacesQuery.error or whatever error variable the fetch hook exposes) so
consumers (that read workspaces, selectedWorkspace) can render API errors;
locate the error coming from the workspace fetch hook used in useCockpitState
and pass it through as workspaceError.
ui/src/features/cockpit/components/DateKanbanSection.tsx (1)

42-55: Consider typing the error more explicitly.

The inline type assertion (error as { message?: string } | undefined)?.message suggests the hook's error type may not be well-defined. If useDateKanbanData returns a typed error (e.g., Error | null), the assertion could be removed.

♻️ Cleaner error handling if hook returns `Error | null`
-          <span className="text-destructive">
-            {(error as { message?: string } | undefined)?.message ||
-              'Failed to load runs'}
-          </span>
+          <span className="text-destructive">
+            {error instanceof Error ? error.message : 'Failed to load runs'}
+          </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/features/cockpit/components/DateKanbanSection.tsx` around lines 42 -
55, The component is using a loose inline cast for error; update the hook
useDateKanbanData to return a properly typed error (e.g., Error | null) and then
remove the cast in DateKanbanSection.tsx—use the typed error directly (e.g.,
error?.message || 'Failed to load runs') and keep the existing retry() call
intact; ensure any callers and types for useDateKanbanData, and the component's
props/state that consume it, are updated to reflect Error | null so the runtime
check is safe and no inline assertion like (error as { message?: string } |
undefined) is needed.
ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsPanel.tsx (1)

40-48: Simplify redundant liveEnabled computation.

liveEnabled for the sub-DAG branch duplicates the checks already performed by isSubDAGRun on line 39. Since isSubDAGRun is Boolean(subDAGRunId && parentDAGRunId && parentName), the ternary condition Boolean(parentName && parentDAGRunId && subDAGRunId) is always true when isSubDAGRun is true.

♻️ Suggested simplification
-  const liveEnabled = isSubDAGRun
-    ? Boolean(parentName && parentDAGRunId && subDAGRunId)
-    : Boolean(name && dagRunId);
+  const liveEnabled = isSubDAGRun || Boolean(name && dagRunId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsPanel.tsx`
around lines 40 - 48, The ternary in liveEnabled redundantly re-checks the same
condition encapsulated by isSubDAGRun; replace the ternary with a simple boolean
expression: set liveEnabled = isSubDAGRun || Boolean(name && dagRunId) so
sub-DAG runs use isSubDAGRun directly and non-sub-DAG runs still check name +
dagRunId, leaving useLiveConnection(liveEnabled) unchanged.
ui/src/hooks/AppLiveManager.ts (1)

293-312: Isolate subscriber exceptions in the shared fan-out.

notifySubscribers() and updateState() invoke consumer callbacks inline. One throw there aborts delivery to the remaining subscribers and can bubble out of the shared live-update path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/hooks/AppLiveManager.ts` around lines 293 - 312, notifySubscribers and
updateState currently call subscriber callbacks inline so one subscriber
throwing aborts delivery to others; wrap each call to
subscriber.onInvalidate(event) in notifySubscribers and
subscriber.onStateChange(conn.state) in updateState with a try/catch to isolate
exceptions, handle/log the error (using existing logger or console) and continue
iterating so one bad subscriber cannot break the fan-out.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/service/frontend/sse/app_stream.go`:
- Around line 316-323: Compare normalized/cleaned paths when deciding the
createRoot boolean: call the same normalization used by uniqueNonEmptyPaths on
cfg.Paths.DAGsDir (e.g., filepath.Clean/absolute equivalent) and compare that
cleaned value to dagRoot (which is already cleaned) before passing the boolean
to newRecursiveWatcher; apply the same fix for the second occurrence around the
other watcher creation (lines 348-360) so createRoot is true when the logical
primary DAGsDir matches regardless of trailing slashes or relative paths.
- Around line 260-263: The code in the fsnotify Create handling and in the
directory traversal swallows errors from w.addDirRecursive (and the
filepath.Walk used inside it), leaving subtrees unwatched; modify the Create
branch to check and propagate the error from w.addDirRecursive (e.g., if err :=
w.addDirRecursive(event.Name); err != nil { return err } or send the error to
the component's existing reset/error channel) and likewise update the traversal
logic inside addDirRecursive (or the filepath.Walk callback) to return any walk
errors instead of discarding them so callers can detect and handle
watch-registration failures.

In `@ui/src/features/cockpit/components/TemplateSelector.tsx`:
- Around line 104-116: The effect in TemplateSelector.tsx clears selectedDag
whenever the dropdown is closed because it returns early on !isOpen and also
clears selectedDag when selectedTemplate exists but the menu is closed, causing
the label to disappear; update the useEffect that references
selectedTemplate/filteredDags/isOpen so it only sets selectedDag(null) when
selectedTemplate is falsy and always tries to find and set the matching dag from
filteredDags (i.e., remove the early return on isOpen), and apply the same
change to the similar effect around lines 163-171 so the preselected
selectedTemplate keeps its label even when the dropdown is closed (refer to
selectedDag, selectedTemplate, filteredDags, isOpen in the effect).

In `@ui/src/features/cockpit/hooks/useCockpitState.ts`:
- Around line 60-70: The POST call to create a workspace using client.POST
currently swallows failures; update the createWorkspace flow to surface failures
by handling the returned error: when client.POST('/workspaces', ...) returns an
error, log it (e.g., via console.error or an existing logger), and either return
the error to callers or set a local error state so the UI can display it; keep
the existing success path that calls applyWorkspaces(...) and
selectWorkspace(...), but ensure the error branch does not silently
no-op—propagate or persist the error for user feedback.

In
`@ui/src/features/dag-runs/components/dag-run-details/__tests__/DAGRunDetailsPanel.test.tsx`:
- Around line 1-8: Add the required GPL v3 license header to the top of this
test file (DAGRunDetailsPanel.test.tsx) above the imports: include the standard
GPL v3 notice (or SPDX identifier such as "SPDX-License-Identifier:
GPL-3.0-or-later") plus the copyright/owner line used across the repo so the
file legally matches other sources; ensure the header sits before any code
(imports like React, render, MemoryRouter, etc.) so linters and tooling
recognize it.

In `@ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsModal.tsx`:
- Around line 73-75: The current gate makes dagRunQueryEnabled require a truthy
dagRunId, but the code later intentionally falls back to 'latest', so change the
condition for dagRunQueryEnabled to allow queries when name is present even if
dagRunId is empty (i.e., use isOpen && !canQuerySubDag && Boolean(name) instead
of requiring dagRunId); apply the same fix to the other occurrence around the
second block (the lines referenced 95-100) so both checks permit the 'latest'
fallback to run.

In
`@ui/src/features/dags/components/chat-history/__tests__/StepMessagesTable.test.tsx`:
- Around line 1-6: This new test file (StepMessagesTable.test.tsx) is missing
the required GPL v3 license header; add the standard GPL v3 license header to
the top of the file (above the imports) either by running the repository helper
(make addlicense) or by inserting the project’s canonical GPL v3 header manually
so the file header matches other source files and CI checks; ensure the header
is placed before the existing imports and saved.

In `@ui/src/hooks/AppLiveManager.ts`:
- Line 1: This new file AppLiveManager.ts is missing the project's required GPL
v3 license header; add the standard GPL header to the top of AppLiveManager.ts
(the file that currently imports getAuthToken) and then run the repository task
to apply/verify headers by executing make addlicense before merging so the file
conforms to the **/*.{go,ts,tsx,js} GPL header policy.

In `@ui/src/hooks/useAppLive.ts`:
- Line 1: This new file (useAppLive.ts — seen by the import line "import {
useContext, useEffect, useRef, useState } from 'react';") is missing the
required GPL v3 license header; run the repository's license tool (make
addlicense) in the ui/src directory (or project root as configured) to insert
the standard GPL header for TypeScript files, then recommit the updated file so
it conforms to the coding guideline for **/*.{go,ts,tsx,js}.

In `@ui/test-results/.last-run.json`:
- Around line 1-6: The committed JSON contains transient test-run state keys
"status" and "failedTests" and should not be versioned; remove the file from the
PR (delete the committed .last-run.json), add an ignore rule to your repo's
.gitignore to exclude the test-results/.last-run.json pattern (or the
test-results directory’s transient artifacts), and if you need a deterministic
example add a checked-in fixture file under a clearly named fixtures path
instead of this runtime output.

In
`@ui/test-results/dagu-cockpit-autoupdate-ch-dc750--run-without-manual-refresh/error-context.md`:
- Around line 1-154: Delete the committed run-specific artifact
ui/test-results/dagu-cockpit-autoupdate-ch-dc750--run-without-manual-refresh/error-context.md
from the repo and add a rule for ui/test-results/ to .gitignore so future
transient test outputs aren’t tracked; remove the file from version control
(ensure it’s deleted/unstaged from the index) and commit the updated .gitignore
and deletion together, and verify no source or tests reference error-context.md
before pushing.

---

Nitpick comments:
In `@internal/service/frontend/sse/app_stream_test.go`:
- Around line 37-56: Add an inline comment to each idempotency test clarifying
that the test asserts "no panic" by invoking the method twice: in
TestRecursiveWatcherStopIsIdempotent, above the two watcher.Stop() calls note
that the test ensures recursiveWatcher.Stop() is safe to call multiple times (no
panic); and in TestAppStreamServiceShutdownIsIdempotent, add a similar comment
above the two service.Shutdown() calls stating the test verifies
AppStreamService.Shutdown() is idempotent (no panic) so readers understand the
implicit assertion.
- Around line 13-35: Replace manual t.Fatalf checks in TestWriteAppEventFrame
with stretchr/testify assertions: import "github.com/stretchr/testify/require"
(or "assert"), call require.NoError(t, err) after writeAppEventFrame, and use
require.Contains(t, body, "event: dag.changed\n"), require.Contains(t, body,
`"fileName":"example.yaml"`), and require.Contains(t, body,
`"reason":"updated"`) instead of the three if/ t.Fatalf blocks; update the test
file imports accordingly and remove the now-unnecessary t.Fatalf branches in
TestWriteAppEventFrame.

In `@ui/src/features/cockpit/components/DateKanbanSection.tsx`:
- Around line 42-55: The component is using a loose inline cast for error;
update the hook useDateKanbanData to return a properly typed error (e.g., Error
| null) and then remove the cast in DateKanbanSection.tsx—use the typed error
directly (e.g., error?.message || 'Failed to load runs') and keep the existing
retry() call intact; ensure any callers and types for useDateKanbanData, and the
component's props/state that consume it, are updated to reflect Error | null so
the runtime check is safe and no inline assertion like (error as { message?:
string } | undefined) is needed.

In `@ui/src/features/cockpit/hooks/useCockpitState.ts`:
- Around line 88-91: The return value always sets workspaceError to null,
removing upstream error visibility; update useCockpitState to return the actual
error from the workspace query (e.g., replace workspaceError: null with
workspaceError: workspacesQuery.error or whatever error variable the fetch hook
exposes) so consumers (that read workspaces, selectedWorkspace) can render API
errors; locate the error coming from the workspace fetch hook used in
useCockpitState and pass it through as workspaceError.

In `@ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsPanel.tsx`:
- Around line 40-48: The ternary in liveEnabled redundantly re-checks the same
condition encapsulated by isSubDAGRun; replace the ternary with a simple boolean
expression: set liveEnabled = isSubDAGRun || Boolean(name && dagRunId) so
sub-DAG runs use isSubDAGRun directly and non-sub-DAG runs still check name +
dagRunId, leaving useLiveConnection(liveEnabled) unchanged.

In `@ui/src/hooks/AppLiveManager.ts`:
- Around line 293-312: notifySubscribers and updateState currently call
subscriber callbacks inline so one subscriber throwing aborts delivery to
others; wrap each call to subscriber.onInvalidate(event) in notifySubscribers
and subscriber.onStateChange(conn.state) in updateState with a try/catch to
isolate exceptions, handle/log the error (using existing logger or console) and
continue iterating so one bad subscriber cannot break the fan-out.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5492cda0-af71-4e24-8bd7-9c2fa35addaa

📥 Commits

Reviewing files that changed from the base of the PR and between 81ff7fb and bf15109.

📒 Files selected for processing (54)
  • internal/service/frontend/server.go
  • internal/service/frontend/sse/app_stream.go
  • internal/service/frontend/sse/app_stream_test.go
  • internal/service/frontend/templates.go
  • internal/service/frontend/templates/base.gohtml
  • internal/service/frontend/templates_test.go
  • ui/index.html
  • ui/src/contexts/ConfigContext.tsx
  • ui/src/features/agent/hooks/__tests__/useAgentChat.test.tsx
  • ui/src/features/cockpit/components/CockpitToolbar.tsx
  • ui/src/features/cockpit/components/DAGPreviewModal.tsx
  • ui/src/features/cockpit/components/DateKanbanList.tsx
  • ui/src/features/cockpit/components/DateKanbanSection.tsx
  • ui/src/features/cockpit/components/TemplateSelector.tsx
  • ui/src/features/cockpit/components/__tests__/DAGPreviewModal.test.tsx
  • ui/src/features/cockpit/components/__tests__/DateKanbanList.test.tsx
  • ui/src/features/cockpit/components/__tests__/TemplateSelector.test.tsx
  • ui/src/features/cockpit/hooks/__tests__/useInfiniteKanban.test.tsx
  • ui/src/features/cockpit/hooks/useCockpitDagRuns.ts
  • ui/src/features/cockpit/hooks/useCockpitState.ts
  • ui/src/features/cockpit/hooks/useDateKanbanData.ts
  • ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsModal.tsx
  • ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsPanel.tsx
  • ui/src/features/dag-runs/components/dag-run-details/__tests__/DAGRunDetailsPanel.test.tsx
  • ui/src/features/dag-runs/components/dag-run-list/__tests__/DAGRunTable.test.tsx
  • ui/src/features/dags/components/chat-history/StepMessagesTable.tsx
  • ui/src/features/dags/components/chat-history/__tests__/StepMessagesTable.test.tsx
  • ui/src/features/dags/components/common/InlineLogViewer.tsx
  • ui/src/features/dags/components/dag-details/DAGDetailsContent.tsx
  • ui/src/features/dags/components/dag-details/DAGDetailsPanel.tsx
  • ui/src/features/dags/components/dag-details/DAGDetailsSidePanel.tsx
  • ui/src/features/dags/components/dag-details/SubDAGRunsList.tsx
  • ui/src/features/dags/components/dag-details/__tests__/DAGDetailsSidePanel.test.tsx
  • ui/src/features/dags/components/dag-editor/DAGSpec.tsx
  • ui/src/features/dags/components/dag-execution/DAGExecutionHistory.tsx
  • ui/src/features/dags/components/dag-execution/ExecutionLog.tsx
  • ui/src/features/dags/components/dag-execution/ParallelExecutionModal.tsx
  • ui/src/features/dags/components/dag-execution/StepLog.tsx
  • ui/src/hooks/AppLiveManager.ts
  • ui/src/hooks/__tests__/queryUtils.test.ts
  • ui/src/hooks/api.ts
  • ui/src/hooks/queryUtils.ts
  • ui/src/hooks/useAppLive.ts
  • ui/src/pages/cockpit/index.tsx
  • ui/src/pages/dag-runs/dag-run/index.tsx
  • ui/src/pages/dag-runs/index.tsx
  • ui/src/pages/dags/dag/index.tsx
  • ui/src/pages/dags/index.tsx
  • ui/src/pages/docs/components/DocEditor.tsx
  • ui/src/pages/docs/index.tsx
  • ui/src/pages/index.tsx
  • ui/src/pages/queues/index.tsx
  • ui/test-results/.last-run.json
  • ui/test-results/dagu-cockpit-autoupdate-ch-dc750--run-without-manual-refresh/error-context.md
💤 Files with no reviewable changes (1)
  • ui/src/features/cockpit/hooks/useCockpitDagRuns.ts

Comment on lines +260 to +263
if event.Op&fsnotify.Create != 0 {
if info, err := os.Stat(event.Name); err == nil && info.IsDir() {
_ = w.addDirRecursive(event.Name)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bubble up recursive watch-registration failures.

Line 262 drops addDirRecursive errors, and Lines 275-277 swallow traversal errors from the walk. That leaves parts of the tree unwatched without any reset/error signal, so invalidations under those directories can silently stop.

Suggested fix
 	if event.Op&fsnotify.Create != 0 {
 		if info, err := os.Stat(event.Name); err == nil && info.IsDir() {
-			_ = w.addDirRecursive(event.Name)
+			if err := w.addDirRecursive(event.Name); err != nil {
+				w.onReset(fmt.Sprintf("failed to register watcher for %s: %v", event.Name, err))
+			}
 		}
 	}
@@
 func (w *recursiveWatcher) addDirRecursive(root string) error {
 	return filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
 		if err != nil {
-			return nil
+			return err
 		}
 		if !d.IsDir() {
 			return nil
 		}
 		return w.watcher.Add(path)

Also applies to: 273-281

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/service/frontend/sse/app_stream.go` around lines 260 - 263, The code
in the fsnotify Create handling and in the directory traversal swallows errors
from w.addDirRecursive (and the filepath.Walk used inside it), leaving subtrees
unwatched; modify the Create branch to check and propagate the error from
w.addDirRecursive (e.g., if err := w.addDirRecursive(event.Name); err != nil {
return err } or send the error to the component's existing reset/error channel)
and likewise update the traversal logic inside addDirRecursive (or the
filepath.Walk callback) to return any walk errors instead of discarding them so
callers can detect and handle watch-registration failures.

Comment on lines +316 to +323
paths := uniqueNonEmptyPaths(
cfg.Paths.DAGsDir,
cfg.Paths.AltDAGsDir,
)
for _, dagRoot := range paths {
service.watchers = append(service.watchers, newRecursiveWatcher(
dagRoot,
dagRoot == cfg.Paths.DAGsDir,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Normalize DAGsDir before deciding createRoot.

uniqueNonEmptyPaths cleans the configured paths, but Line 323 compares the cleaned dagRoot to the raw cfg.Paths.DAGsDir. A value like /var/lib/dagu/dags/ or ./dags flips createRoot to false, so the primary DAG root is no longer auto-created and the watcher quietly no-ops when that directory is missing.

Suggested fix
+	primaryDAGRoot := ""
+	if cfg.Paths.DAGsDir != "" {
+		primaryDAGRoot = filepath.Clean(cfg.Paths.DAGsDir)
+	}
+
 	paths := uniqueNonEmptyPaths(
 		cfg.Paths.DAGsDir,
 		cfg.Paths.AltDAGsDir,
 	)
 	for _, dagRoot := range paths {
 		service.watchers = append(service.watchers, newRecursiveWatcher(
 			dagRoot,
-			dagRoot == cfg.Paths.DAGsDir,
+			dagRoot == primaryDAGRoot,
 			service.handleDAGFileEvent,
 			service.publishReset,
 		))
 	}

Also applies to: 348-360

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/service/frontend/sse/app_stream.go` around lines 316 - 323, Compare
normalized/cleaned paths when deciding the createRoot boolean: call the same
normalization used by uniqueNonEmptyPaths on cfg.Paths.DAGsDir (e.g.,
filepath.Clean/absolute equivalent) and compare that cleaned value to dagRoot
(which is already cleaned) before passing the boolean to newRecursiveWatcher;
apply the same fix for the second occurrence around the other watcher creation
(lines 348-360) so createRoot is true when the logical primary DAGsDir matches
regardless of trailing slashes or relative paths.

Comment on lines +104 to +116
useEffect(() => {
if (!selectedTemplate) {
setSelectedDag(null);
return;
}
if (!isOpen) {
return;
}
const found = filteredDags.find((d) => d.fileName === selectedTemplate);
if (found) {
setSelectedDag(found);
}
}, [selectedTemplate, filteredDags]);
}, [filteredDags, isOpen, selectedTemplate]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep the selected template label available while the dropdown is closed.

selectedDag is only rehydrated while the menu is open, then it gets cleared again on remote-node changes. A preselected selectedTemplate can therefore render as “Select template...” until the user reopens the selector, which makes the current selection look lost.

Also applies to: 163-171

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/features/cockpit/components/TemplateSelector.tsx` around lines 104 -
116, The effect in TemplateSelector.tsx clears selectedDag whenever the dropdown
is closed because it returns early on !isOpen and also clears selectedDag when
selectedTemplate exists but the menu is closed, causing the label to disappear;
update the useEffect that references selectedTemplate/filteredDags/isOpen so it
only sets selectedDag(null) when selectedTemplate is falsy and always tries to
find and set the matching dag from filteredDags (i.e., remove the early return
on isOpen), and apply the same change to the similar effect around lines 163-171
so the preselected selectedTemplate keeps its label even when the dropdown is
closed (refer to selectedDag, selectedTemplate, filteredDags, isOpen in the
effect).

Comment on lines +60 to 70
const { data, error } = await client.POST('/workspaces', {
params: { query: { remoteNode } },
body: { name },
});
if (!error) {
mutate();
selectWorkspace(name);
if (!error && data) {
applyWorkspaces((prev) => {
const next = [...prev.filter((ws) => ws.id !== data.id), data];
return next.sort((a, b) => a.name.localeCompare(b.name));
});
selectWorkspace(data.name);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silently ignoring errors in createWorkspace may hide failures.

When the POST request fails, the error is not surfaced or logged. Users won't know why the workspace wasn't created.

🛡️ Consider surfacing or logging errors
       const { data, error } = await client.POST('/workspaces', {
         params: { query: { remoteNode } },
         body: { name },
       });
-      if (!error && data) {
+      if (error) {
+        console.error('Failed to create workspace:', error);
+        return;
+      }
+      if (data) {
         applyWorkspaces((prev) => {

Alternatively, consider returning the error or updating a local error state that the UI can display.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data, error } = await client.POST('/workspaces', {
params: { query: { remoteNode } },
body: { name },
});
if (!error) {
mutate();
selectWorkspace(name);
if (!error && data) {
applyWorkspaces((prev) => {
const next = [...prev.filter((ws) => ws.id !== data.id), data];
return next.sort((a, b) => a.name.localeCompare(b.name));
});
selectWorkspace(data.name);
}
const { data, error } = await client.POST('/workspaces', {
params: { query: { remoteNode } },
body: { name },
});
if (error) {
console.error('Failed to create workspace:', error);
return;
}
if (data) {
applyWorkspaces((prev) => {
const next = [...prev.filter((ws) => ws.id !== data.id), data];
return next.sort((a, b) => a.name.localeCompare(b.name));
});
selectWorkspace(data.name);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/features/cockpit/hooks/useCockpitState.ts` around lines 60 - 70, The
POST call to create a workspace using client.POST currently swallows failures;
update the createWorkspace flow to surface failures by handling the returned
error: when client.POST('/workspaces', ...) returns an error, log it (e.g., via
console.error or an existing logger), and either return the error to callers or
set a local error state so the UI can display it; keep the existing success path
that calls applyWorkspaces(...) and selectWorkspace(...), but ensure the error
branch does not silently no-op—propagate or persist the error for user feedback.

Comment on lines +1 to +8
import React from 'react';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { AppBarContext } from '@/contexts/AppBarContext';
import { useQuery } from '@/hooks/api';
import { useLiveConnection } from '@/hooks/useAppLive';
import DAGRunDetailsPanel from '../DAGRunDetailsPanel';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing GPL v3 license header.

This file is missing the required license header. As per coding guidelines, source files should include GPL v3 license headers.

🔧 Add license header
+// Copyright (C) 2026 Yota Hamada
+// SPDX-License-Identifier: GPL-3.0-or-later
+
 import React from 'react';
 import { render, screen } from '@testing-library/react';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import React from 'react';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { AppBarContext } from '@/contexts/AppBarContext';
import { useQuery } from '@/hooks/api';
import { useLiveConnection } from '@/hooks/useAppLive';
import DAGRunDetailsPanel from '../DAGRunDetailsPanel';
// Copyright (C) 2026 Yota Hamada
// SPDX-License-Identifier: GPL-3.0-or-later
import React from 'react';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { AppBarContext } from '@/contexts/AppBarContext';
import { useQuery } from '@/hooks/api';
import { useLiveConnection } from '@/hooks/useAppLive';
import DAGRunDetailsPanel from '../DAGRunDetailsPanel';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ui/src/features/dag-runs/components/dag-run-details/__tests__/DAGRunDetailsPanel.test.tsx`
around lines 1 - 8, Add the required GPL v3 license header to the top of this
test file (DAGRunDetailsPanel.test.tsx) above the imports: include the standard
GPL v3 notice (or SPDX identifier such as "SPDX-License-Identifier:
GPL-3.0-or-later") plus the copyright/owner line used across the repo so the
file legally matches other sources; ensure the header sits before any code
(imports like React, render, MemoryRouter, etc.) so linters and tooling
recognize it.

Comment on lines +1 to +6
import React from 'react';
import { cleanup, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { AppBarContext } from '@/contexts/AppBarContext';
import { useQuery } from '@/hooks/api';
import { StepMessagesTable } from '../StepMessagesTable';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add GPL v3 license header to new test file.

This is a new file and is missing the required GPL v3 license header. As per coding guidelines, source files should have GPL v3 license headers managed via make addlicense.

📜 Proposed fix to add license header
+// Copyright (C) 2026 Yota Hamada
+// SPDX-License-Identifier: GPL-3.0-or-later
+
 import React from 'react';
 import { cleanup, render, screen } from '@testing-library/react';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import React from 'react';
import { cleanup, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { AppBarContext } from '@/contexts/AppBarContext';
import { useQuery } from '@/hooks/api';
import { StepMessagesTable } from '../StepMessagesTable';
// Copyright (C) 2026 Yota Hamada
// SPDX-License-Identifier: GPL-3.0-or-later
import React from 'react';
import { cleanup, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { AppBarContext } from '@/contexts/AppBarContext';
import { useQuery } from '@/hooks/api';
import { StepMessagesTable } from '../StepMessagesTable';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ui/src/features/dags/components/chat-history/__tests__/StepMessagesTable.test.tsx`
around lines 1 - 6, This new test file (StepMessagesTable.test.tsx) is missing
the required GPL v3 license header; add the standard GPL v3 license header to
the top of the file (above the imports) either by running the repository helper
(make addlicense) or by inserting the project’s canonical GPL v3 header manually
so the file header matches other source files and CI checks; ensure the header
is placed before the existing imports and saved.

@@ -0,0 +1,337 @@
import { getAuthToken } from '@/lib/authHeaders';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add the standard GPL header to this new file.

Please run make addlicense here before merging.

As per coding guidelines, **/*.{go,ts,tsx,js}: Apply GPL v3 license headers on source files, managed via make addlicense.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/hooks/AppLiveManager.ts` at line 1, This new file AppLiveManager.ts is
missing the project's required GPL v3 license header; add the standard GPL
header to the top of AppLiveManager.ts (the file that currently imports
getAuthToken) and then run the repository task to apply/verify headers by
executing make addlicense before merging so the file conforms to the
**/*.{go,ts,tsx,js} GPL header policy.

@@ -0,0 +1,241 @@
import { useContext, useEffect, useRef, useState } from 'react';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add the standard GPL header to this new file.

Please run make addlicense here before merging.

As per coding guidelines, **/*.{go,ts,tsx,js}: Apply GPL v3 license headers on source files, managed via make addlicense.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/hooks/useAppLive.ts` at line 1, This new file (useAppLive.ts — seen by
the import line "import { useContext, useEffect, useRef, useState } from
'react';") is missing the required GPL v3 license header; run the repository's
license tool (make addlicense) in the ui/src directory (or project root as
configured) to insert the standard GPL header for TypeScript files, then
recommit the updated file so it conforms to the coding guideline for
**/*.{go,ts,tsx,js}.

Comment thread ui/test-results/.last-run.json Outdated
Comment on lines +1 to +6
{
"status": "failed",
"failedTests": [
"1b112854bd09624bf1f9-fa870e5e77ba8b4c5af0"
]
} No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not commit transient test-run state files.

This file captures local/CI runtime state (status: failed) and will cause noisy, non-deterministic diffs across runs. Please remove it from the PR and ignore ui/test-results/.last-run.json in VCS unless intentionally versioned as a fixture.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/test-results/.last-run.json` around lines 1 - 6, The committed JSON
contains transient test-run state keys "status" and "failedTests" and should not
be versioned; remove the file from the PR (delete the committed .last-run.json),
add an ignore rule to your repo's .gitignore to exclude the
test-results/.last-run.json pattern (or the test-results directory’s transient
artifacts), and if you need a deterministic example add a checked-in fixture
file under a clearly named fixtures path instead of this runtime output.

Comment on lines +1 to +154
# Page snapshot

```yaml
- generic [ref=e4]:
- complementary [ref=e5]:
- navigation [ref=e7]:
- generic [ref=e8]:
- generic [ref=e9]:
- button "Collapse sidebar" [ref=e10]:
- generic:
- generic: D
- img [ref=e12]
- generic [ref=e14]: Dagu
- navigation [ref=e15]:
- combobox [ref=e17]:
- generic [ref=e18]:
- img
- generic [ref=e19]: local
- img
- generic [ref=e20]:
- generic [ref=e21]:
- generic [ref=e22]: Overview
- link "Cockpit" [ref=e24] [cursor=pointer]:
- /url: /cockpit
- img [ref=e27]
- generic [ref=e30]: Cockpit
- link "Dashboard" [ref=e32] [cursor=pointer]:
- /url: /dashboard
- img [ref=e34]
- generic [ref=e35]: Dashboard
- link "Docs" [ref=e37] [cursor=pointer]:
- /url: /docs
- img [ref=e39]
- generic [ref=e42]: Docs
- generic [ref=e43]:
- generic [ref=e44]: Workflows
- link "Definitions" [ref=e46] [cursor=pointer]:
- /url: /dags
- img [ref=e48]
- generic [ref=e53]: Definitions
- link "Runs" [ref=e55] [cursor=pointer]:
- /url: /dag-runs
- img [ref=e57]
- generic [ref=e61]: Runs
- link "Queues" [ref=e63] [cursor=pointer]:
- /url: /queues
- img [ref=e65]
- generic [ref=e68]: Queues
- link "Search" [ref=e70] [cursor=pointer]:
- /url: /search
- img [ref=e72]
- generic [ref=e75]: Search
- link "Base Config" [ref=e77] [cursor=pointer]:
- /url: /base-config
- img [ref=e79]
- generic [ref=e91]: Base Config
- generic [ref=e92]:
- generic [ref=e93]: Settings
- link "System Status" [ref=e95] [cursor=pointer]:
- /url: /system-status
- img [ref=e97]
- generic [ref=e99]: System Status
- link "Remote Nodes" [ref=e101] [cursor=pointer]:
- /url: /remote-nodes
- img [ref=e103]
- generic [ref=e106]: Remote Nodes
- link "Audit Logs (Pro)" [ref=e108] [cursor=pointer]:
- /url: /audit-logs
- img [ref=e110]
- generic [ref=e113]: Audit Logs (Pro)
- generic [ref=e114]:
- button "Agent" [ref=e116]:
- img [ref=e118]
- generic [ref=e121]: Agent
- img [ref=e123]
- generic [ref=e125]:
- link "Settings" [ref=e127] [cursor=pointer]:
- /url: /agent-settings
- img [ref=e129]
- generic [ref=e132]: Settings
- link "Memory" [ref=e134] [cursor=pointer]:
- /url: /agent-memory
- img [ref=e136]
- generic [ref=e144]: Memory
- link "Skills" [ref=e146] [cursor=pointer]:
- /url: /agent-skills
- img [ref=e148]
- generic [ref=e150]: Skills
- link "Souls" [ref=e152] [cursor=pointer]:
- /url: /agent-souls
- img [ref=e154]
- generic [ref=e156]: Souls
- generic [ref=e157]:
- generic [ref=e158]: Admin
- link "License" [ref=e160] [cursor=pointer]:
- /url: /license
- img [ref=e162]
- generic [ref=e164]: License
- generic [ref=e165]:
- generic [ref=e166]:
- button "Agent" [ref=e167]:
- img [ref=e169]
- generic [ref=e171]: Agent
- button "Dark Mode" [ref=e172]:
- img [ref=e174]
- generic [ref=e176]: Dark Mode
- generic [ref=e177]: vv2.3.1-11-ge4e4c088
- main [ref=e179]:
- generic [ref=e180]:
- generic [ref=e181]:
- text: "Update available: vv2.3.1-11-ge4e4c088 → v2.3.1"
- link "View release" [ref=e182] [cursor=pointer]:
- /url: https://github.com/dagu-org/dagu/releases
- generic [ref=e183]:
- text: · Run
- code [ref=e184]: dagu upgrade
- text: to update
- button "Dismiss update notification" [ref=e185]:
- img [ref=e186]
- generic [ref=e190]:
- generic [ref=e191]:
- combobox [ref=e193]:
- generic: All workspaces
- img
- button "Select template..." [ref=e195] [cursor=pointer]:
- img [ref=e196]
- generic [ref=e199]: Select template...
- img [ref=e200]
- generic [ref=e202]:
- generic [ref=e203]:
- heading "2026-03-21 Sat" [level=2] [ref=e205]
- generic [ref=e206]: No runs
- generic [ref=e207]:
- heading "2026-03-20 Fri" [level=2] [ref=e209]
- generic [ref=e210]: Loading runs...
- generic [ref=e211]:
- heading "2026-03-19 Thu" [level=2] [ref=e213]
- generic [ref=e214]: Loading runs...
- generic [ref=e215]:
- heading "2026-03-18 Wed" [level=2] [ref=e217]
- generic [ref=e218]: Loading runs...
- generic [ref=e219]:
- heading "2026-03-17 Tue" [level=2] [ref=e221]
- generic [ref=e222]: Loading runs...
- generic [ref=e223]:
- heading "2026-03-16 Mon" [level=2] [ref=e225]
- generic [ref=e226]: Loading runs...
- generic [ref=e227]:
- heading "2026-03-15 Sun" [level=2] [ref=e229]
- generic [ref=e230]: Loading runs...
- generic [ref=e231]:
- heading "2026-03-14 Sat" [level=2] [ref=e233]
- generic [ref=e234]: Loading runs...
``` No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether test-results artifacts are intended to be versioned.
# Expected: either explicit allowlist in tests/docs, or ignore patterns for transient outputs.

set -euo pipefail

echo "== Search for references to ui/test-results as committed fixtures =="
rg -n -C2 'ui/test-results|test-results/.last-run|error-context.md' README.md .gitignore .eslintignore .prettierignore package.json pnpm-workspace.yaml || true

echo
echo "== Find ignore rules for test-results artifacts =="
rg -n -C2 'test-results|\\.last-run\\.json|error-context\\.md' .gitignore **/.gitignore || true

Repository: dagu-org/dagu

Length of output: 417


🏁 Script executed:

# Check if the file exists and is tracked in git
git ls-files | grep -E 'ui/test-results|error-context' || echo "No matches in git index"

echo "---"

# Check git status for this file
git status --short | grep -E 'test-results|error-context' || echo "No staged/unstaged changes for these files"

echo "---"

# Read .gitignore to see actual ignore patterns
echo "=== .gitignore content ==="
cat .gitignore || echo ".gitignore not found"

Repository: dagu-org/dagu

Length of output: 779


🏁 Script executed:

# Check if test infrastructure references or expects these files
rg -i 'test-results|error-context|\.last-run' --type-list 2>/dev/null | head -20 || true

echo "---"

# Search in source code for references
rg -i 'test-results|error-context' . --type py --type go --type ts --type js 2>/dev/null | head -20 || echo "No source code references found"

echo "---"

# Check if there are any docs about UI testing or test artifacts
find . -maxdepth 3 -name '*test*.md' -o -name '*ui*.md' 2>/dev/null | grep -v node_modules | head -10 || true

Repository: dagu-org/dagu

Length of output: 64


Remove committed test artifacts and add ignore rules.

The file ui/test-results/dagu-cockpit-autoupdate-ch-dc750--run-without-manual-refresh/error-context.md is run-specific debug output that should not be committed to the repository. It contains volatile data (timestamps, version strings, UI element references) and is not referenced by any source code or test infrastructure.

Additionally, add ui/test-results/ to .gitignore to prevent similar transient artifacts from being accidentally committed in the future.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ui/test-results/dagu-cockpit-autoupdate-ch-dc750--run-without-manual-refresh/error-context.md`
around lines 1 - 154, Delete the committed run-specific artifact
ui/test-results/dagu-cockpit-autoupdate-ch-dc750--run-without-manual-refresh/error-context.md
from the repo and add a rule for ui/test-results/ to .gitignore so future
transient test outputs aren’t tracked; remove the file from version control
(ensure it’s deleted/unstaged from the index) and commit the updated .gitignore
and deletion together, and verify no source or tests reference error-context.md
before pushing.

@yottahmd yottahmd marked this pull request as draft March 21, 2026 10:57
@yottahmd yottahmd marked this pull request as ready for review March 21, 2026 10:58
@yottahmd yottahmd merged commit 3b2f2f5 into main Mar 21, 2026
6 checks passed
@yottahmd yottahmd deleted the fix-sse-bug branch March 21, 2026 11:30
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.22%. Comparing base (c33fb51) to head (bd01f76).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1816      +/-   ##
==========================================
+ Coverage   69.18%   69.22%   +0.03%     
==========================================
  Files         426      426              
  Lines       51421    51421              
==========================================
+ Hits        35576    35595      +19     
+ Misses      12827    12809      -18     
+ Partials     3018     3017       -1     
Files with missing lines Coverage Δ
skills/embed.go 100.00% <ø> (ø)

... and 12 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c33fb51...bd01f76. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

1 participant