Skip to content

feat: add cockpit workspace kanban view#1728

Merged
yottahmd merged 11 commits intomainfrom
cockpit
Mar 6, 2026
Merged

feat: add cockpit workspace kanban view#1728
yottahmd merged 11 commits intomainfrom
cockpit

Conversation

@yottahmd
Copy link
Copy Markdown
Collaborator

@yottahmd yottahmd commented Mar 5, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Cockpit page for managing workspaces and DAG templates.
    • Workspace management API with create, read, update, and delete operations.
    • Kanban board view displaying DAG runs organized by date and execution status.
    • DAG preview modal with real-time updates via streaming.
    • Embedded agent chat integrated into Cockpit.
    • Workspace-based filtering and organization.
  • UI/UX

    • Updated navigation menu with new Cockpit and Settings sections.
    • Minor styling refinements to form components.
  • Dependencies

    • Added motion animation library.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 5, 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: 9d3edbc7-08d2-46e5-92ab-e17d3a06b44f

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 complete workspace management feature to the platform, including new OpenAPI endpoint definitions for workspace CRUD operations, backend domain models with file-based persistence, configuration updates, API handlers, and a comprehensive React-based UI with workspace selection, DAG run Kanban visualization, and cockpit page integration. Additionally, it adds a new DAG code generation tool for the agent and updates navigation menu structure.

Changes

Cohort / File(s) Summary
API Definition
api/v1/api.yaml, ui/src/api/v1/schema.ts
Added OpenAPI endpoints and schemas for workspace CRUD operations (/workspaces, /workspaces/{workspaceId}) with CreateWorkspaceRequest, UpdateWorkspaceRequest, WorkspaceResponse, and WorkspaceListResponse schemas. Security definitions extended for workspace operations.
Domain & Persistence
internal/workspace/workspace.go, internal/workspace/store.go, internal/persis/fileworkspace/store.go, internal/persis/fileworkspace/store_test.go
Introduces Workspace domain model with constructor, storage format, conversion methods, Store interface with CRUD contract, and file-based implementation using JSON persistence with in-memory indexing, including comprehensive tests.
Configuration
internal/cmn/config/config.go, internal/cmn/config/definition.go, internal/cmn/config/loader.go
Added WorkspacesDir path configuration field to support workspace storage directory with automatic path derivation under the data directory.
Audit & API Setup
internal/service/audit/entry.go, internal/service/frontend/api/v1/api.go, internal/service/frontend/server.go
Added workspace audit category constant, new APIOption WithWorkspaceStore for DI, and server-level workspace store initialization with fallback logging on failure.
API Handlers
internal/service/frontend/api/v1/workspaces.go
Implements five CRUD handler methods (ListWorkspaces, CreateWorkspace, GetWorkspace, UpdateWorkspace, DeleteWorkspace) with permission validation, error translation, audit logging, and response mapping.
Agent DAG Codegen
internal/agent/dag_codegen.go, internal/agent/dag_codegen_test.go
New DAG code generation tool with input validation, YAML generation, cycle detection, and post-generation validation, including comprehensive unit tests for parallel/sequential DAGs and error cases.
Cockpit Frontend Pages
ui/src/pages/cockpit/index.tsx, ui/src/features/cockpit/components/CockpitToolbar.tsx, ui/src/features/cockpit/components/CockpitPage.tsx
Introduces CockpitPage with workspace selection and template management UI, CockpitToolbar orchestrating workspace/template selectors and DAG preview modal.
Cockpit UI Components
ui/src/features/cockpit/components/WorkspaceSelector.tsx, ui/src/features/cockpit/components/TemplateSelector.tsx, ui/src/features/cockpit/components/DAGPreviewModal.tsx, ui/src/features/cockpit/components/EmbeddedAgentChat.tsx
Implements workspace selector with create/delete dialogs, template dropdown with tag filtering and grouping, DAG preview modal with SSE updates and enqueue capability, and embedded agent chat with session management and auto-selection logic.
Kanban Visualization
ui/src/features/cockpit/components/KanbanBoard.tsx, ui/src/features/cockpit/components/KanbanColumn.tsx, ui/src/features/cockpit/components/KanbanCard.tsx, ui/src/features/cockpit/components/DateKanbanList.tsx, ui/src/features/cockpit/components/DateKanbanSection.tsx
Introduces multi-level Kanban components for displaying DAG runs by status (Queued/Running/Done/Failed) with infinite scroll date navigation, elapsed time display, and animation support via Framer Motion.
Cockpit Hooks
ui/src/features/cockpit/hooks/useCockpitState.ts, ui/src/features/cockpit/hooks/useDateKanbanData.ts, ui/src/features/cockpit/hooks/useInfiniteKanban.ts
Introduces three custom hooks managing workspace/template selection, Kanban column aggregation with SSE synchronization, and infinite date range pagination respectively.
DAG Component Extensions
ui/src/features/dags/components/dag-details/DAGDetailsContent.tsx, ui/src/features/dags/components/dag-details/DAGHeader.tsx, ui/src/features/dags/components/common/DAGActions.tsx
Adds optional onEnqueue callback prop to enable custom enqueue handlers, allowing DAGPreviewModal to inject workspace tags and override default enqueue behavior.
Navigation & Styling
ui/src/menu.tsx, ui/src/App.tsx, ui/src/components/ui/date-range-picker.tsx, ui/src/components/ui/select.tsx, ui/src/components/ui/tag-combobox.tsx, ui/src/pages/dag-runs/index.tsx, ui/src/pages/queues/index.tsx
Restructures sidebar navigation with new Overview/Cockpit sections, Agent group, Settings reorganization, adds /cockpit route, and applies minor styling tweaks to component heights and background colors.
Dependencies
ui/package.json
Added framer-motion ^12.35.0 for animation support in Kanban and other UI components.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as Cockpit UI
    participant API as Backend API
    participant Store as Workspace Store
    participant FS as File System

    User->>UI: Select/Create Workspace
    UI->>API: POST /workspaces or GET /workspaces
    API->>Store: Create(ctx, ws) or List(ctx)
    Store->>FS: Write/Read JSON files
    FS-->>Store: File I/O complete
    Store-->>API: Workspace(s) returned
    API-->>UI: WorkspaceResponse(s)
    UI->>UI: Update selectedWorkspace state
    
    User->>UI: Select DAG Template
    UI->>API: Fetch DAG metadata
    API-->>UI: DAG details
    UI->>UI: Display DAGPreviewModal
    
    User->>UI: Click Enqueue on DAG
    UI->>UI: Inject workspace tag into DAG spec
    UI->>API: POST /dag-runs/enqueue with workspace tag
    API-->>UI: DAGRun created
    UI->>UI: Display run in Kanban board
Loading
sequenceDiagram
    participant User as User
    participant Cockpit as Cockpit Page
    participant Kanban as DateKanbanList
    participant SSE as SSE Stream
    participant API as Backend API

    Cockpit->>Kanban: Render with selectedWorkspace
    Kanban->>Kanban: useInfiniteKanban → loadedDates
    loop For Each Loaded Date
        Kanban->>API: Fetch DAG runs for date range
        API-->>Kanban: DAG runs
        Kanban->>Kanban: useDateKanbanData → group by status
        Kanban->>Kanban: Render DateKanbanSection
    end
    
    Note over Cockpit: Real-time updates via SSE
    Kanban->>SSE: Subscribe to DAG run updates (if isToday)
    SSE-->>Kanban: New/updated run
    Kanban->>Kanban: Update columns via mutation
    Kanban->>Kanban: Re-render Kanban cards
    
    User->>Kanban: Scroll to bottom
    Kanban->>Kanban: IntersectionObserver triggers
    Kanban->>Kanban: loadNextDate()
    Kanban->>API: Fetch previous day runs
    API-->>Kanban: Older runs
    Kanban->>Kanban: Append to loadedDates
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The PR introduces a cohesive but substantial feature spanning backend domain models, persistence layer, API endpoints, handlers, and an extensive React component tree with custom hooks. While individual changes follow consistent patterns (workspace CRUD, Kanban components, hook definitions), the heterogeneous nature across multiple domains (Go backend, TypeScript/React frontend, OpenAPI definitions, configuration) and the interplay between workspace selection, DAG run visualization, and enqueue mechanics require careful review of control flow integration points, state management patterns, error handling consistency, and permission checks.

Possibly related PRs

  • fix(ui): redo dashboard page design #1547: Modifies the same file (ui/src/features/dags/components/dag-details/DAGDetailsContent.tsx) to propagate the onEnqueue prop introduced in this PR.
  • Frontend refactoring #1616: Refactors frontend navigation menu structure (ui/src/menu.tsx), directly impacting the sidebar integration with new Cockpit sections.
  • fix(ui): refactor small UI issues #1504: Extends ui/src/features/dags/components/common/DAGActions.tsx with additional state management that coordinates with the new onEnqueue callback pattern.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% 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 PR title 'feat: add cockpit workspace kanban view' accurately reflects the main feature addition: a new Cockpit page with workspace selection and Kanban-based DAG run visualization.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cockpit

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: 14

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (7)
ui/src/features/cockpit/components/TemplateSelector.tsx-76-85 (1)

76-85: ⚠️ Potential issue | 🟡 Minor

Clear cached selection when the selected template is no longer present.

If filteredDags no longer contains selectedTemplate, selectedDag stays stale and can display the wrong label.

Suggested fix
-    const found = filteredDags.find((d) => d.fileName === selectedTemplate);
-    if (found) {
-      setSelectedDag(found);
-    }
+    const found = filteredDags.find((d) => d.fileName === selectedTemplate);
+    setSelectedDag(found ?? null);
🤖 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 76 -
85, The effect in useEffect that syncs selectedTemplate with selectedDag can
leave selectedDag stale when filteredDags no longer contains the
selectedTemplate; update the effect so that after searching
filteredDags.find((d) => d.fileName === selectedTemplate) you explicitly clear
the selection when not found (call setSelectedDag(null)) in addition to setting
it when found, keeping the same dependency array [selectedTemplate,
filteredDags] and referencing the existing symbols selectedTemplate,
filteredDags, setSelectedDag, and selectedDag.
ui/src/features/cockpit/components/WorkspaceSelector.tsx-58-61 (1)

58-61: ⚠️ Potential issue | 🟡 Minor

Use explicit compact sizing/padding for the input and select trigger.

These controls currently rely on component defaults, which can render taller than the surrounding compact toolbar controls.

Suggested class adjustments
-          className="px-2 text-xs w-40"
+          className="h-7 w-40 px-2 py-0.5 text-xs bg-background"
...
-        <SelectTrigger className="text-xs w-40">
+        <SelectTrigger className="h-7 w-40 px-2 py-0.5 text-xs bg-background">

As per coding guidelines "Use information-dense, compact UI design with minimal whitespace, small form element heights (h-7 or smaller for select boxes), and reduced padding" and "Ensure all form inputs use compact padding (py-0.5 or py-1) and consistent background colors matching related elements".

Also applies to: 82-83

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

In `@ui/src/features/cockpit/components/WorkspaceSelector.tsx` around lines 58 -
61, The Input control (ref inputRef) and the corresponding Select trigger should
use explicit compact sizing and padding rather than rely on defaults; update the
Input's className to include a fixed height (h-7), compact vertical padding
(py-0.5), small text (text-xs), tight line-height (leading-none) and existing
horizontal padding/width (px-2 w-40) and apply the same class set to the Select
trigger element so both controls match in height, padding and background color
(use the same bg-... utility or remove bg to inherit) for consistent,
information-dense toolbar styling.
ui/src/features/cockpit/components/TemplateSelector.tsx-318-339 (1)

318-339: ⚠️ Potential issue | 🟡 Minor

Use wrapping classes for long DAG names/descriptions in the list.

Current truncate usage clips content; this list should wrap long strings to avoid losing essential metadata.

Suggested class changes
-                          <span className={cn(
-                            "font-medium text-xs truncate",
+                          <span className={cn(
+                            "font-medium text-xs whitespace-normal break-words",
                             dag.errors?.length > 0 && "text-destructive"
                           )}>
...
-                          <div className="text-[11px] text-muted-foreground truncate">
+                          <div className="text-[11px] text-muted-foreground whitespace-normal break-words">
                             {description}
                           </div>

As per coding guidelines "Always handle long text in tables and lists with whitespace-normal break-words to prevent layout overflow".

🤖 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 318 -
339, The list currently uses "truncate" on the DAG name and description in
TemplateSelector (the span with className built via cn for dag.dag.name and the
description div), which clips long text; replace those "truncate" uses with
wrapping classes "whitespace-normal break-words" (and remove/avoid "truncate")
so long DAG names and descriptions wrap instead of being clipped, and ensure the
parent container (the surrounding div rendering the name/description) allows
wrapping by not forcing no-wrap behavior.
ui/src/features/cockpit/components/EmbeddedAgentChat.tsx-82-86 (1)

82-86: ⚠️ Potential issue | 🟡 Minor

Don’t swallow send failures silently.

sendMessage(...).catch(() => {}) drops actionable errors and can leave users without feedback when message send fails.

Suggested error handling
-  const handleSend = useCallback(
-    (message: string, dagContexts?: DAGContext[], model?: string, soulId?: string): void => {
-      setInitialInputValue(null);
-      sendMessage(message, model, dagContexts, soulId).catch(() => {});
-    },
-    [sendMessage, setInitialInputValue]
-  );
+  const handleSend = useCallback(
+    async (message: string, dagContexts?: DAGContext[], model?: string, soulId?: string): Promise<void> => {
+      setInitialInputValue(null);
+      try {
+        await sendMessage(message, model, dagContexts, soulId);
+      } catch (err) {
+        setError(err instanceof Error ? err.message : 'Failed to send message');
+      }
+    },
+    [sendMessage, setInitialInputValue, setError]
+  );

As per coding guidelines "Use error handling with try/catch for async operations".

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

In `@ui/src/features/cockpit/components/EmbeddedAgentChat.tsx` around lines 82 -
86, Replace the silent promise swallow in handleSend by making the callback
async and wrapping the sendMessage call in try/catch: in the handleSend function
(which calls setInitialInputValue and sendMessage), await sendMessage(message,
model, dagContexts, soulId) inside a try block, and in the catch block log the
error and surface user feedback (e.g., call the app's toast/error handler or set
an error state) so failures are visible; ensure you still clear or restore input
via setInitialInputValue as appropriate and do not leave the promise unhandled.
ui/src/App.tsx-237-237 (1)

237-237: ⚠️ Potential issue | 🟡 Minor

Wrap cockpit route with feature guard or add disabled state handling.

The /cockpit route is accessible to all authenticated users, but the menu item is only shown when config.agentEnabled is true. Users can navigate directly to /cockpit even when the agent feature is disabled.

The codebase uses route-level guards for similar feature-gated pages (e.g., <DeveloperElement> for /system-status, <LicensedRoute> for /audit-logs). Apply the same pattern here:

  • Either wrap the route with a feature guard component
  • Or have CockpitPage gracefully handle the disabled state and show an appropriate message
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/App.tsx` at line 237, The /cockpit route is exposed even when the
agent feature is disabled; update the routing so access is gated by the same
feature-guard pattern used elsewhere: wrap the Route element for path="/cockpit"
(currently rendering CockpitPage) with a feature guard that checks
config.agentEnabled (e.g., create/ reuse a guard similar to DeveloperElement or
LicensedRoute that returns <Navigate/> or a “feature disabled” fallback when
false), or alternatively update CockpitPage to render a graceful disabled-state
UI when config.agentEnabled is false; ensure you reference the Route
path="/cockpit", CockpitPage, and config.agentEnabled when implementing the
guard or the in-component handling.
ui/src/features/cockpit/components/KanbanCard.tsx-47-50 (1)

47-50: ⚠️ Potential issue | 🟡 Minor

Set explicit button type to prevent accidental form submit.

<button> defaults to type="submit". If this card is ever rendered inside a form, clicking it can trigger unintended submission.

🔧 Proposed fix
       <button
+        type="button"
         onClick={onClick}
         className="w-full text-left p-2 rounded-md border border-border bg-card hover:bg-accent/50 transition-colors cursor-pointer"
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/features/cockpit/components/KanbanCard.tsx` around lines 47 - 50, In
the KanbanCard component update the JSX button element to include an explicit
type attribute (type="button") so clicks don't act as form submissions; locate
the <button ... onClick={onClick} className="w-full text-left p-2 rounded-md
border border-border bg-card hover:bg-accent/50 transition-colors
cursor-pointer"> and add type="button" to it to prevent accidental submits when
rendered inside a form.
api/v1/api.yaml-9675-9691 (1)

9675-9691: ⚠️ Potential issue | 🟡 Minor

Add non-empty validation for workspace name.

name currently permits empty strings in both create and update payloads, which weakens contract validation.

🛠️ Suggested schema hardening
    CreateWorkspaceRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
+          minLength: 1
        description:
          type: string

    UpdateWorkspaceRequest:
      type: object
      properties:
        name:
          type: string
+          minLength: 1
        description:
          type: string
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/v1/api.yaml` around lines 9675 - 9691, Add non-empty validation to the
workspace "name" field by updating both CreateWorkspaceRequest and
UpdateWorkspaceRequest: add a constraint like minLength: 1 (or an equivalent
pattern) to the name property so empty strings are rejected; keep name required
on CreateWorkspaceRequest and ensure UpdateWorkspaceRequest's name, when
present, also enforces minLength: 1.
🧹 Nitpick comments (4)
ui/src/menu.tsx (1)

266-270: Missing isExpanded in useEffect dependency array is intentional but should be documented.

The missing dependency is intentional to prevent collapsing when manually toggled, but adding a comment would clarify this to future maintainers.

💡 Suggested documentation
   React.useEffect(() => {
+    // Intentionally omitting isExpanded to only auto-expand on route navigation,
+    // not when user manually toggles the group
     if (isChildActive && !isExpanded) {
       setIsExpanded(true);
     }
   }, [isChildActive]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/menu.tsx` around lines 266 - 270, Add an inline comment above the
React.useEffect that references isChildActive, isExpanded, and setIsExpanded
explaining that omitting isExpanded from the dependency array is intentional:
the effect should only expand the item when a child becomes active
(isChildActive changes) but must not collapse when isExpanded is toggled
manually, so only isChildActive is listed as a dependency. Keep the comment
concise and mention the behavioral intent to prevent automatic collapsing.
ui/src/features/cockpit/components/DAGPreviewModal.tsx (1)

210-213: Use denser modal padding/header spacing for cockpit UI consistency.

Current p-6 + mb-4 makes this panel visually looser than the compact cockpit patterns.

🎨 Suggested refactor
-          <div className="p-6 w-full flex flex-col h-full dag-modal-content">
+          <div className="p-3 w-full flex flex-col h-full dag-modal-content">
@@
-            <div className="flex justify-between items-center mb-4">
+            <div className="flex justify-between items-center mb-2">

As per coding guidelines "Keep modal headers small and information-dense with minimal padding (p-2 or p-3 instead of p-4 or p-6) and support keyboard navigation".

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

In `@ui/src/features/cockpit/components/DAGPreviewModal.tsx` around lines 210 -
213, Replace the loose modal spacing in DAGPreviewModal by changing the wrapper
div's className from "p-6 w-full flex flex-col h-full dag-modal-content" to a
denser padding (e.g., "p-3 w-full flex flex-col h-full dag-modal-content") and
reduce the toolbar bottom margin from "mb-4" to "mb-2" so the header is visually
compact and matches cockpit patterns; after updating classes, verify the toolbar
and modal header elements in DAGPreviewModal remain keyboard-focusable
(tab/escape handling) to preserve keyboard navigation.
api/v1/api.yaml (1)

5962-5967: Extract workspaceId into a reusable parameter component.

workspaceId is defined inline three times; centralizing it under components/parameters will prevent drift.

♻️ Suggested refactor
  /workspaces/{workspaceId}:
    get:
      parameters:
-        - name: workspaceId
-          in: path
-          required: true
-          schema:
-            type: string
+        - $ref: "#/components/parameters/WorkspaceId"
         - $ref: "#/components/parameters/RemoteNode"
...
    patch:
      parameters:
-        - name: workspaceId
-          in: path
-          required: true
-          schema:
-            type: string
+        - $ref: "#/components/parameters/WorkspaceId"
         - $ref: "#/components/parameters/RemoteNode"
...
    delete:
      parameters:
-        - name: workspaceId
-          in: path
-          required: true
-          schema:
-            type: string
+        - $ref: "#/components/parameters/WorkspaceId"
         - $ref: "#/components/parameters/RemoteNode"
  parameters:
+    WorkspaceId:
+      name: workspaceId
+      in: path
+      required: true
+      description: "The unique identifier of the workspace"
+      schema:
+        type: string
+        minLength: 1

Also applies to: 5987-5992, 6024-6029

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

In `@api/v1/api.yaml` around lines 5962 - 5967, The inline workspaceId path
parameter is duplicated; extract it into a reusable components/parameters entry
named e.g. WorkspaceId (define schema type: string and required:true, in:path)
and replace each inline definition (the blocks where name: workspaceId in:path
required:true schema:type:string — occurrences near the existing RemoteNode ref)
with a $ref: "#/components/parameters/WorkspaceId"; update all three occurrences
so they reference the new component to prevent drift.
ui/src/api/v1/schema.ts (1)

11660-11840: Consider adding default error responses for workspace operations for type-safety parity.

Most operations in this file expose a default error response; workspace operations currently do not. Adding them in the OpenAPI source will make client-side error handling more uniform after regeneration.

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

In `@ui/src/api/v1/schema.ts` around lines 11660 - 11840, The workspace endpoints
(listWorkspaces, createWorkspace, getWorkspace, deleteWorkspace,
updateWorkspace) lack a default error response; add a default response entry to
each operation's responses using the existing Error schema (i.e. default: {
headers: { [name: string]: unknown }, content: { "application/json":
components["schemas"]["Error"] } }) so client-side error handling is uniform,
then regenerate the OpenAPI clients/spec artifacts.
🤖 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/agent/dag_codegen_test.go`:
- Line 1: The new test file (dag_codegen_test.go, package agent) is missing the
required GPL v3 license header; add the repository-standard header by running
the repository tool or adding the header manually: run `make addlicense` (which
applies the GPL v3 header to source files) or insert the project's GPL v3
license header at the top of dag_codegen_test.go so the file complies with the
repository's license header policy.

In `@internal/cmn/config/loader.go`:
- Line 966: The WorkspacesDir field is only added as a default
({&cfg.Paths.WorkspacesDir, "workspaces"}) but not included in path resolution
or environment binding, causing inconsistent behavior; update loadPathsConfig to
include paths.workspaces_dir in the resolution loop so relative values for
cfg.Paths.WorkspacesDir are resolved the same as other path fields, and add an
env binding entry for "paths.workspaces_dir" (or the existing env naming
convention used for other paths) so WorkspacesDir can be overridden from the
environment; locate references to WorkspacesDir, loadPathsConfig, and the paths
binding map in loader.go to mirror the handling used for the other path fields
and ensure the default, resolution, and env binding are all wired together.

In `@internal/service/frontend/api/v1/workspaces.go`:
- Around line 17-19: The handlers currently check a.workspaceStore == nil but
return inconsistent responses (e.g., ListWorkspaces uses
ListWorkspaces200JSONResponse with empty slice while other handlers return 404
or 503); treat a nil workspaceStore as an infrastructure/service unavailable
error across all endpoints. Update each handler that uses workspaceStore (refer
to checks for workspaceStore and response constructors like
ListWorkspaces200JSONResponse and the other handlers around the ranges noted) to
return a consistent ServiceUnavailable response (use the API’s
ServiceUnavailable response builder and include a short error message) instead
of 200 or 404 when workspaceStore == nil so all paths signal an unavailable
store.

In `@internal/workspace/workspace.go`:
- Line 1: The new file begins with "package workspace" but lacks the required
GPLv3 license header; run the repository tooling to add it (e.g., execute the
prescribed command such as make addlicense) or prepend the standard GPL v3
header to the top of the file so that the file containing package workspace
includes the repository-required license comment block.

In `@ui/src/features/cockpit/components/DAGPreviewModal.tsx`:
- Around line 153-156: The window.open call in DAGPreviewModal.tsx (where url =
`/dags/${fileName}/spec` and you call window.open(url, '_blank')) must include
no-opener and no-referrer to prevent the new page from accessing window.opener;
update the window.open invocation to include the security flags (e.g., pass
"noopener,noreferrer" as the third argument or use an anchor with rel="noopener
noreferrer"), or alternatively set the opened window's opener to null after
opening; change the code around the window.open call in the DAGPreviewModal
component accordingly.
- Around line 164-182: The keydown handler is registered even when the modal is
closed; update the useEffect that defines handleKeyDown to only attach the
listener while the modal is open (e.g., depend on an isOpen prop/state and
early-return if not open), and ensure you still remove the listener when the
modal closes; modify the dependency array to include isOpen and keep references
to shouldIgnoreKeyboardShortcuts, onClose, and handleFullscreenClick so Escape
and 'f' only act when the modal is open.

In `@ui/src/features/cockpit/components/EmbeddedAgentChat.tsx`:
- Around line 113-115: The icon-only sidebar toggle button in
EmbeddedAgentChat.tsx is missing accessible properties; update the button that
uses toggleSidebar and the PanelLeftClose/PanelLeftOpen icons to include
type="button" and an appropriate aria-label (e.g., "Toggle sidebar" or "Open
sidebar" / "Close sidebar" depending on sidebarOpen) so screen readers can
identify the action. Do the same for the error-dismiss action button (the button
that clears/dismisses errors) by adding type="button" and an aria-label like
"Dismiss error" while keeping its existing onClick handler intact.

In `@ui/src/features/cockpit/components/TemplateSelector.tsx`:
- Around line 199-222: The trigger button currently nests another <button>
(clear action) inside it which breaks HTML semantics and accessibility; refactor
TemplateSelector so the clear action is not a nested button: move the clear
control out of the main trigger rendered by handleOpen (or replace the inner
<button> with a non-nested interactive element such as a <span> or <div> with
role="button" and proper keyboard handlers), keep the clear logic using
onSelect('') and setSelectedDag(null) but remove e.stopPropagation(), ensure the
new control has an accessible label (aria-label) and handles onClick and
onKeyDown to support keyboard users, and preserve styles/icons (X) and visual
layout currently tied to selectedDagName.

In `@ui/src/features/cockpit/components/WorkspaceSelector.tsx`:
- Around line 37-65: Race between Enter key and blur causes duplicate onCreate
calls; fix by adding a ref guard (e.g., createdRef or submittingRef) checked
inside handleCreate so subsequent calls are no-ops after the first invocation,
set that ref to true before calling onCreate and before calling
setIsCreating(false), and reset it when opening creation flow; also update
handleKeyDown to set the guard when handling Enter or Escape (for Escape set the
guard to prevent onBlur-triggered create) and include that same guard check in
the Input onBlur handler path; finally adjust SelectTrigger's padding from
"py-2" to a compact value like "py-1" (or "py-0.5") to comply with the compact
form input guideline.

In `@ui/src/features/cockpit/hooks/useCockpitState.ts`:
- Around line 45-52: The deleteWorkspace callback currently ignores errors from
client.DELETE and always calls mutate() and setSelectedWorkspace(''), causing UI
desync; update deleteWorkspace to await client.DELETE inside a try/catch so that
mutate() and setSelectedWorkspace('') are only executed after a successful
response, and handle failures in the catch (e.g., log the error or surface a
user-visible error) while leaving the current selection intact; specifically
modify the deleteWorkspace function that calls
client.DELETE('/workspaces/{workspaceId}', ...), wrapping the request in
try/catch and moving mutate() and setSelectedWorkspace('') into the successful
path.
- Around line 19-30: The useEffect currently references createWorkspace before
it's defined causing a temporal dead zone; move the entire useEffect block so it
sits after the createWorkspace declaration (keep references to autoCreatedRef
and selectedWorkspace as before). Update the deleteWorkspace callback (the async
function that calls client.DELETE) to only clear selection and call mutate when
the DELETE returns no error (check the returned { error } and only run mutate()
and setSelectedWorkspace('') on success). Finally, include selectedWorkspace in
the useEffect dependency array so the effect reacts to selection changes.

In `@ui/src/features/cockpit/hooks/useDateKanbanData.ts`:
- Around line 63-67: sseParams used for the SSE subscription (const sseParams
and useDAGRunsListSSE) omits remoteNode which can cause cross-node updates;
update the useMemo that defines sseParams to include remoteNode in the returned
object and add remoteNode to the dependency array so the memo and SSE
subscription are routed to the same node as the list (mirror how useQuery routes
by node).

In `@ui/src/features/dags/components/common/DAGActions.tsx`:
- Around line 523-525: The await of onEnqueue(params, dagRunId, immediate) can
throw and needs a try/catch around it: wrap the await call in try { await
onEnqueue(...) } catch (err) { if (typeof onEnqueueError === "function") call
onEnqueueError(err, params, dagRunId, immediate); else log the error and surface
a user-friendly message via the app's notification utility (or console.error) so
the rejection is handled and users get feedback; keep the early return behavior
after a successful await. Reference: onEnqueue, params, dagRunId, immediate.

In `@ui/src/menu.tsx`:
- Around line 574-581: The Remote Nodes NavItem is rendered unconditionally;
restrict it to admins by rendering it only when isAdmin is true (use the
existing isAdmin value from useIsAdmin). Locate the NavItem instance for "Remote
Nodes" and wrap it in a conditional check (e.g., if (isAdmin) ... ) so it
matches other permission-guarded items like canAccessSystemStatus/canWrite;
ensure props (to, text, icon, isOpen, onClick, customColor) remain unchanged.

---

Minor comments:
In `@api/v1/api.yaml`:
- Around line 9675-9691: Add non-empty validation to the workspace "name" field
by updating both CreateWorkspaceRequest and UpdateWorkspaceRequest: add a
constraint like minLength: 1 (or an equivalent pattern) to the name property so
empty strings are rejected; keep name required on CreateWorkspaceRequest and
ensure UpdateWorkspaceRequest's name, when present, also enforces minLength: 1.

In `@ui/src/App.tsx`:
- Line 237: The /cockpit route is exposed even when the agent feature is
disabled; update the routing so access is gated by the same feature-guard
pattern used elsewhere: wrap the Route element for path="/cockpit" (currently
rendering CockpitPage) with a feature guard that checks config.agentEnabled
(e.g., create/ reuse a guard similar to DeveloperElement or LicensedRoute that
returns <Navigate/> or a “feature disabled” fallback when false), or
alternatively update CockpitPage to render a graceful disabled-state UI when
config.agentEnabled is false; ensure you reference the Route path="/cockpit",
CockpitPage, and config.agentEnabled when implementing the guard or the
in-component handling.

In `@ui/src/features/cockpit/components/EmbeddedAgentChat.tsx`:
- Around line 82-86: Replace the silent promise swallow in handleSend by making
the callback async and wrapping the sendMessage call in try/catch: in the
handleSend function (which calls setInitialInputValue and sendMessage), await
sendMessage(message, model, dagContexts, soulId) inside a try block, and in the
catch block log the error and surface user feedback (e.g., call the app's
toast/error handler or set an error state) so failures are visible; ensure you
still clear or restore input via setInitialInputValue as appropriate and do not
leave the promise unhandled.

In `@ui/src/features/cockpit/components/KanbanCard.tsx`:
- Around line 47-50: In the KanbanCard component update the JSX button element
to include an explicit type attribute (type="button") so clicks don't act as
form submissions; locate the <button ... onClick={onClick} className="w-full
text-left p-2 rounded-md border border-border bg-card hover:bg-accent/50
transition-colors cursor-pointer"> and add type="button" to it to prevent
accidental submits when rendered inside a form.

In `@ui/src/features/cockpit/components/TemplateSelector.tsx`:
- Around line 76-85: The effect in useEffect that syncs selectedTemplate with
selectedDag can leave selectedDag stale when filteredDags no longer contains the
selectedTemplate; update the effect so that after searching
filteredDags.find((d) => d.fileName === selectedTemplate) you explicitly clear
the selection when not found (call setSelectedDag(null)) in addition to setting
it when found, keeping the same dependency array [selectedTemplate,
filteredDags] and referencing the existing symbols selectedTemplate,
filteredDags, setSelectedDag, and selectedDag.
- Around line 318-339: The list currently uses "truncate" on the DAG name and
description in TemplateSelector (the span with className built via cn for
dag.dag.name and the description div), which clips long text; replace those
"truncate" uses with wrapping classes "whitespace-normal break-words" (and
remove/avoid "truncate") so long DAG names and descriptions wrap instead of
being clipped, and ensure the parent container (the surrounding div rendering
the name/description) allows wrapping by not forcing no-wrap behavior.

In `@ui/src/features/cockpit/components/WorkspaceSelector.tsx`:
- Around line 58-61: The Input control (ref inputRef) and the corresponding
Select trigger should use explicit compact sizing and padding rather than rely
on defaults; update the Input's className to include a fixed height (h-7),
compact vertical padding (py-0.5), small text (text-xs), tight line-height
(leading-none) and existing horizontal padding/width (px-2 w-40) and apply the
same class set to the Select trigger element so both controls match in height,
padding and background color (use the same bg-... utility or remove bg to
inherit) for consistent, information-dense toolbar styling.

---

Nitpick comments:
In `@api/v1/api.yaml`:
- Around line 5962-5967: The inline workspaceId path parameter is duplicated;
extract it into a reusable components/parameters entry named e.g. WorkspaceId
(define schema type: string and required:true, in:path) and replace each inline
definition (the blocks where name: workspaceId in:path required:true
schema:type:string — occurrences near the existing RemoteNode ref) with a $ref:
"#/components/parameters/WorkspaceId"; update all three occurrences so they
reference the new component to prevent drift.

In `@ui/src/api/v1/schema.ts`:
- Around line 11660-11840: The workspace endpoints (listWorkspaces,
createWorkspace, getWorkspace, deleteWorkspace, updateWorkspace) lack a default
error response; add a default response entry to each operation's responses using
the existing Error schema (i.e. default: { headers: { [name: string]: unknown },
content: { "application/json": components["schemas"]["Error"] } }) so
client-side error handling is uniform, then regenerate the OpenAPI clients/spec
artifacts.

In `@ui/src/features/cockpit/components/DAGPreviewModal.tsx`:
- Around line 210-213: Replace the loose modal spacing in DAGPreviewModal by
changing the wrapper div's className from "p-6 w-full flex flex-col h-full
dag-modal-content" to a denser padding (e.g., "p-3 w-full flex flex-col h-full
dag-modal-content") and reduce the toolbar bottom margin from "mb-4" to "mb-2"
so the header is visually compact and matches cockpit patterns; after updating
classes, verify the toolbar and modal header elements in DAGPreviewModal remain
keyboard-focusable (tab/escape handling) to preserve keyboard navigation.

In `@ui/src/menu.tsx`:
- Around line 266-270: Add an inline comment above the React.useEffect that
references isChildActive, isExpanded, and setIsExpanded explaining that omitting
isExpanded from the dependency array is intentional: the effect should only
expand the item when a child becomes active (isChildActive changes) but must not
collapse when isExpanded is toggled manually, so only isChildActive is listed as
a dependency. Keep the comment concise and mention the behavioral intent to
prevent automatic collapsing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 845843fc-679e-4fb7-b986-55bbd8c0cd81

📥 Commits

Reviewing files that changed from the base of the PR and between c9b6a1c and ca93ccb.

⛔ Files ignored due to path filters (1)
  • ui/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (41)
  • api/v1/api.gen.go
  • api/v1/api.yaml
  • internal/agent/dag_codegen.go
  • internal/agent/dag_codegen_test.go
  • internal/cmn/config/config.go
  • internal/cmn/config/definition.go
  • internal/cmn/config/loader.go
  • internal/persis/fileworkspace/store.go
  • internal/persis/fileworkspace/store_test.go
  • internal/service/audit/entry.go
  • internal/service/frontend/api/v1/api.go
  • internal/service/frontend/api/v1/workspaces.go
  • internal/service/frontend/server.go
  • internal/workspace/store.go
  • internal/workspace/workspace.go
  • ui/package.json
  • ui/src/App.tsx
  • ui/src/api/v1/schema.ts
  • ui/src/components/ui/date-range-picker.tsx
  • ui/src/components/ui/select.tsx
  • ui/src/components/ui/tag-combobox.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/EmbeddedAgentChat.tsx
  • ui/src/features/cockpit/components/KanbanBoard.tsx
  • ui/src/features/cockpit/components/KanbanCard.tsx
  • ui/src/features/cockpit/components/KanbanColumn.tsx
  • ui/src/features/cockpit/components/TemplateSelector.tsx
  • ui/src/features/cockpit/components/WorkspaceSelector.tsx
  • ui/src/features/cockpit/hooks/useCockpitState.ts
  • ui/src/features/cockpit/hooks/useDateKanbanData.ts
  • ui/src/features/cockpit/hooks/useInfiniteKanban.ts
  • ui/src/features/dags/components/common/DAGActions.tsx
  • ui/src/features/dags/components/dag-details/DAGDetailsContent.tsx
  • ui/src/features/dags/components/dag-details/DAGHeader.tsx
  • ui/src/menu.tsx
  • ui/src/pages/cockpit/index.tsx
  • ui/src/pages/dag-runs/index.tsx
  • ui/src/pages/queues/index.tsx

Comment thread internal/agent/dag_codegen_test.go Outdated
Comment thread internal/cmn/config/loader.go
Comment thread internal/service/frontend/api/v1/workspaces.go
Comment thread internal/workspace/workspace.go
Comment thread ui/src/features/cockpit/components/DAGPreviewModal.tsx
Comment thread ui/src/features/cockpit/hooks/useCockpitState.ts Outdated
Comment thread ui/src/features/cockpit/hooks/useCockpitState.ts
Comment thread ui/src/features/cockpit/hooks/useDateKanbanData.ts
Comment thread ui/src/features/dags/components/common/DAGActions.tsx
Comment thread ui/src/menu.tsx Outdated
@yottahmd yottahmd changed the title feat: add cockpit workspace kanban view with DAG codegen feat: add cockpit workspace kanban view Mar 6, 2026
@yottahmd yottahmd merged commit 032407a into main Mar 6, 2026
5 checks passed
@yottahmd yottahmd deleted the cockpit branch March 6, 2026 00:58
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 6, 2026

Codecov Report

❌ Patch coverage is 55.80110% with 80 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.52%. Comparing base (017880d) to head (5480bd5).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
internal/persis/fileworkspace/store.go 49.04% 52 Missing and 28 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1728      +/-   ##
==========================================
- Coverage   69.61%   69.52%   -0.09%     
==========================================
  Files         398      400       +2     
  Lines       43873    44058     +185     
==========================================
+ Hits        30540    30632      +92     
- Misses      10879    10941      +62     
- Partials     2454     2485      +31     
Files with missing lines Coverage Δ
internal/cmn/config/config.go 73.10% <ø> (ø)
internal/cmn/config/loader.go 82.61% <100.00%> (+0.11%) ⬆️
internal/service/audit/entry.go 100.00% <ø> (ø)
internal/workspace/workspace.go 100.00% <100.00%> (ø)
internal/persis/fileworkspace/store.go 49.04% <49.04%> (ø)

... and 9 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 017880d...5480bd5. 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