Skip to content

🤖 feat: smart sections — rule-based automatic workspace classification#2775

Open
ThomasK33 wants to merge 39 commits intomainfrom
sections-mprd
Open

🤖 feat: smart sections — rule-based automatic workspace classification#2775
ThomasK33 wants to merge 39 commits intomainfrom
sections-mprd

Conversation

@ThomasK33
Copy link
Member

Summary

Adds rule-based automatic section assignment so sections become live dashboards. Users define rules on a section (e.g., "all workspaces in plan mode", "all workspaces with an open PR"), and workspaces are re-evaluated and moved automatically when their state changes.

Background

Sections today are static containers — workspaces must be manually dragged between them. This PR introduces smart sections: users can configure declarative rules on any section, and workspaces auto-sort into the correct section when state changes (stream end, activity change, PR status change).

Key design decisions:

  • Rules first, agent later — deterministic rules cover mode, PR state, streaming, task status. LLM classifier is future scope.
  • Manual override via pinning — dragging a workspace into a section "pins" it; rules won't move it. Dragging to "Unsectioned" clears the pin.
  • First-match wins — sections evaluated in display order; first match claims the workspace.
  • No polling — evaluation triggers on stream-end, activity events, and PR status changes, with 300ms debounce.

Implementation

Phase 1 — Rule Evaluation Engine (common/)

  • SectionRuleConditionSchema + SectionRuleSchema Zod schemas
  • pinnedToSection field on WorkspaceConfigSchema and WorkspaceMetadataSchema
  • Pure evaluation engine: evaluateCondition (eq/neq/in operators), evaluateSectionRules (first-match, OR rules, AND conditions)
  • 18 unit tests covering all operators, semantics, pinning, and edge cases

Phase 2 — Backend Service & Pinning

  • SectionAssignmentService with debounced per-workspace evaluation
  • Listens to stream-end and activity events from AIService/WorkspaceService
  • Extended assignWorkspaceToSection with pinned parameter
  • Extended updateSection to accept rules
  • New IPC routes: evaluateWorkspace (accepts frontend PR/git context), evaluateProject
  • Existing assignWorkspace route now sets pinned: true for user-initiated drags
  • Section update route triggers project-wide re-evaluation when rules change

Phase 3 — Frontend PR Trigger

  • PRStatusStore detects PR field changes (state, mergeStatus, isDraft, checks) and fires evaluateWorkspace IPC with PR context

Phase 4 — Rule Editor UI

  • SectionRuleEditor component with condition builder (field/operator/value selects)
  • 5 preset templates: Planning workspaces, Open PRs, Merge-ready, Actively streaming, Failed CI
  • ListFilter icon in section header action bar + persistent indicator for smart sections
  • Shared RULE_FIELD_META constant ensures editor and engine stay in sync
  • 5 UI tests

Validation

  • make static-check ✅ (typecheck, lint, fmt, docs links all pass)
  • 29 new tests across 3 files, all passing
  • Existing sections without rules behave exactly as before (no regression)

New files

File Purpose
src/common/utils/sectionRules.ts Pure rule evaluation engine
src/common/utils/sectionRules.test.ts 18 engine tests
src/common/constants/sectionRuleFields.ts Shared field metadata for editor + engine
src/node/services/sectionAssignmentService.ts Backend auto-assignment service
src/node/services/sectionAssignmentService.test.ts 6 backend tests
src/browser/components/SectionHeader/SectionRuleEditor.tsx Rule editor UI
src/browser/components/SectionHeader/SectionRuleEditor.test.tsx 5 UI tests

📋 Implementation Plan

Smart Sections — Automatic Workspace Classification

Context & Goals

Sections today are static containers — workspaces must be manually dragged between them.
This plan introduces rule-based automatic section assignment: users define rules on a
section (e.g., "all workspaces in plan mode", "all workspaces with an open PR"), and
workspaces are re-evaluated and moved automatically when their state changes.

User impact: Sections become a live dashboard — "Plans", "PR Ready", "Active", "Needs
Attention" sections that stay current without manual intervention.

Design Principles

  1. Rules first, agent later. Deterministic rules cover the stated use cases (mode, PR
    state, streaming, task status). An LLM classifier is future scope for fuzzy rules.
  2. Manual override. Dragging a workspace into a section "pins" it there — rules won't
    move it. Dragging to "Unsectioned" clears the pin and returns rule ownership.
  3. Evaluate on state change. No polling — evaluate rules on stream-end, activity change,
    and PR status change.
  4. First-match wins. Sections evaluated in display order; first match claims the workspace.
  5. Rules own non-pinned assignments. If a workspace was auto-assigned to section A and
    later matches no rules, it is moved to "Unsectioned." Rules are the source of truth for
    all non-pinned workspaces.

Assumptions

  • Agent mode on backend: The agentId field on workspace metadata stores the agent
    definition (e.g., "plan", "exec", "auto"). For the agentMode rule field, we read
    workspace.agentId from persisted metadata. This reflects the last mode the user selected.
  • PR/git status lives on frontend only. The backend has no PR or git status store. For
    v1, the frontend triggers backend re-evaluation via an IPC call when PR status changes.
  • Child workspaces inherit parent section via existing partitionWorkspacesBySection
    resolution. Rules only need to assign the parent; children follow automatically.
  • Activity events fire frequently (every message, every status_set). The backend service
    must debounce evaluation per-workspace to avoid redundant config reads/writes.
Evidence / Sources
What Where
SectionConfig schema src/common/schemas/project.tsSectionConfigSchema
WorkspaceConfig schema (persists sectionId) src/common/schemas/project.tsWorkspaceConfigSchema
Workspace metadata src/common/orpc/schemas/workspace.tsWorkspaceMetadataSchema, WorkspaceActivitySnapshotSchema
PR status types src/common/types/links.tsGitHubPRStatus
Agent mode src/common/types/mode.tsAgentMode (plan, exec, compact)
Section assignment (backend) src/node/services/projectService.tsassignWorkspaceToSection()
Section assignment (IPC) src/node/orpc/router.tsprojects.sections.assignWorkspace route, calls refreshAndEmitMetadata
Section update (IPC) src/node/orpc/router.tsprojects.sections.update route → projectService.updateSection() (accepts name, color)
stream-end lifecycle src/node/services/aiService.tsworkspaceService.handleStreamCompletion
Activity events workspaceService.emit("activity", ...) on recency/streaming/status changes
Metadata propagation workspaceService.refreshAndEmitMetadata → oRPC onMetadata stream → WorkspaceContext.tsx
Sidebar rendering src/browser/components/ProjectSidebar/ProjectSidebar.tsxrenderSection, uses partitionWorkspacesBySection
Workspace filtering src/browser/utils/ui/workspaceFiltering.tspartitionWorkspacesBySection
Section sorting src/common/utils/sections.tssortSectionsByLinkedList
Section header UI src/browser/components/SectionHeader/SectionHeader.tsx — action buttons at line 127
Service composition src/node/services/coreServices.tscreateCoreServices() factory
Service container src/node/services/serviceContainer.ts — constructor wires services
PRStatusStore src/browser/stores/PRStatusStore.ts — overwrites cache unconditionally (no diff)
Frontend stores PRStatusStore.ts, GitStatusStore.ts, WorkspaceStore.ts in src/browser/stores/

Phase 1 — Rule Evaluation Engine (Red → Green → Refactor)

The pure logic layer. No backend wiring, no UI. Shared in common/ for both backend and
frontend use.

1a. RED: Write failing tests first

New file: src/common/utils/sectionRules.test.ts (~120 LoC)

Test helper — factory for WorkspaceRuleContext with sensible defaults:

function makeCtx(overrides?: Partial<WorkspaceRuleContext>): WorkspaceRuleContext {
  return {
    workspaceId: "ws-1",
    agentMode: undefined,
    streaming: false,
    prState: "none",
    prMergeStatus: undefined,
    prIsDraft: undefined,
    prHasFailedChecks: undefined,
    prHasPendingChecks: undefined,
    taskStatus: undefined,
    hasAgentStatus: false,
    gitDirty: undefined,
    currentSectionId: undefined,
    pinnedToSection: false,
    ...overrides,
  };
}

function makeSection(id: string, rules?: SectionRule[]): SectionConfig {
  return { id, name: `Section ${id}`, rules };
}

Test cases for evaluateCondition:

# Test name Input Expected
1 eq matches string field { field: "agentMode", op: "eq", value: "plan" }, ctx agentMode: "plan" true
2 eq rejects mismatch same condition, ctx agentMode: "exec" false
3 neq matches non-equal { field: "prState", op: "neq", value: "OPEN" }, ctx prState: "none" true
4 eq matches boolean field { field: "streaming", op: "eq", value: true }, ctx streaming: true true
5 eq handles undefined field gracefully` { field: "prMergeStatus", op: "eq", value: "CLEAN" }, ctx prMergeStatus: undefined false
6 neq with undefined field` { field: "prMergeStatus", op: "neq", value: "CLEAN" }, ctx prMergeStatus: undefined true
7 in matches value in set { field: "taskStatus", op: "in", value: '["queued","running"]' }, ctx taskStatus: "running" true
8 in rejects value not in set same condition, ctx taskStatus: "reported" false
9 in with undefined field same condition, ctx taskStatus: undefined false

Test cases for evaluateSectionRules:

# Test name Setup Expected
10 no sections have rules → undefined 2 sections, no rules undefined
11 single section, single rule, matches section A has agentMode eq plan, ctx agentMode: "plan" "A"
12 single section, single rule, no match section A has agentMode eq plan, ctx agentMode: "exec" undefined
13 AND: multi-condition rule, all match rule: prState eq OPEN AND prMergeStatus eq CLEAN section id
14 AND: multi-condition rule, one fails rule: prState eq OPEN AND prMergeStatus eq CLEAN, ctx prMergeStatus: "BLOCKED" undefined
15 OR: multi-rule section, second matches rule1: agentMode eq plan (no match), rule2: prState eq OPEN (match) section id
16 first-match wins across sections section A: streaming eq true, section B: streaming eq true; ctx streaming "A"
17 pinned workspace is skipped section A matches, ctx pinnedToSection: true undefined
18 sections with empty rules array → never match section A has rules: [] undefined
19 previously auto-assigned, no longer matches → undefined ctx currentSectionId: "A", no rules match undefined

1b. GREEN: Implement the engine

File: src/common/schemas/project.ts — Add schemas (~30 LoC)

export const SectionRuleConditionSchema = z.object({
  field: z.enum([
    "agentMode",        // "plan" | "exec" | "compact" | "auto"
    "streaming",        // boolean
    "prState",          // "OPEN" | "CLOSED" | "MERGED" | "none"
    "prMergeStatus",    // "CLEAN" | "BLOCKED" | "BEHIND" | "DIRTY" | "DRAFT" | ...
    "prIsDraft",        // boolean
    "prHasFailedChecks",// boolean
    "prHasPendingChecks",// boolean
    "taskStatus",       // "queued" | "running" | "awaiting_report" | "reported" | ...
    "hasAgentStatus",   // boolean
    "gitDirty",         // boolean
  ]),
  op: z.enum(["eq", "neq", "in"]),
  value: z.union([z.string(), z.boolean()]),
});
export type SectionRuleCondition = z.infer<typeof SectionRuleConditionSchema>;

export const SectionRuleSchema = z.object({
  conditions: z.array(SectionRuleConditionSchema).min(1),
});
export type SectionRule = z.infer<typeof SectionRuleSchema>;

Extend SectionConfigSchema — add rules: z.array(SectionRuleSchema).optional().

Add pinnedToSection: z.boolean().optional() to both WorkspaceConfigSchema (persistence)
and WorkspaceMetadataSchema (runtime/IPC).

New file: src/common/utils/sectionRules.ts (~80 LoC)

export interface WorkspaceRuleContext {
  workspaceId: string;
  agentMode: string | undefined;  // from workspace.agentId
  streaming: boolean;
  prState: "OPEN" | "CLOSED" | "MERGED" | "none";
  prMergeStatus: string | undefined;
  prIsDraft: boolean | undefined;
  prHasFailedChecks: boolean | undefined;
  prHasPendingChecks: boolean | undefined;
  taskStatus: string | undefined;
  hasAgentStatus: boolean;
  gitDirty: boolean | undefined;
  currentSectionId: string | undefined;
  pinnedToSection: boolean;
}

/** Map condition field names to their getter from context. */
function getFieldValue(field: SectionRuleCondition["field"], ctx: WorkspaceRuleContext):
  string | boolean | undefined {
  const fieldMap: Record<SectionRuleCondition["field"], string | boolean | undefined> = {
    agentMode: ctx.agentMode,
    streaming: ctx.streaming,
    prState: ctx.prState,
    prMergeStatus: ctx.prMergeStatus,
    prIsDraft: ctx.prIsDraft,
    prHasFailedChecks: ctx.prHasFailedChecks,
    prHasPendingChecks: ctx.prHasPendingChecks,
    taskStatus: ctx.taskStatus,
    hasAgentStatus: ctx.hasAgentStatus,
    gitDirty: ctx.gitDirty,
  };
  return fieldMap[field];
}

export function evaluateCondition(
  condition: SectionRuleCondition,
  ctx: WorkspaceRuleContext,
): boolean {
  const actual = getFieldValue(condition.field, ctx);
  switch (condition.op) {
    case "eq":
      return actual === condition.value;
    case "neq":
      return actual !== condition.value;
    case "in": {
      if (actual == null) return false;
      // value is a JSON array string, e.g. '["queued","running"]'
      const allowed = JSON.parse(condition.value as string) as (string | boolean)[];
      return allowed.includes(actual);
    }
  }
}

export function evaluateSectionRules(
  sections: SectionConfig[],  // pre-sorted by display order
  ctx: WorkspaceRuleContext,
): string | undefined {
  if (ctx.pinnedToSection) return undefined;
  for (const section of sections) {
    const rules = section.rules;
    if (!rules || rules.length === 0) continue;
    // Section matches if ANY rule matches (OR)
    const sectionMatches = rules.some((rule) =>
      // Rule matches if ALL conditions match (AND)
      rule.conditions.every((cond) => evaluateCondition(cond, ctx)),
    );
    if (sectionMatches) return section.id;
  }
  return undefined;
}

1c. REFACTOR

After tests pass:

  • Verify getFieldValue uses Record<Enum, Value> for exhaustiveness (compiler catches missing fields if enum grows).
  • Confirm evaluateCondition doesn't use as any.
  • Check if JSON.parse in the "in" operator needs a try/catch for malformed values
    (defensive programming — add assertion or fallback).

Phase 2 — Backend Service & Pinning (Red → Green → Refactor)

Wire the engine into the stream-end lifecycle.

2a. RED: Write failing tests first

New file: src/node/services/sectionAssignmentService.test.ts (~100 LoC)

Uses real ProjectService (backed by temp config dir) and spied WorkspaceService.

# Test name Setup Expected
1 stream-end triggers evaluation Section with streaming eq false rule. Emit stream-end. Workspace assigned to section.
2 pinned workspace not moved Workspace pinned to section A. Rules match section B. Workspace stays in section A.
3 no sections have rules → no-op Emit stream-end. assignWorkspaceToSection never called.
4 already in correct section → no-op Workspace in section A, rules still match A. No redundant assignWorkspaceToSection call.
5 no match → unassign auto-assigned Workspace auto-assigned to A, rules changed, no match. assignWorkspaceToSection(null, false) called.
6 evaluateProject re-evaluates all 3 workspaces, 2 match section A. 2 assigned, 1 untouched.
7 debounce: rapid events coalesced Emit 5 activity events in 10ms. evaluateWorkspace body executes once.
8 drag-to-section sets pinnedToSection Call assignWorkspaceToSection with pinned: true. Config has pinnedToSection: true.
9 drag-to-unsectioned clears pin Call assignWorkspaceToSection with sectionId: null. Config has pinnedToSection: undefined, sectionId: undefined.

2b. GREEN: Implement

File: src/common/schemas/project.ts — already done in Phase 1 (schemas).

File: src/node/services/projectService.ts — extend assignWorkspaceToSection (~10 LoC)

async assignWorkspaceToSection(
  projectPath: string,
  workspaceId: string,
  sectionId: string | null,
  pinned?: boolean,  // NEW
): Promise<Result<void>> {
  ...
  workspace.sectionId = sectionId ?? undefined;
  if (pinned != null) {
    workspace.pinnedToSection = pinned || undefined;
  }
  // When dragging to unsectioned, always clear pin
  if (sectionId == null) {
    workspace.pinnedToSection = undefined;
  }
  ...
}

File: src/node/services/projectService.ts — extend updateSection to accept rules (~5 LoC)

Add rules?: SectionRule[] to the updates parameter of updateSection().

New file: src/node/services/sectionAssignmentService.ts (~100 LoC)

import { evaluateSectionRules, WorkspaceRuleContext } from "@/common/utils/sectionRules";
import { sortSectionsByLinkedList } from "@/common/utils/sections";

const DEBOUNCE_MS = 300;

export class SectionAssignmentService {
  private pendingEvaluations = new Map<string, ReturnType<typeof setTimeout>>();

  constructor(
    private projectService: ProjectService,
    private workspaceService: WorkspaceService,
    private aiService: AIService,
  ) {
    this.setupListeners();
  }

  private setupListeners(): void {
    this.aiService.on("stream-end", (data: StreamEndEvent) => {
      this.scheduleEvaluation(data.workspaceId);
    });
    this.workspaceService.on("activity", (data: { workspaceId: string }) => {
      this.scheduleEvaluation(data.workspaceId);
    });
  }

  /** Debounce per workspace to coalesce rapid activity bursts. */
  private scheduleEvaluation(workspaceId: string): void {
    const existing = this.pendingEvaluations.get(workspaceId);
    if (existing) clearTimeout(existing);
    this.pendingEvaluations.set(
      workspaceId,
      setTimeout(() => {
        this.pendingEvaluations.delete(workspaceId);
        void this.evaluateWorkspace(workspaceId);
      }, DEBOUNCE_MS),
    );
  }

  async evaluateWorkspace(workspaceId: string): Promise<void> {
    // 1. Get workspace metadata
    const metadata = this.workspaceService.getWorkspaceMetadata(workspaceId);
    if (!metadata) return;
    if (metadata.pinnedToSection) return;

    // 2. Get project sections, bail if none have rules
    const project = this.projectService.getProject(metadata.projectPath);
    if (!project) return;
    const sections = sortSectionsByLinkedList(project.sections ?? []);
    const anyRules = sections.some((s) => s.rules && s.rules.length > 0);
    if (!anyRules) return;

    // 3. Build context
    const activity = this.workspaceService.getActivitySnapshot(workspaceId);
    const ctx: WorkspaceRuleContext = {
      workspaceId,
      agentMode: metadata.agentId,
      streaming: activity?.streaming ?? false,
      prState: "none",          // Backend has no PR data; frontend triggers separately
      prMergeStatus: undefined,
      prIsDraft: undefined,
      prHasFailedChecks: undefined,
      prHasPendingChecks: undefined,
      taskStatus: metadata.taskStatus,
      hasAgentStatus: activity?.agentStatus != null,
      gitDirty: undefined,      // Backend has no git status; frontend triggers separately
      currentSectionId: metadata.sectionId,
      pinnedToSection: false,
    };

    // 4. Evaluate
    const targetSectionId = evaluateSectionRules(sections, ctx);

    // 5. Apply if changed
    if (targetSectionId !== metadata.sectionId) {
      await this.projectService.assignWorkspaceToSection(
        metadata.projectPath,
        workspaceId,
        targetSectionId ?? null,
        false, // auto-assigned, never pinned
      );
      this.workspaceService.refreshAndEmitMetadata(workspaceId);
    }
  }

  async evaluateProject(projectPath: string): Promise<void> {
    const workspaces = this.workspaceService.listWorkspaces(projectPath);
    for (const ws of workspaces) {
      await this.evaluateWorkspace(ws.id);
    }
  }
}

File: src/node/services/coreServices.ts — instantiate (~5 LoC)

// Inside createCoreServices(), after WorkspaceService and AIService:
const sectionAssignmentService = new SectionAssignmentService(
  projectService, workspaceService, aiService,
);
// Expose on the returned CoreServices object

File: src/node/orpc/router.ts — add IPC routes (~20 LoC)

// Under projects.sections:
evaluateWorkspace: t
  .input(z.object({ workspaceId: z.string() }))
  .handler(async ({ input, context }) => {
    await context.sectionAssignmentService.evaluateWorkspace(input.workspaceId);
  }),
evaluateProject: t
  .input(z.object({ projectPath: z.string() }))
  .handler(async ({ input, context }) => {
    await context.sectionAssignmentService.evaluateProject(input.projectPath);
  }),

Extend the existing projects.sections.update route input to include rules:

update: t
  .input(schemas.projects.sections.update.input.extend({
    rules: z.array(SectionRuleSchema).optional(),
  }))
  .handler(({ context, input }) =>
    context.projectService.updateSection(input.projectPath, input.sectionId, {
      name: input.name,
      color: input.color,
      rules: input.rules,
    })
  ),

File: src/node/orpc/router.ts — update assignWorkspace to pass pinned: true (~3 LoC)

The existing projects.sections.assignWorkspace handler must pass pinned: true since
this route is only called from user-initiated drag-and-drop:

assignWorkspace: t
  .input(...)
  .handler(async ({ input, context }) => {
    const result = await context.projectService.assignWorkspaceToSection(
      input.projectPath, input.workspaceId, input.sectionId,
      true,  // user-initiated = pinned
    );
    ...
  }),

2c. REFACTOR

  • Verify evaluateWorkspace never throws on missing data (defensive — metadata/activity can be null).
  • Check debounce cleanup: ensure pendingEvaluations timers are cleared if the service is disposed.
  • Confirm refreshAndEmitMetadata propagates the new sectionId to the frontend
    (it already does — it re-reads config and emits metadata event).
  • Look for potential race: stream-end fires → handleStreamCompletion sets streaming: false
    → activity event fires → both trigger scheduleEvaluation. Debounce handles this correctly
    (second call replaces the first timer). Verify in tests.

Phase 3 — Frontend: PR Trigger + Evaluation (~20 LoC, no new tests)

3a. GREEN: Implement

File: src/browser/stores/PRStatusStore.ts — add change detection + trigger (~15 LoC)

In the fetch callback where PR status is updated, compare old vs. new before overwriting:

// Before overwriting cache:
const oldEntry = this.workspacePRCache.get(workspaceId);
const oldState = oldEntry?.status?.state;
// ... overwrite cache ...
const newState = newEntry?.status?.state;
if (oldState !== newState) {
  void api.projects.sections.evaluateWorkspace({ workspaceId });
}

Also trigger on mergeable, isDraft, hasFailedChecks, hasPendingChecks changes.

File: src/node/orpc/router.ts — The evaluateWorkspace IPC route needs to accept
frontend-provided PR/git context so the backend can use it for evaluation. Extend the input:

evaluateWorkspace: t
  .input(z.object({
    workspaceId: z.string(),
    // Optional frontend-provided state (backend has no PR/git stores)
    prState: z.enum(["OPEN", "CLOSED", "MERGED", "none"]).optional(),
    prMergeStatus: z.string().optional(),
    prIsDraft: z.boolean().optional(),
    prHasFailedChecks: z.boolean().optional(),
    prHasPendingChecks: z.boolean().optional(),
    gitDirty: z.boolean().optional(),
  }))
  .handler(async ({ input, context }) => {
    await context.sectionAssignmentService.evaluateWorkspace(
      input.workspaceId,
      { // frontend-provided context overrides
        prState: input.prState,
        prMergeStatus: input.prMergeStatus,
        prIsDraft: input.prIsDraft,
        prHasFailedChecks: input.prHasFailedChecks,
        prHasPendingChecks: input.prHasPendingChecks,
        gitDirty: input.gitDirty,
      },
    );
  }),

Update SectionAssignmentService.evaluateWorkspace to merge optional frontend-provided
context into the WorkspaceRuleContext (overriding the backend defaults of "none" / undefined).

3b. REFACTOR

  • Ensure PRStatusStore comparison is cheap (shallow field comparison, not deep equals).
  • Confirm frontend doesn't fire evaluation for workspaces that have no project with smart sections.

Phase 4 — Rule Editor UI (Red → Green → Refactor)

4a. RED: Write failing tests first

New file: src/browser/components/SectionHeader/SectionRuleEditor.test.tsx (~60 LoC)

Uses happy-dom + React Testing Library (following existing test patterns).

# Test name Expected
1 renders empty state when no rules Shows "No rules configured" + "Add rule" button.
2 displays existing rules Given 2 rules, renders 2 rule cards with correct fields.
3 add rule from preset Click "Add rule" → select "Planning workspaces" → rule card appears with agentMode eq plan.
4 add condition to existing rule Click "+ Add condition" → new condition row appears.
5 remove rule Click remove on rule 1 → only rule 2 remains.
6 save calls update IPC with rules Click "Save" → api.projects.sections.update called with correct rules payload.
7 save triggers project re-evaluation After save, api.projects.sections.evaluateProject called.

4b. GREEN: Implement

File: src/browser/components/SectionHeader/SectionHeader.tsx — add Rules button (~15 LoC)

Insert a new action button (with ListFilter icon from lucide-react) in the hover action
bar at line ~127, between the Color picker and Rename buttons:

<Tooltip content="Rules">
  <button
    onClick={() => setShowRuleEditor(true)}
    className="..."
  >
    <ListFilter className="size-3.5" />
  </button>
</Tooltip>

Add a smart-section indicator: when section.rules?.length > 0, show a small dot or the
ListFilter icon persistently (not just on hover) next to the section name.

New component: src/browser/components/SectionHeader/SectionRuleEditor.tsx (~150 LoC)

A popover/dialog containing the rule builder form:

interface SectionRuleEditorProps {
  section: SectionConfig;
  projectPath: string;
  onClose: () => void;
}

Structure:

  • Header: "Auto-assign workspaces when:"
  • Rule cards (array of SectionRule): each renders its conditions as rows
  • Each condition row: three <select> elements — Field, Operator, Value
    • Field options: human-readable labels → enum values
      ("Agent Mode"agentMode, "PR State"prState, etc.)
    • Operator options: depend on field type (string fields: eq/neq/in;
      boolean fields: eq/neq)
    • Value options: depend on field
      (agentModeplan/exec; prStateOPEN/CLOSED/MERGED/none; etc.)
  • "+ Add condition" button per rule
  • "OR" separator between rules
  • "+ Add rule" button with preset dropdown
  • "Save" button → calls api.projects.sections.update({ projectPath, sectionId, rules }),
    then api.projects.sections.evaluateProject({ projectPath })

Preset templates (populate a new rule with pre-filled conditions):

const RULE_PRESETS: Record<string, { label: string; rule: SectionRule }> = {
  planning: {
    label: "Planning workspaces",
    rule: { conditions: [{ field: "agentMode", op: "eq", value: "plan" }] },
  },
  openPR: {
    label: "Open PRs",
    rule: { conditions: [{ field: "prState", op: "eq", value: "OPEN" }] },
  },
  mergeReady: {
    label: "Merge-ready",
    rule: { conditions: [
      { field: "prState", op: "eq", value: "OPEN" },
      { field: "prHasFailedChecks", op: "eq", value: false },
      { field: "prHasPendingChecks", op: "eq", value: false },
    ]},
  },
  streaming: {
    label: "Actively streaming",
    rule: { conditions: [{ field: "streaming", op: "eq", value: true }] },
  },
  failedCI: {
    label: "Failed CI",
    rule: { conditions: [{ field: "prHasFailedChecks", op: "eq", value: true }] },
  },
};

4c. REFACTOR

  • Extract field metadata (label, type, allowed values) into a shared constant so the rule
    editor and the evaluation engine stay in sync. E.g.:

    // src/common/constants/sectionRules.ts
    export const RULE_FIELD_META: Record<SectionRuleCondition["field"], {
      label: string;
      type: "string" | "boolean";
      options?: { label: string; value: string | boolean }[];
    }> = { ... };
  • Ensure the <select> value options for each field are derived from this shared constant.

  • Look for duplicated condition rendering logic and extract a <ConditionRow> sub-component.

  • Verify all strings are not hardcoded — use the field metadata constants.


Verification Checklist

Check Command / Method
Rule engine unit tests pass bun test src/common/utils/sectionRules.test.ts
Backend service tests pass bun test src/node/services/sectionAssignmentService.test.ts
UI component tests pass bun test src/browser/components/SectionHeader/SectionRuleEditor.test.tsx
Type checks pass make typecheck
Lint passes make lint
Full static check make static-check
Manual smoke test 1. Create a section "Plans" with rule agentMode eq plan. 2. Switch a workspace to plan mode and send a message. 3. After stream ends, workspace auto-moves to "Plans" section. 4. Drag workspace to "Unsectioned" — it gets pinned out. 5. Edit rules on section → workspace re-evaluated immediately.
No regression: sections without rules Existing sections with no rules behave exactly as before (static, manual-only).
No perf regression Activity event debounce prevents excessive config reads. Fast path skips evaluation when no sections have rules.

LoC Estimate

Phase Product LoC Test LoC
1: Schemas + rule engine ~110 ~120
2: Backend service + pinning ~140 ~100
3: Frontend PR trigger ~20
4: Rule editor UI + header ~170 ~60
Total ~440 ~280

Future (Not in Scope)

  • Tier 2: LLM agent classifier — for fuzzy rules like "workspace seems stuck."
    Would follow the workspaceTitleGenerator.ts pattern: internal agent with a
    classify_section tool, fired on stream-end only for sections with type: "agent" rules.
  • Backend PR/git polling — move status detection to backend for fully server-side triggers.
  • Rule templates library — pre-built section configs users can import.
  • "Unpin from section" context menu — explicit unpin without dragging to unsectioned.

Generated with mux • Model: anthropic:claude-opus-4-6 • Thinking: xhigh • Cost: $18.37

@ThomasK33
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f97e493ac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Both review issues have been addressed:

  1. P1 — PR-aware rules with empty context: Backend-triggered re-evaluations now skip sections with frontend-only rules (PR/git fields) when no frontend context is provided. Workspaces already in such sections are preserved rather than being moved to unsectioned.

  2. P2 — Unpin on section removal: removeSection now clears pinnedToSection alongside sectionId so deleted-section pins don't permanently exclude workspaces from auto-assignment.

Both fixes include new regression tests.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6cdf4a42d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Round 2 issues addressed:

  1. P1 — evaluateProject skipping frontend-only sections: Added source: "auto" | "explicit" parameter. Backend event listeners use "auto" (conservative filtering). IPC calls and project-wide re-evaluation from rule edits use "explicit" (evaluates all sections including PR/git ones).

  2. P1 — gitDirty not triggering reevaluation: Added change detection in GitStatusStore that fires evaluateWorkspace with gitDirty context when the dirty state changes, matching the PRStatusStore pattern.

Both fixes include new tests.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2cef0754a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Round 3 issues addressed with a fundamental redesign of how partial context is handled:

  1. P1 — evaluateProject without frontend context & P1 — Partial frontend context forcing evaluation of unrelated rules: Replaced the source: "auto"/"explicit" mechanism with field-level availability tracking. Each evaluation now carries an availableFields set. Conditions on unavailable fields return "inconclusive" — they neither pass nor fail. This means:

    • Backend-triggered re-evaluations (stream-end, activity) only evaluate rules for backend-known fields (agentMode, streaming, taskStatus, hasAgentStatus)
    • PR status changes only evaluate rules referencing PR fields
    • Git status changes only evaluate rules referencing gitDirty
    • evaluateProject from rule edits evaluates backend-known fields; PR/git rules are inconclusive and preserve current assignments
    • No workspace is ever misplaced due to missing context
  2. P2 — Malformed in arrays throwing: Replaced assertion with defensive return false — malformed JSON, non-arrays, or invalid entries all silently fail the condition.

All fixes include comprehensive test coverage.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b4dfaf9e0f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Round 4 issues addressed:

  1. P1 — pinnedToSection not propagated: Updated all WorkspaceConfig → WorkspaceMetadata mapping paths in Config.getAllWorkspaceMetadata() to carry pinnedToSection through. Also updated Config.addWorkspace() to persist the field. Pinned workspaces are now correctly honored by the assignment service.

  2. P2 — Reorder/remove not triggering re-evaluation: Section reorder and remove route handlers now trigger evaluateProject on success, so first-match ordering changes and section removals take effect immediately.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 085bfa705f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Round 5 issues addressed:

  1. P1 — No-PR workspaces never triggering evaluation: Added per-workspace tracking in PRStatusStore so the first completed check always triggers evaluation with prState: "none", even when no PR exists. Subsequent identical states are deduplicated.

  2. P2 — evaluateProject without frontend context (resolved as by-design): The backend fundamentally does not store PR/git state — that data lives only in the frontend stores (PRStatusStore, GitStatusStore). The inconclusive-field approach correctly handles this:

    • Backend-triggered project-wide re-evaluation only conclusively evaluates backend-known rules (agentMode, streaming, taskStatus, hasAgentStatus)
    • PR/git rules are treated as inconclusive → current assignments are preserved (no incorrect moves)
    • The next per-workspace frontend-triggered evaluation (PR status change, git dirty change) will correctly re-evaluate with full context

    This is the intended and safest behavior. Moving PR/git polling to the backend is explicitly listed as future scope in the plan. The alternative (evaluating with wrong defaults) was the original P1 bug that we fixed in round 3.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 10b641bbba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Round 6 issues addressed:

  1. P1 — Subscription-scoped PR evaluation: This is a known architectural limitation documented in the plan's "Future (Not in Scope)" section as "Backend PR/git polling — move status detection to backend for fully server-side triggers." For v1, PR-based rules evaluate when workspaces become visible (landing page cards mount useWorkspacePR) or when PR status changes. Non-viewed workspaces are evaluated when the user navigates to them. This is an acceptable trade-off until backend PR polling is implemented.

  2. P2 — Evaluated flag before IPC success: Fixed. The workspaceSectionEvaluated set is now populated only in the .then() success callback. Failed IPC calls leave the workspace unmarked for retry on next refresh.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f5cdc59b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Round 7 issues addressed:

  1. P1 — Stale sections when all rules removed: Removed the early return when no sections have rules. The evaluation now falls through to the unassignment logic, properly clearing auto-assigned workspaces when rules are deleted.

  2. P2 — Git-dirty failed IPC handling: Added per-workspace tracking (lastEvaluatedDirty, pendingEvaluatedDirty) with success/failure callbacks. Failed IPC calls allow retry on next poll cycle. Includes cleanup on workspace removal and store dispose.

Both fixes include new tests.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e6aa8c8f2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Addressed the PRStatusStore retry tracking:

Applied the same per-workspace state tracking pattern as GitStatusStore. Replaced workspaceSectionEvaluated: Set<string> with lastEvaluatedPRState/pendingPRState maps that track the exact PR state key. Failed IPC calls leave the last-success unchanged so subsequent polls retry. Successful evaluations prevent redundant calls.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c72d3d6fad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Round 8 issues addressed:

  1. P1 — Legacy section assignments: Evaluation now treats workspaces with sectionId set but pinnedToSection === undefined (legacy manual placement) as implicitly pinned. Only explicitly auto-assigned workspaces (pinnedToSection === false) can be re-evaluated or unassigned. Also fixed assignWorkspaceToSection to persist false (not undefined) for auto-assignments.

  2. P1 — PR subscription scope: This is a known v1 architectural limitation. PR status detection is subscription-scoped because PRStatusStore only polls for workspaces that mount useWorkspacePR. This means PR-based rules only apply to workspaces that are currently visible (landing page cards, active workspace). Moving PR/git polling to the backend is documented as future scope. For v1, this is acceptable: backend-only rules (agentMode, streaming, taskStatus) work for all workspaces immediately; PR/git rules apply when workspaces become visible.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b742f24d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Addressed both cache invalidation issues:

Both PRStatusStore and GitStatusStore now expose invalidateSectionEvaluationCache() methods that clear their dedup tracking maps. ProjectContext.updateSection() calls these invalidators when rules are included in the update, ensuring the next poll cycle re-sends current PR/git context for all workspaces.

This completes the evaluation lifecycle: rule changes → server-side evaluateProject (handles backend-known rules) + cache invalidation → next frontend poll re-evaluates PR/git rules with fresh context.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e860d8a851

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Addressed both P2 issues:

  1. Mixed PR+git rules: Documented as a known v1 limitation in sectionRuleFields.ts. PR and git context are sent from separate stores independently, so rules combining both field types may be inconclusive until both stores send context. This is inherent to the frontend-store architecture and will be resolved when backend PR/git polling is implemented.

  2. Reorder/remove cache invalidation: Section reorder and remove operations in ProjectContext now call invalidatePRSectionEvaluationCache() and invalidateGitSectionEvaluationCache() on success, ensuring the next poll cycle re-sends current context.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba8629db6a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

This P2 is the same subscription-scope concern already addressed in rounds 5, 6, and 8. The architecture is intentional:

  • PR status polling is subscription-driven by design (only visible workspaces are polled)
  • Backend has no PR/git state store — moving this to backend is listed as "Future (Not in Scope)" in the plan
  • Backend-only rules (agentMode, streaming, taskStatus) apply to ALL workspaces immediately
  • PR/git rules apply when workspaces become visible (landing page, active workspace)
  • Cache invalidation on rule changes ensures the next poll resends context

This is an acceptable v1 trade-off. No further code change is needed for this known architectural limitation.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba8629db6a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c5c0116b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

@ThomasK33
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f8b96dabee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f1eb307a5f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

1 similar comment
@ThomasK33
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 18cdcd594e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

1 similar comment
@ThomasK33
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4c703b625

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4c703b625

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f2cfd50bfc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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