Conversation
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>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
📝 WalkthroughWalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment Tip CodeRabbit can approve the review once all CodeRabbit's comments are resolved.Enable the |
There was a problem hiding this comment.
🧹 Nitpick comments (4)
ui/src/index.css (1)
748-748: Consider removing quotes around font-family name to satisfy Stylelint.Stylelint flags
'Syne'withfont-family-name-quoteserror. 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 forpositionparameter.The
positionparameter accepts anystring, 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
AbortControllerin 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, theArray.isArray(taskRes)check on line 135 appears to be dead code sincegetTasksalways 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
📒 Files selected for processing (4)
ui/src/components/Sidebar.tsxui/src/index.cssui/src/main.tsxui/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>
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 27264 🏁 Script executed: Length of output: 32924
🔐 Security Review — PR
|
| 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:
- 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 thanhttpOnlycookies. 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 | 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
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
ui/src/components/Sidebar.tsxui/src/index.cssui/src/main.tsxui/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; |
There was a problem hiding this comment.
🧩 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
fiRepository: 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 -nRepository: 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 -20Repository: chittyos/chittycommand
Length of output: 124
🏁 Script executed:
# Check Stylelint configuration
cat ./.stylelintrc.jsonRepository: 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:
- 1: https://stylelint.io/user-guide/rules/font-family-name-quotes/
- 2: https://stylelint.io/user-guide/rules/font-family-name-quotes
🌐 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:
- 1: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-family
- 2: https://www.w3.org/TR/CSS21/fonts.html
- 3: https://www.w3.org/TR/css-fonts-3/
- 4: https://mathiasbynens.be/notes/unquoted-font-family
🌐 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:
- 1: https://github.com/stylelint-scss/stylelint-config-standard-scss
- 2: https://github.com/stylelint/stylelint-config-standard
- 3: https://registry.npmjs.org/stylelint-config-standard-scss
- 4: https://www.npmjs.com/package/stylelint-config-standard
- 5: https://github.com/stylelint/stylelint-config-standard/blob/HEAD/index.js
- 6: https://github.com/stylelint-scss/stylelint-config-standard-scss/blob/main/index.js
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'; | |||
There was a problem hiding this comment.
🧩 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/srcRepository: chittyos/chittycommand
Length of output: 1820
🏁 Script executed:
wc -l ui/src/pages/AgentBoard.tsxRepository: chittyos/chittycommand
Length of output: 97
🏁 Script executed:
sed -n '155,175p' ui/src/pages/AgentBoard.tsxRepository: chittyos/chittycommand
Length of output: 838
🏁 Script executed:
grep -n "React\\.CSSProperties" ui/src/pages/AgentBoard.tsxRepository: chittyos/chittycommand
Length of output: 168
🏁 Script executed:
grep -n "import.*React" ui/src/pages/AgentBoard.tsxRepository: chittyos/chittycommand
Length of output: 48
🏁 Script executed:
grep -n "React\\.CSSProperties" ui/src/pages/AgentBoard.tsx | wc -lRepository: chittyos/chittycommand
Length of output: 67
🏁 Script executed:
grep -n "CSSProperties" ui/src/pages/AgentBoard.tsxRepository: 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".
| const load = useCallback(async () => { | ||
| setLoading(true); | ||
| setError(null); | ||
| try { | ||
| const res = await api.getTasks({ limit: 200 }); | ||
| setTasks(Array.isArray(res) ? res : res.tasks ?? []); |
There was a problem hiding this comment.
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).
| <button onClick={load} className="ab-refresh" title="Reload"> | ||
| <RefreshCw size={16} /> | ||
| </button> |
There was a problem hiding this comment.
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.
| <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.
Summary
/prioritiespage — 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/agentspage — 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 trackingDetails
Priority Matrix (
/priorities)Agent Board (
/agents)Test plan
/priorities— verify matrix loads with data from all 4 sources/agents— verify kanban columns populate from tasks APInpx tsc --noEmit)cd ui && npm run build)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Style