Skip to content

feat: Priority Matrix + Agent Board pages#45

Open
chitcommit wants to merge 2 commits intomainfrom
feat/eisenhower-priority-matrix
Open

feat: Priority Matrix + Agent Board pages#45
chitcommit wants to merge 2 commits intomainfrom
feat/eisenhower-priority-matrix

Conversation

@chitcommit
Copy link
Contributor

@chitcommit chitcommit commented Mar 17, 2026

Summary

  • Adds /priorities page — Eisenhower 2×2 Priority Matrix that classifies obligations, disputes, tasks, and recommendations into Do First / Schedule / Delegate / Eliminate quadrants based on urgency scoring, due dates, and priority levels
  • Adds /agents page — Kanban board mirroring the Notion Agent Task Board with status columns (Pending → Claimed → Running → Done / Failed / Blocked), agent name badges, task type labels, and due date tracking
  • Both pages wired into sidebar navigation with Crosshair and Bot icons

Details

Priority Matrix (/priorities)

  • Auto-classifies items using urgency scores, due date proximity, amount thresholds, and priority levels
  • Tactical crosshair grid layout with pulsing center dot, axis labels, per-quadrant color coding (red/indigo/amber/slate)
  • Each item row shows source icon (bill/dispute/task/rec), title, amount, and relative due date
  • Responsive: stacked cards on mobile

Agent Board (/agents)

  • Kanban columns match Notion Agent Task Board statuses: Pending, Claimed, Running, Completed, Failed, Blocked
  • Task cards show type label, agent badge (color-coded per agent name), priority, due date, and verification type
  • Toggle to show/hide completed tasks
  • Responsive: 3-col on tablet, single-col on mobile

Test plan

  • Navigate to /priorities — verify matrix loads with data from all 4 sources
  • Navigate to /agents — verify kanban columns populate from tasks API
  • Check show/hide completed toggle on Agent Board
  • Verify mobile layout collapses properly on both pages
  • Confirm sidebar shows both new nav items (Priorities, Agent Board)
  • Type-check passes (npx tsc --noEmit)
  • Build succeeds (cd ui && npm run build)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Priorities page with an interactive 2x2 Priority Matrix (counts, refresh, loading/error states).
    • Added an Agent Board page presenting a Kanban-style board with columns, cards, metrics, and refresh control.
    • Added navigation entries for Priorities and Agent Board.
  • Style

    • Large presentational CSS for the matrix and Kanban board including responsive layout, animations, and theming.

Adds a tactical 2×2 priority matrix that aggregates obligations, disputes,
tasks, and recommendations into four quadrants (Do First, Schedule, Delegate,
Eliminate) based on importance and urgency scoring. Includes crosshair grid
layout with animated center dot, responsive mobile stacking, and per-item
source icons with due-date/amount metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions
Copy link

  1. @coderabbitai review
  2. @copilot review
  3. @codex review
  4. @claude review
    Adversarial review request: evaluate security, policy bypass paths, regression risk, and merge-gating bypass attempts.

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds two new pages—Priorities (Eisenhower matrix) and AgentBoard (Kanban)—updates navigation and routing, and introduces extensive presentational CSS for both interfaces. Priorities aggregates four APIs and classifies items into quadrants; AgentBoard presents grouped tasks with interactive controls.

Changes

Cohort / File(s) Summary
Navigation & Routing
ui/src/components/Sidebar.tsx, ui/src/main.tsx
Imports Crosshair and Bot icons; adds "Priorities" (/priorities) and "Agent Board" (/agents) navigation items and wires both pages into protected routes.
Styling
ui/src/index.css
Adds comprehensive CSS for the Eisenhower priority matrix and the Agent Board Kanban: layouts, quadrants, chips, crosshair/grid axes, Kanban columns/cards, responsive rules, animations, color tokens, and various state styles.
Priorities Page
ui/src/pages/Priorities.tsx
New Priorities component: fetches obligations, disputes, tasks, recommendations; normalizes items to a PriorityItem type; computes daysLeft/urgency/priority; classifies into four quadrants; renders quadrant panels, rows, loading/error states, and refresh control.
Agent Board Page
ui/src/pages/AgentBoard.tsx
New AgentBoard component: loads tasks, maps backend statuses to board columns, groups tasks into Kanban columns, computes aggregates, provides show-completed toggle and reload, renders TaskCard items with badges/overdue/priority, and handles loading/error/empty states.

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant Priorities as Priorities Page
    participant AgentBoard as AgentBoard Page
    participant API as Backend API
    participant Renderer as UI Renderer

    User->>Browser: Navigate to /priorities or /agents
    Browser->>Priorities: Mount Priorities (if /priorities)
    Browser->>AgentBoard: Mount AgentBoard (if /agents)

    Priorities->>API: getObligations(), getDisputes(), getTasks(), getRecommendations()
    API-->>Priorities: return data
    Priorities->>Priorities: normalize, score, classify, sort
    Priorities->>Renderer: render quadrants and rows

    AgentBoard->>API: load tasks
    API-->>AgentBoard: return tasks
    AgentBoard->>AgentBoard: map statuses, group into columns, compute aggregates
    AgentBoard->>Renderer: render columns and cards

    User->>Priorities: Click refresh
    Priorities->>API: refetch sources
    API-->>Priorities: updated data
    Priorities->>Renderer: re-render updated matrix

    User->>AgentBoard: Toggle showCompleted / Click reload
    AgentBoard->>API: refetch tasks (if reload)
    API-->>AgentBoard: updated tasks
    AgentBoard->>Renderer: re-render board
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A hop, a whisker, a crosshair in sight,
Cards march in columns by day and by night,
Four quadrants aligned, small tasks take their place,
Agents and priorities all find their space,
I nibble the changes and celebrate the pace!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Priority Matrix + Agent Board pages' accurately summarizes the main changes, highlighting both new features added in a concise manner.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/eisenhower-priority-matrix
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can approve the review once all CodeRabbit's comments are resolved.

Enable the reviews.request_changes_workflow setting to automatically approve the review once all CodeRabbit's comments are resolved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
ui/src/index.css (1)

748-748: Consider removing quotes around font-family name to satisfy Stylelint.

Stylelint flags 'Syne' with font-family-name-quotes error. While quotes around single-word font names are syntactically valid, removing them would satisfy the linter and match CSS conventions for simple identifiers.

🔧 Proposed fix
 .eisenhower-title {
-  font-family: 'Syne', sans-serif;
+  font-family: Syne, sans-serif;
   font-size: 1.25rem;
 .eisenhower-quadrant-label {
-  font-family: 'Syne', sans-serif;
+  font-family: Syne, sans-serif;
   font-weight: 700;

Also applies to: 960-960

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

In `@ui/src/index.css` at line 748, Remove the unnecessary quotes around the
single-word font family name in the font-family declarations (e.g., change
font-family: 'Syne', sans-serif; to font-family: Syne, sans-serif;) in
ui/src/index.css and any other occurrences flagged (notably the other occurrence
around line 960); update each font-family declaration so the single-word
identifier is unquoted to satisfy Stylelint's font-family-name-quotes rule.
ui/src/pages/Priorities.tsx (3)

310-310: Consider stricter typing for position parameter.

The position parameter accepts any string, but valid values are only 'tl' | 'tr' | 'bl' | 'br'. A union type would improve type safety.

🔒 Type-safe position parameter
-function QuadrantPanel({ quadrant, items, position }: { quadrant: Quadrant; items: PriorityItem[]; position: string }) {
+function QuadrantPanel({ quadrant, items, position }: { quadrant: Quadrant; items: PriorityItem[]; position: 'tl' | 'tr' | 'bl' | 'br' }) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/pages/Priorities.tsx` at line 310, The QuadrantPanel component
currently types the position prop as string; narrow it to the exact union "'tl'
| 'tr' | 'bl' | 'br'" for stronger type safety by changing the signature of
QuadrantPanel to accept position: 'tl' | 'tr' | 'bl' | 'br', then update any
call sites that pass position to ensure they use one of those literals (or
cast/derive a validated value) and adjust any logic that branches on position
(inside QuadrantPanel) if necessary to rely on the narrowed type.

144-144: Consider abort controller for fetch cleanup.

If the user navigates away while data is loading, the pending state updates could trigger React warnings. An AbortController in the useEffect cleanup would handle this gracefully, though it's not critical.

🛡️ Optional: Add abort controller for cleanup
+  useEffect(() => {
+    const controller = new AbortController();
+    load(controller.signal);
+    return () => controller.abort();
+  }, []);
-  useEffect(() => { load(); }, []);

This would require passing the signal to your API calls. Given the complexity involved and that React handles stale updates gracefully, this is truly optional.

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

In `@ui/src/pages/Priorities.tsx` at line 144, The useEffect currently calls
load() without cleanup, so add an AbortController in the useEffect and pass its
signal into load (and into any fetch/API calls inside load) so the fetches can
be aborted on unmount; add a cleanup function that calls controller.abort(), and
update load and the internal fetch calls to accept an optional signal parameter
and either pass it to fetch/axios or bail out (check signal.aborted) before
calling setState to avoid stale updates.

127-142: Resilient error handling with individual catch handlers.

Good pattern using individual .catch() handlers so one failing API doesn't block the entire page. However, the Array.isArray(taskRes) check on line 135 appears to be dead code since getTasks always returns an object structure (and your catch fallback also returns an object). Consider simplifying:

✨ Simplified task extraction
-      setTasks(Array.isArray(taskRes) ? taskRes : taskRes.tasks?.filter((t: Task) => t.backend_status !== 'completed') ?? []);
+      setTasks(taskRes.tasks?.filter((t: Task) => t.backend_status !== 'completed') ?? []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/pages/Priorities.tsx` around lines 127 - 142, The
Array.isArray(taskRes) branch is unnecessary because api.getTasks() and its
catch fallback always return an object; update the task handling in the
Promise.all result processing to treat taskRes as the object shape and setTasks
to taskRes.tasks filtered by backend_status !== 'completed' (with a safe
fallback to an empty array if tasks is missing). Locate the Promise.all
unpacking (taskRes) and the setTasks call (setTasks(...)) and remove the
Array.isArray() conditional, replacing it with a single expression that reads
taskRes.tasks ?? [] then filters out completed tasks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ui/src/index.css`:
- Line 748: Remove the unnecessary quotes around the single-word font family
name in the font-family declarations (e.g., change font-family: 'Syne',
sans-serif; to font-family: Syne, sans-serif;) in ui/src/index.css and any other
occurrences flagged (notably the other occurrence around line 960); update each
font-family declaration so the single-word identifier is unquoted to satisfy
Stylelint's font-family-name-quotes rule.

In `@ui/src/pages/Priorities.tsx`:
- Line 310: The QuadrantPanel component currently types the position prop as
string; narrow it to the exact union "'tl' | 'tr' | 'bl' | 'br'" for stronger
type safety by changing the signature of QuadrantPanel to accept position: 'tl'
| 'tr' | 'bl' | 'br', then update any call sites that pass position to ensure
they use one of those literals (or cast/derive a validated value) and adjust any
logic that branches on position (inside QuadrantPanel) if necessary to rely on
the narrowed type.
- Line 144: The useEffect currently calls load() without cleanup, so add an
AbortController in the useEffect and pass its signal into load (and into any
fetch/API calls inside load) so the fetches can be aborted on unmount; add a
cleanup function that calls controller.abort(), and update load and the internal
fetch calls to accept an optional signal parameter and either pass it to
fetch/axios or bail out (check signal.aborted) before calling setState to avoid
stale updates.
- Around line 127-142: The Array.isArray(taskRes) branch is unnecessary because
api.getTasks() and its catch fallback always return an object; update the task
handling in the Promise.all result processing to treat taskRes as the object
shape and setTasks to taskRes.tasks filtered by backend_status !== 'completed'
(with a safe fallback to an empty array if tasks is missing). Locate the
Promise.all unpacking (taskRes) and the setTasks call (setTasks(...)) and remove
the Array.isArray() conditional, replacing it with a single expression that
reads taskRes.tasks ?? [] then filters out completed tasks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d3e3300-6cfa-4403-8de7-4a703056c8f3

📥 Commits

Reviewing files that changed from the base of the PR and between 75a8e37 and b04a664.

📒 Files selected for processing (4)
  • ui/src/components/Sidebar.tsx
  • ui/src/index.css
  • ui/src/main.tsx
  • ui/src/pages/Priorities.tsx

Adds /agents page with a Kanban-style board mirroring the Notion Agent Task
Board. Displays tasks grouped by status columns (Pending → Claimed → Running →
Done/Failed/Blocked) with agent badges, task type labels, priority indicators,
and due date tracking. Includes toggle for completed tasks visibility.

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

  1. @coderabbitai review
  2. @copilot review
  3. @codex review
  4. @claude review
    Adversarial review request: evaluate security, policy bypass paths, regression risk, and merge-gating bypass attempts.

@chitcommit chitcommit changed the title feat: Eisenhower Priority Matrix page feat: Priority Matrix + Agent Board pages Mar 18, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Priorities.tsx ==="
cat ui/src/pages/Priorities.tsx

echo ""
echo "=== main.tsx ==="
cat ui/src/main.tsx

echo ""
echo "=== Sidebar.tsx ==="
cat ui/src/components/Sidebar.tsx

echo ""
echo "=== API calls used ==="
rg -n "getObligations|getDisputes|getTasks|getRecommendations" --type=ts -C3

echo ""
echo "=== API service files ==="
fd "api|service|client" --type=f -e ts -e tsx | head -20

echo ""
echo "=== ProtectedRoute implementation ==="
rg -n "ProtectedRoute" --type=tsx --type=ts -C5

echo ""
echo "=== dangerouslySetInnerHTML or innerHTML usage ==="
rg -n "dangerouslySetInnerHTML|innerHTML" --type=tsx --type=ts -C3

echo ""
echo "=== Direct URL/href construction from data ==="
rg -n "href=|window\.location|window\.open|navigate\(" --type=tsx --type=ts -C3

echo ""
echo "=== eval, Function constructor usage ==="
rg -n "\beval\b|new Function" --type=tsx --type=ts -C3

echo ""
echo "=== index.css additions (first 100 lines) ==="
head -100 ui/src/index.css

echo ""
echo "=== Check for any CSS injection risks ==="
rg -n "content:|background.*url|@import" ui/src/index.css | head -30

Length of output: 27264


🏁 Script executed:

#!/bin/bash
echo "=== api.ts full content ==="
cat ui/src/lib/api.ts

echo ""
echo "=== auth.ts ==="
cat ui/src/lib/auth.ts

echo ""
echo "=== utils.ts (formatCurrency, formatDate, daysUntil) ==="
cat ui/src/lib/utils.ts

echo ""
echo "=== getDisputes status param injection risk ==="
rg -n "status.*\+" ui/src/lib/api.ts

echo ""
echo "=== dangerouslySetInnerHTML anywhere in tsx files ==="
rg -rn "dangerouslySetInnerHTML|innerHTML" ui/src/ 

echo ""
echo "=== href, window.open, navigate from data ==="
rg -rn "href=\{|window\.open|window\.location" ui/src/

echo ""
echo "=== eval or Function( usage ==="
rg -rn "\beval\(|new Function\(" ui/src/

echo ""
echo "=== localStorage / sessionStorage usage (token storage) ==="
rg -rn "localStorage|sessionStorage|cookie" ui/src/

echo ""
echo "=== API base URL configuration ==="
rg -n "baseURL\|API_URL\|BASE_URL\|VITE_" ui/src/lib/api.ts

echo ""
echo "=== Request function - how auth token is attached ==="
rg -n "Authorization\|Bearer\|token\|headers" ui/src/lib/api.ts -A2 -B2

echo ""
echo "=== Check getDisputes status string concatenation ==="
grep -n "status" ui/src/lib/api.ts

echo ""
echo "=== getTasks limit param - is 100 cap enforced server-side check? ==="
grep -n "limit" ui/src/lib/api.ts

echo ""
echo "=== Check if Priorities.tsx renders any raw HTML strings ==="
grep -n "dangerouslySetInner\|__html\|innerHTML" ui/src/pages/Priorities.tsx

echo ""
echo "=== animationDelay inline style ==="
grep -n "animationDelay\|style={{" ui/src/pages/Priorities.tsx

echo ""
echo "=== Silent error swallowing - catch(() => []) pattern ==="
grep -n "catch(() =>" ui/src/pages/Priorities.tsx

echo ""
echo "=== classifyDispute - getDisputes called without filter, could expose resolved disputes? ==="
grep -n "getDisputes\|getObligations\|getRecommendations\|getTasks" ui/src/pages/Priorities.tsx

Length of output: 32924


@github-actions[bot]: Here is the adversarial security review of PR #45 (Eisenhower Priority Matrix). I reviewed all four changed files plus the full api.ts, auth.ts, and utils.ts chains.


🔐 Security Review — PR #45: Eisenhower Priority Matrix

✅ No Critical Attack Vectors Found in New Code

Vector Result
XSS via dangerouslySetInnerHTML ✅ None found anywhere in new files
eval / new Function ✅ None
User-data interpolated into CSS / inline styles animationDelay uses numeric array index only
Open redirect / window.open from data ✅ Not present in Priorities.tsx
CSS injection via background-image: url(...) ✅ Static SVG data URI, no user data interpolated
Unprotected route /priorities is inside ProtectedRoute
Auth bypass path ✅ No new auth surface introduced
Merge-gating bypass (CI/workflow changes) ✅ No .github/workflows modifications

🔴 Must-Fix: Silent Partial Failure Masks Critical Items

File: ui/src/pages/Priorities.tsx, Lines 127–137

const [obRes, dispRes, taskRes, recRes] = await Promise.all([
  api.getObligations().catch(() => [] as Obligation[]),       // ← silently swallowed
  api.getDisputes().catch(() => [] as Dispute[]),             // ← silently swallowed
  api.getTasks({ limit: 100 }).catch(() => ({ tasks: [] ... })), // ← silently swallowed
  api.getRecommendations().catch(() => [] as Recommendation[]),  // ← silently swallowed
]);

Because each child promise has its own .catch(), the outer try/catch never fires for partial failures. If getObligations() and getDisputes() both fail (network blip, backend error, rate-limit), the component renders a completely empty "Do First" quadrant with:

  • error === null (no banner shown)
  • loading === false (spinner gone)
  • Zero items displayed — silently

A user with a mortgage payment overdue could see "All clear" with no warning. This is both a correctness and a data-integrity security concern for a financial prioritisation tool.

Fix: Track partial failures and surface them:

const load = async () => {
  setLoading(true);
  setError(null);
  const partialErrors: string[] = [];

  const [obRes, dispRes, taskRes, recRes] = await Promise.all([
    api.getObligations().catch((e) => { partialErrors.push(`Bills: ${e.message}`); return [] as Obligation[]; }),
    api.getDisputes().catch((e) => { partialErrors.push(`Disputes: ${e.message}`); return [] as Dispute[]; }),
    api.getTasks({ limit: 100 }).catch((e) => { partialErrors.push(`Tasks: ${e.message}`); return { tasks: [] as Task[], total: 0, limit: 100, offset: 0 }; }),
    api.getRecommendations().catch((e) => { partialErrors.push(`Recs: ${e.message}`); return [] as Recommendation[]; }),
  ]);

  if (partialErrors.length > 0) {
    setError(`Partial load — some sources unavailable: ${partialErrors.join('; ')}`);
  }
  // ... rest of load
};

🟡 Medium: Task Pagination Silently Truncates High-Priority Items

File: ui/src/pages/Priorities.tsx, Line 130

api.getTasks({ limit: 100 })

If the user has >100 active tasks, tasks ranked beyond position 100 are invisibly omitted from the matrix with no indicator. A high-priority legal/financial task could be silently excluded from "Do First".

Fix: Either add a visual indicator (e.g., "Showing first 100 of {total} tasks") or increase the limit / paginate:

{taskRes.total > 100 && (
  <div className="eisenhower-warning">
    ⚠ Showing first 100 of {taskRes.total} tasks — some may not appear
  </div>
)}

🟡 Medium: getDisputes() Over-Fetches — Client-Side Status Filter

File: ui/src/pages/Priorities.tsx, Lines 129, 134

api.getDisputes()  // no status filter → fetches ALL disputes from server
// ...
setDisputes(dispRes.filter(d => d.status !== 'resolved' && d.status !== 'closed'))

All disputes including resolved/closed ones are transmitted over the wire, then discarded client-side. Compare with api.getDisputes('open') used in Disputes.tsx. This is a data over-fetch and increases the page's attack surface if the response payload is large.

Fix:

api.getDisputes('active')  // or 'open' — let the server filter

ℹ️ Pre-existing Issues (Not Introduced by This PR)

These exist in auth.ts and were present before this PR. They are noted for completeness:

  1. JWT stored in localStorage — susceptible to token theft if any XSS is ever introduced. No XSS vectors were found in this PR, but the storage mechanism is inherently weaker than httpOnly cookies.
  2. isAuthenticated() = !!getToken() — checks token presence only, not expiry or validity. An expired-but-present token passes the guard until a 401 is returned from an API call.

✅ Regression Risk Assessment

Area Risk
Sidebar nav ✅ Low — purely additive, existing items unchanged
Router (main.tsx) ✅ Low — new sibling route, no existing routes modified
CSS (index.css) ✅ Low — all classes namespaced under eisenhower-*, no global selector overrides
Classification logic ⚠️ Medium — no automated tests; if urgency_score, priority, or type fields change on the backend, quadrant assignments silently drift

🔒 Merge-Gating Bypass Assessment

None found. No .github/workflows files were modified, no CI configuration was changed, no linting/type-check configs were altered, and no test files were added or removed.


Summary: The PR is safe to merge from an XSS/injection/auth-bypass perspective. The two must-fix items are correctness/data-integrity issues: silent partial failure hiding critical financial items, and silent task truncation at 100. These should be resolved before merge given the financial-data sensitivity of the application.


🧠 Learnings used
Learnt from: CR
Repo: chittyos/chittycommand PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-04T04:42:57.380Z
Learning: Resolve must-fix review comments (security, correctness, compliance, merge blockers) before merge

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
ui/src/pages/AgentBoard.tsx (1)

132-195: Please keep this new page in Tailwind instead of growing a global .ab-* stylesheet contract.

The component is now tightly coupled to a large page-specific block in ui/src/index.css, which makes the styling harder to co-locate and reuse. Please move these view styles into Tailwind utilities/components rather than extending the global stylesheet for a single page.

As per coding guidelines: ui/**/*.{ts,tsx}: Use Tailwind CSS for styling in React frontend components.

Also applies to: 201-239

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

In `@ui/src/pages/AgentBoard.tsx` around lines 132 - 195, The page currently
relies on a global `.ab-*` stylesheet; replace these with Tailwind utility
classes co-located in the component to avoid growing global styles. Update
AgentBoard.tsx to remove usages of className values like "ab-root", "ab-header",
"ab-header-left", "ab-header-actions", "ab-toggle", "ab-refresh", "ab-error",
"ab-board", "ab-column", "ab-column-header", "ab-column-icon",
"ab-column-label", "ab-column-tactical", "ab-column-count", "ab-column-list",
"ab-empty" and instead apply equivalent Tailwind utility classes (layout,
spacing, typography, colors, flex/grid) directly in the JSX (use the cn helper
where conditional classes are needed, e.g., toggle active state). For the
dynamic column count currently set via style {{ '--ab-cols': activeCols.length
}}, convert to a Tailwind-friendly layout (e.g., responsive grid with dynamic
grid-cols by mapping activeCols.length to `grid-cols-{n}` class or compute a
className string) or use a small inline style object for CSS variables scoped to
the component. After updating JSX, remove the corresponding `.ab-*` rules from
the global index.css. Keep TaskCard and other component props unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/src/index.css`:
- Line 748: Remove the unnecessary quotes around the Syne font in each
declaration flagged by stylelint; locate every instance of the declaration
pattern font-family: 'Syne', sans-serif; (including the occurrences flagged at
lines noted in the review) and change it to use the unquoted identifier
font-family: Syne, sans-serif; so the font-family value is a valid CSS
identifier and satisfies the font-family-name-quotes rule.

In `@ui/src/pages/AgentBoard.tsx`:
- Around line 152-154: The reload button that calls the load function and
renders the RefreshCw icon lacks an accessible name; add an aria-label (e.g.,
aria-label="Reload") and set type="button" on the <button> with className
"ab-refresh" to ensure it's correctly announced and behaves as a button.
- Around line 77-82: The load function currently calls api.getTasks({ limit: 200
}) and only uses that single page, so boards break when there are >200 tasks;
update load to page through the API by repeatedly calling api.getTasks with the
returned pagination token/nextPage parameters (or offset) until no more pages,
accumulating results and then call setTasks with the full array, and ensure any
API pagination fields used by api.getTasks (e.g., res.nextCursor / res.pageToken
or res.tasks/res.data) are handled; alternatively, if you intend to keep a hard
cap, make load set a visible capped flag and surface that in the UI instead of
silently truncating (refer to the load function, setTasks, and api.getTasks).
- Line 1: The code uses React.CSSProperties (e.g., in the style type at the
component or variable around line 166) but the React namespace isn't imported
under the automatic JSX runtime; import the CSSProperties type directly from
'react' (add CSSProperties to the existing import from react) and replace
occurrences of React.CSSProperties with CSSProperties (or annotate
variables/props using CSSProperties) so TypeScript compiles under "react-jsx".

---

Nitpick comments:
In `@ui/src/pages/AgentBoard.tsx`:
- Around line 132-195: The page currently relies on a global `.ab-*` stylesheet;
replace these with Tailwind utility classes co-located in the component to avoid
growing global styles. Update AgentBoard.tsx to remove usages of className
values like "ab-root", "ab-header", "ab-header-left", "ab-header-actions",
"ab-toggle", "ab-refresh", "ab-error", "ab-board", "ab-column",
"ab-column-header", "ab-column-icon", "ab-column-label", "ab-column-tactical",
"ab-column-count", "ab-column-list", "ab-empty" and instead apply equivalent
Tailwind utility classes (layout, spacing, typography, colors, flex/grid)
directly in the JSX (use the cn helper where conditional classes are needed,
e.g., toggle active state). For the dynamic column count currently set via style
{{ '--ab-cols': activeCols.length }}, convert to a Tailwind-friendly layout
(e.g., responsive grid with dynamic grid-cols by mapping activeCols.length to
`grid-cols-{n}` class or compute a className string) or use a small inline style
object for CSS variables scoped to the component. After updating JSX, remove the
corresponding `.ab-*` rules from the global index.css. Keep TaskCard and other
component props unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8ebf837-f222-4553-86f6-e4d283530139

📥 Commits

Reviewing files that changed from the base of the PR and between b04a664 and d52b41a.

📒 Files selected for processing (4)
  • ui/src/components/Sidebar.tsx
  • ui/src/index.css
  • ui/src/main.tsx
  • ui/src/pages/AgentBoard.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • ui/src/components/Sidebar.tsx
  • ui/src/main.tsx

opacity: 0.8;
}
.eisenhower-title {
font-family: 'Syne', sans-serif;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and examine the flagged lines
if [ -f "ui/src/index.css" ]; then
  echo "=== File exists. Checking flagged lines ==="
  sed -n '748p;960p;1184p;1301p' ui/src/index.css | cat -n
else
  echo "File not found. Searching for index.css..."
  find . -name "index.css" -type f
fi

Repository: chittyos/chittycommand

Length of output: 277


🏁 Script executed:

# Also check the context around these lines to understand the CSS structure
echo "=== Context around line 748 ==="
sed -n '745,751p' ui/src/index.css | cat -n

echo -e "\n=== Context around line 960 ==="
sed -n '957,963p' ui/src/index.css | cat -n

echo -e "\n=== Context around line 1184 ==="
sed -n '1181,1187p' ui/src/index.css | cat -n

echo -e "\n=== Context around line 1301 ==="
sed -n '1298,1304p' ui/src/index.css | cat -n

Repository: chittyos/chittycommand

Length of output: 988


🏁 Script executed:

# Check for Stylelint configuration files
echo "=== Searching for Stylelint config ==="
find . -maxdepth 3 -type f \( -name ".stylelintrc*" -o -name "stylelint.config.*" \) 2>/dev/null | head -20

Repository: chittyos/chittycommand

Length of output: 124


🏁 Script executed:

# Check Stylelint configuration
cat ./.stylelintrc.json

Repository: chittyos/chittycommand

Length of output: 1072


🏁 Script executed:

# Check if there's any documentation or comments about the font-family-name-quotes rule
# Also verify the CSS standards for font-family declarations
web_search "CSS font-family single word quotes unquoted Syne"

Repository: chittyos/chittycommand

Length of output: 116


🌐 Web query:

Stylelint font-family-name-quotes rule single-word font unquoted

💡 Result:

The Stylelint rule "font-family-name-quotes" controls quoting for font-family names in font and font-family properties. It has three options, none of which are named "single-word" or directly tied to "single-line", but they determine when single-word fonts (valid CSS identifiers, not keywords like "serif") should be unquoted. - "always-unless-keyword": Requires quotes around ALL non-keyword font names, so single-word fonts like "Arial" or "Roboto" MUST be quoted. Unquoted is a violation. (e.g. font-family: Arial → error; font-family: 'Arial' → ok) - "always-where-required": Requires quotes ONLY for invalid CSS identifiers (e.g. containing $, !, /), disallows elsewhere. Single-word fonts like "Arial" (valid identifiers) should be UNQUOTED. Quoted is a violation; unquoted is ok. (e.g. font-family: "Arial" → error; font-family: Arial → ok) - "always-where-recommended": Requires quotes for spec-recommended cases (whitespace, digits, punctuation beyond hyphens), disallows elsewhere. Single-word fonts without those (e.g. "Arial", "Roboto") should be UNQUOTED. (e.g. "Roboto" → autofix removes quotes) All options disallow quotes around keywords (serif, sans-serif, etc.) and vendor system fonts (-apple-system). Single-word fonts that are valid CSS identifiers are unquoted by default in "always-where-required" and "always-where-recommended". Use these for unquoted single-word fonts. Rule supports --fix autofix.

Citations:


🌐 Web query:

CSS font-family quotes single word font name standard

💡 Result:

In CSS, for the font-family property, a single-word font name that is a valid custom identifier (consisting of letters, digits (not starting with digit unless escaped), hyphens, underscores, or non-ASCII Unicode characters, and not matching a generic keyword like 'serif' or 'sans-serif') does not require quotes. It is valid both quoted (as a string) and unquoted (as a custom-ident). Quotes are optional but recommended by specs for names with whitespace, digits, or punctuation to avoid parsing issues. Generic families must remain unquoted.

Citations:


🌐 Web query:

stylelint-config-standard-scss font-family-name-quotes default rule

💡 Result:

In stylelint-config-standard-scss (latest v17.0.0 as of Jan 2026), the font-family-name-quotes rule defaults to "always-where-recommended". This is inherited from its parent config stylelint-config-standard (v40.0.0), which explicitly sets 'font-family-name-quotes': 'always-where-recommended' in its index.js file. The stylelint-config-standard-scss index.js extends stylelint-config-standard and stylelint-config-recommended-scss without overriding this rule, so it uses the parent's setting. This option requires quotes around font family names only when recommended by the CSS spec (e.g., names with whitespace, digits, or certain punctuation, but not hyphens), and disallows them otherwise. It never requires quotes around generic keywords like "serif" or system fonts like "-apple-system".

Citations:


Remove the quotes from these new font-family declarations.

Stylelint is flagging each of these as font-family-name-quotes violations. Syne is a single-word font name that is a valid CSS identifier, so it should remain unquoted.

🧹 Proposed fix
 .eisenhower-title {
-  font-family: 'Syne', sans-serif;
+  font-family: Syne, sans-serif;
 }

 .eisenhower-quadrant-label {
-  font-family: 'Syne', sans-serif;
+  font-family: Syne, sans-serif;
 }

 .ab-title {
-  font-family: 'Syne', sans-serif;
+  font-family: Syne, sans-serif;
 }

 .ab-column-label {
-  font-family: 'Syne', sans-serif;
+  font-family: Syne, sans-serif;
 }

Also applies to: 960-960, 1184-1184, 1301-1301

🧰 Tools
🪛 Stylelint (17.4.0)

[error] 748-748: Unexpected quotes around "Syne" (font-family-name-quotes)

(font-family-name-quotes)

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

In `@ui/src/index.css` at line 748, Remove the unnecessary quotes around the Syne
font in each declaration flagged by stylelint; locate every instance of the
declaration pattern font-family: 'Syne', sans-serif; (including the occurrences
flagged at lines noted in the review) and change it to use the unquoted
identifier font-family: Syne, sans-serif; so the font-family value is a valid
CSS identifier and satisfies the font-family-name-quotes rule.

@@ -0,0 +1,240 @@
import { useEffect, useState, useMemo, useCallback } from 'react';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- AgentBoard imports ---'
sed -n '1,25p' ui/src/pages/AgentBoard.tsx

printf '\n%s\n' '--- tsconfig flags that affect React namespace access ---'
fd 'tsconfig*.json' . -x sh -c 'printf "\n## %s\n" "$1"; rg -n "\"jsx\"|\"allowUmdGlobalAccess\"" "$1" || true' sh {}

printf '\n%s\n' '--- Existing CSSProperties patterns in ui/src ---'
rg -n --type=ts --type=tsx 'React\.CSSProperties|type\s+CSSProperties|import\s+React' ui/src

Repository: chittyos/chittycommand

Length of output: 1820


🏁 Script executed:

wc -l ui/src/pages/AgentBoard.tsx

Repository: chittyos/chittycommand

Length of output: 97


🏁 Script executed:

sed -n '155,175p' ui/src/pages/AgentBoard.tsx

Repository: chittyos/chittycommand

Length of output: 838


🏁 Script executed:

grep -n "React\\.CSSProperties" ui/src/pages/AgentBoard.tsx

Repository: chittyos/chittycommand

Length of output: 168


🏁 Script executed:

grep -n "import.*React" ui/src/pages/AgentBoard.tsx

Repository: chittyos/chittycommand

Length of output: 48


🏁 Script executed:

grep -n "React\\.CSSProperties" ui/src/pages/AgentBoard.tsx | wc -l

Repository: chittyos/chittycommand

Length of output: 67


🏁 Script executed:

grep -n "CSSProperties" ui/src/pages/AgentBoard.tsx

Repository: chittyos/chittycommand

Length of output: 168


Import the CSSProperties type from React and use it instead of React.CSSProperties.

Code at line 166 references React.CSSProperties without importing the React namespace. Since the UI tsconfig uses the automatic JSX runtime ("jsx": "react-jsx"), the React namespace is not in scope, and this will fail TypeScript compilation.

🛠️ Fix required
-import { useEffect, useState, useMemo, useCallback } from 'react';
+import { useEffect, useState, useMemo, useCallback, type CSSProperties } from 'react';-      <div className="ab-board" style={{ '--ab-cols': activeCols.length } as React.CSSProperties}>
+      <div className="ab-board" style={{ '--ab-cols': activeCols.length } as CSSProperties}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/pages/AgentBoard.tsx` at line 1, The code uses React.CSSProperties
(e.g., in the style type at the component or variable around line 166) but the
React namespace isn't imported under the automatic JSX runtime; import the
CSSProperties type directly from 'react' (add CSSProperties to the existing
import from react) and replace occurrences of React.CSSProperties with
CSSProperties (or annotate variables/props using CSSProperties) so TypeScript
compiles under "react-jsx".

Comment on lines +77 to +82
const load = useCallback(async () => {
setLoading(true);
setError(null);
try {
const res = await api.getTasks({ limit: 200 });
setTasks(Array.isArray(res) ? res : res.tasks ?? []);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This only loads the first page of tasks.

ui/src/lib/api.ts:328-332 returns paginated task data, but load() always stops at limit: 200. Once there are more than 200 tasks, columns disappear and the header counts become wrong. Either page through the full result set or explicitly surface that the board is capped.

📦 Proposed fix
   const load = useCallback(async () => {
     setLoading(true);
     setError(null);
     try {
-      const res = await api.getTasks({ limit: 200 });
-      setTasks(Array.isArray(res) ? res : res.tasks ?? []);
+      const pageSize = 200;
+      const firstPage = await api.getTasks({ limit: pageSize, offset: 0 });
+      if (Array.isArray(firstPage)) {
+        setTasks(firstPage);
+        return;
+      }
+
+      const allTasks = [...(firstPage.tasks ?? [])];
+      for (let offset = allTasks.length; offset < firstPage.total; offset += pageSize) {
+        const page = await api.getTasks({ limit: pageSize, offset });
+        if (Array.isArray(page)) break;
+        allTasks.push(...(page.tasks ?? []));
+      }
+
+      setTasks(allTasks);
     } catch (e: unknown) {
       setError(e instanceof Error ? e.message : 'Failed to load tasks');
     } finally {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/pages/AgentBoard.tsx` around lines 77 - 82, The load function
currently calls api.getTasks({ limit: 200 }) and only uses that single page, so
boards break when there are >200 tasks; update load to page through the API by
repeatedly calling api.getTasks with the returned pagination token/nextPage
parameters (or offset) until no more pages, accumulating results and then call
setTasks with the full array, and ensure any API pagination fields used by
api.getTasks (e.g., res.nextCursor / res.pageToken or res.tasks/res.data) are
handled; alternatively, if you intend to keep a hard cap, make load set a
visible capped flag and surface that in the UI instead of silently truncating
(refer to the load function, setTasks, and api.getTasks).

Comment on lines +152 to +154
<button onClick={load} className="ab-refresh" title="Reload">
<RefreshCw size={16} />
</button>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Give the icon-only reload button an accessible name.

Right now this control only renders the SVG, and title is not a reliable accessible label. Add an aria-label here; type="button" is also worth setting while you’re touching the element.

♿ Proposed fix
-          <button onClick={load} className="ab-refresh" title="Reload">
+          <button
+            type="button"
+            onClick={load}
+            className="ab-refresh"
+            title="Reload"
+            aria-label="Reload agent board"
+          >
             <RefreshCw size={16} />
           </button>
📝 Committable suggestion

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

Suggested change
<button onClick={load} className="ab-refresh" title="Reload">
<RefreshCw size={16} />
</button>
<button
type="button"
onClick={load}
className="ab-refresh"
title="Reload"
aria-label="Reload agent board"
>
<RefreshCw size={16} />
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/pages/AgentBoard.tsx` around lines 152 - 154, The reload button that
calls the load function and renders the RefreshCw icon lacks an accessible name;
add an aria-label (e.g., aria-label="Reload") and set type="button" on the
<button> with className "ab-refresh" to ensure it's correctly announced and
behaves as a button.

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