Skip to content

Improved grants AI reviewer#1396

Open
RevTpark wants to merge 1 commit into
mainfrom
fix/improve-grants-ai-reviewer
Open

Improved grants AI reviewer#1396
RevTpark wants to merge 1 commit into
mainfrom
fix/improve-grants-ai-reviewer

Conversation

@RevTpark

@RevTpark RevTpark commented May 26, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

  • changed Notes from normal editor to Rich Text Editor
  • updated "notes" field from varchar to text
  • Updated evaluation format to support agent review output format.
  • added formatter for AI Review before adding to the notes.

Where should the reviewer start?

How should this be manually tested?

Any background context you want to provide?

What are the relevant issues?

Screenshots (if appropriate)

Summary by CodeRabbit

Release Notes

  • New Features
    • Rich text editor with formatting toolbar (bold, italic, lists) added to notes
    • Notes panel is now resizable with auto-saved width preference
    • AI review recommendations automatically formatted and included in application notes
    • Increased notes character limit to 2000 for expanded content

Review Change Stack

@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
earn Ready Ready Preview Jun 8, 2026 12:35pm

Request Review

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The PR extends grant application notes from plain text (1000 char limit) to rich HTML content (unlimited via Text column). It introduces AI-powered note formatting, adds a resizable editor panel with formatting toolbar, and implements character validation across the client and server.

Changes

Rich text notes editor with AI formatting and resizable layout

Layer / File(s) Summary
Database schema and evaluation types
prisma/schema.prisma, src/features/grants/types/index.ts
Notes column expands from VarChar(1000) to Text. EvaluationResult type adds optional recommendation, confidence, decisionReason, risks, scores, and nested colosseum/solanaTechnical objects for richer AI evaluation data.
AI review notes formatting utility
src/features/grants/utils/formatGrantApplicationAiReviewNotes.ts, src/app/api/sponsor-dashboard/grant-application/ai/commit-reviewed/route.ts, src/features/sponsor-dashboard/mutations/useCommitReviewsGrantApplications.ts
New formatGrantApplicationAiReviewNotes utility converts AI review into structured bullet-point notes with recommendation, reasoning, scores, and risks. Replaces prior inline shortNote splitting/bulleting in both the commit-reviewed API route and useCommitReviewsGrantApplications mutation, each piping output through convertTextToNotesHTML for HTML storage.
Rich text editor component with formatting toolbar
src/features/sponsor-dashboard/components/NotesRichEditor.tsx
Introduces NotesSelectionMenu component with TipTap BubbleMenu offering Bold/Italic/Numbered list/Bullet list toggles on non-empty selection. Updates editor imports and layout to flex column with menu above scroll area, and revises CSS sizing/overflow rules to min-height: 100% and overflow: visible.
Notes component refactor to HTML-aware editing
src/features/sponsor-dashboard/components/GrantApplications/Notes.tsx
Replaces plain Textarea with NotesRichEditor. Adds HTML detection (isHtmlNote), automatic plain-text-to-HTML conversion (convertTextToNotesHTML), and character counting via getTextCharacterCount for 2000-char limit. Refactors state sync to watch application changes and reinitialize editor, and replaces useMemo debounce with useRef-based debounced function with cleanup.
Resizable notes panel with localStorage persistence
src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx
Notes panel becomes drag-resizable with width persisted to localStorage. Adds sizing constants, clampNotesWidth helper (20–80% range), pointer event handlers for real-time resize, and ref-based flex layout using flexBasis derived from notesWidth state. On mount, restores user's preferred width from storage.
API validation for plain-text notes length
src/pages/api/sponsor-dashboard/grants/update-notes.ts
Adds server-side validation extracting plain-text length from HTML notes and rejecting requests exceeding MAX_NOTES_LENGTH (2000 chars) or where notes is not a string, returning 400 error.
Toast message copy update
src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx
Corrects onComplete toast message text from "submissions" to "applications".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The PR spans multiple layers: schema/type updates, a new AI formatting utility, rich editor with toolbar components, Notes component refactor with state management changes, ApplicationDetails layout changes with pointer handlers and localStorage, and API validation. Heterogeneous edits across files demand separate reasoning for each checkpoint, but changes follow consistent patterns (HTML conversion, character validation, state synchronization) and the logic density is moderate, not critical.

Possibly related PRs

  • SuperteamDAO/earn#1144: Fixes convertTextToNotesHTML nested <ul> handling, directly related to this PR's new usage of that utility for AI and editor notes HTML generation.
  • SuperteamDAO/earn#1092: Modifies shortNote cleanup before splitting in the same commit-reviewed AI review path that this PR replaces with formatGrantApplicationAiReviewNotes.
  • SuperteamDAO/earn#1091: Alters AI review shortNotenotes transformation in the sponsor-dashboard commit-reviewed flow, the same path refactored here with shared formatting utilities.

Suggested reviewers

  • a20hek

Poem

🐰 Rich notes now bloom in vibrant hues,
HTML threads and AI clues,
Drag to resize, format with flair,
Grant reviews flourish everywhere! 🌻

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Improved grants AI reviewer' is vague and generic, using non-descriptive language that does not clearly convey what specific aspects of the grants AI reviewer were improved or what the main changes entail. Consider a more specific title that highlights the primary change, such as 'Add rich text editor for grant notes and update AI review formatting' or 'Enhance AI review notes with rich text editing and improved formatting'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 fix/improve-grants-ai-reviewer

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx (2)

575-583: ⚡ Quick win

Consider adding explicit pointer capture release.

While pointer capture is typically released automatically, adding an onPointerUp handler to explicitly call releasePointerCapture improves robustness and makes the capture lifecycle explicit.

♻️ Proposed addition

Add this handler to the component:

const handleResizePointerUp = useCallback(
  (event: React.PointerEvent<HTMLButtonElement>) => {
    if (event.currentTarget.hasPointerCapture(event.pointerId)) {
      event.currentTarget.releasePointerCapture(event.pointerId);
    }
  },
  [],
);

Then add to the button:

 <button
   aria-label="Resize notes panel"
   className="group flex w-3 shrink-0 cursor-col-resize items-stretch justify-center self-stretch focus:outline-none"
   onPointerDown={handleResizePointerDown}
   onPointerMove={handleResizePointerMove}
+  onPointerUp={handleResizePointerUp}
   type="button"
 >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx`
around lines 575 - 583, The pointer-capture lifecycle should be made explicit:
add a new handler function named handleResizePointerUp (useCallback) that checks
event.currentTarget.hasPointerCapture(event.pointerId) and calls
event.currentTarget.releasePointerCapture(event.pointerId) when true, and attach
it to the resize button as onPointerUp; this complements the existing
handleResizePointerDown and handleResizePointerMove handlers and ensures pointer
capture is explicitly released.

575-583: 🏗️ Heavy lift

Consider adding keyboard support for panel resizing.

The resize handle currently only supports pointer-based interaction. Adding keyboard support (e.g., arrow keys to adjust width) would improve accessibility for users who rely on keyboard navigation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx`
around lines 575 - 583, The resize handle only supports pointer events; add
keyboard support by handling key events on the same button element (where
handleResizePointerDown and handleResizePointerMove are used): implement an
onKeyDown handler (e.g., handleResizeKeyDown) that listens for
ArrowLeft/ArrowRight (and Home/End/PageUp/PageDown if desired) to adjust the
panel width state or call the same resize logic used by handleResizePointerMove,
ensure focus styling and aria attributes (aria-label already present) are
preserved, and update any state-updating functions (the resize state updater
used by handleResizePointerMove) to be callable from the keyboard handler so
keyboard users can incrementally resize the panel.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/api/sponsor-dashboard/grant-application/ai/commit-reviewed/route.ts`:
- Around line 87-89: The AI-derived notes are being converted to and stored as
raw HTML via
convertTextToNotesHTML(formatGrantApplicationAiReviewNotes(aiReview)), which
risks stored XSS; update the code so the AI text is sanitized/escaped before
HTML persistence—either modify convertTextToNotesHTML to escape HTML entities
and/or run a strict HTML sanitizer (whitelist-based, e.g., sanitize-html or
DOMPurify server-side) on its output, or escape the result of
formatGrantApplicationAiReviewNotes(aiReview) prior to conversion; ensure the
sanitizer is applied server-side in the route handling (route.ts) and that only
safe tags/attributes are allowed.
- Around line 87-89: The code unconditionally writes notes:
convertTextToNotesHTML(formatGrantApplicationAiReviewNotes(aiReview)) which
becomes an empty string when aiReview is undefined and can wipe existing notes;
change the update logic in route.ts so the notes field is only set when aiReview
is present (e.g., conditionally include notes or set notes to undefined) —
reference the aiReview variable and the functions
formatGrantApplicationAiReviewNotes and convertTextToNotesHTML and ensure
existing notes are preserved by omitting the notes property when aiReview is
falsy.

In `@src/features/grants/types/index.ts`:
- Around line 83-110: The EvaluationResult type uses optional properties (e.g.
recommendation?, confidence?, decisionReason?, risks?, colosseum?,
colosseum.error?, solanaTechnical? and its nested fields, and scores?) which
must be converted to explicit union-with-undefined form; update the
EvaluationResult definition so each optional becomes property: T | undefined
(and for arrays or nested fields use string[] | undefined or error: string |
undefined etc.), preserving the existing types/values but replacing every "?"
optional marker with the explicit "| undefined" form for top-level and nested
fields like colosseum.error and solanaTechnical.* and scores.

In `@src/features/grants/utils/formatGrantApplicationAiReviewNotes.ts`:
- Around line 5-76: Add explicit return type annotations to the top-level helper
arrow functions: labelText should be (value?: string) => string | undefined,
compactText should be (value?: string) => string | undefined, normalizeNote
should be (value?: string) => string | undefined, truncate should be (value:
string, maxLength: number) => string, addMissingRisksToShortNote should be
(note: string, risks?: string[]) => string, and both appendBullet and
appendListBullet should be typed as returning void; update the const
declarations for labelText, compactText, normalizeNote, truncate,
addMissingRisksToShortNote, appendBullet, and appendListBullet with these
explicit return types so TypeScript enforces the intended signatures.

In `@src/features/sponsor-dashboard/components/GrantApplications/Notes.tsx`:
- Around line 108-113: The effect in Notes.tsx only updates local notes when
previousApplicationIdRef.current !== selectedApplication?.id, so when the atom
replaces selectedApplication.notes but keeps the same id the editor stays stale;
modify the useEffect (the one that calls
setNotes(normalizeNotesForEditor(selectedApplication?.notes))) to also detect
changes to notes by adding a previousNotesRef (or comparing
previousNotesRef.current) and update when previousNotesRef.current !==
selectedApplication?.notes (updating previousNotesRef.current after setNotes),
so changes to selectedApplication.notes will sync into the editor even if id is
unchanged.

---

Nitpick comments:
In
`@src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx`:
- Around line 575-583: The pointer-capture lifecycle should be made explicit:
add a new handler function named handleResizePointerUp (useCallback) that checks
event.currentTarget.hasPointerCapture(event.pointerId) and calls
event.currentTarget.releasePointerCapture(event.pointerId) when true, and attach
it to the resize button as onPointerUp; this complements the existing
handleResizePointerDown and handleResizePointerMove handlers and ensures pointer
capture is explicitly released.
- Around line 575-583: The resize handle only supports pointer events; add
keyboard support by handling key events on the same button element (where
handleResizePointerDown and handleResizePointerMove are used): implement an
onKeyDown handler (e.g., handleResizeKeyDown) that listens for
ArrowLeft/ArrowRight (and Home/End/PageUp/PageDown if desired) to adjust the
panel width state or call the same resize logic used by handleResizePointerMove,
ensure focus styling and aria attributes (aria-label already present) are
preserved, and update any state-updating functions (the resize state updater
used by handleResizePointerMove) to be callable from the keyboard handler so
keyboard users can incrementally resize the panel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 434450d5-1f44-428b-9016-808fa4dba202

📥 Commits

Reviewing files that changed from the base of the PR and between dd345c5 and e9051f4.

📒 Files selected for processing (10)
  • prisma/schema.prisma
  • src/app/api/sponsor-dashboard/grant-application/ai/commit-reviewed/route.ts
  • src/features/grants/types/index.ts
  • src/features/grants/utils/formatGrantApplicationAiReviewNotes.ts
  • src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx
  • src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx
  • src/features/sponsor-dashboard/components/GrantApplications/Notes.tsx
  • src/features/sponsor-dashboard/components/NotesRichEditor.tsx
  • src/features/sponsor-dashboard/mutations/useCommitReviewsGrantApplications.ts
  • src/pages/api/sponsor-dashboard/grants/update-notes.ts

Comment on lines +87 to +89
notes: convertTextToNotesHTML(
formatGrantApplicationAiReviewNotes(aiReview),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Sanitize AI-derived note content before HTML persistence.

convertTextToNotesHTML interpolates content into <li>/<p> without escaping. Since AI output is untrusted text, storing it as raw HTML here creates a stored-XSS risk path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/sponsor-dashboard/grant-application/ai/commit-reviewed/route.ts`
around lines 87 - 89, The AI-derived notes are being converted to and stored as
raw HTML via
convertTextToNotesHTML(formatGrantApplicationAiReviewNotes(aiReview)), which
risks stored XSS; update the code so the AI text is sanitized/escaped before
HTML persistence—either modify convertTextToNotesHTML to escape HTML entities
and/or run a strict HTML sanitizer (whitelist-based, e.g., sanitize-html or
DOMPurify server-side) on its output, or escape the result of
formatGrantApplicationAiReviewNotes(aiReview) prior to conversion; ensure the
sanitizer is applied server-side in the route handling (route.ts) and that only
safe tags/attributes are allowed.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against clearing notes when AI review is missing.

This path writes '' into notes when aiReview is undefined, which can erase existing notes for matched applications.

Proposed fix
-            notes: convertTextToNotesHTML(
-              formatGrantApplicationAiReviewNotes(aiReview),
-            ),
+            ...(aiReview
+              ? {
+                  notes: convertTextToNotesHTML(
+                    formatGrantApplicationAiReviewNotes(aiReview),
+                  ),
+                }
+              : {}),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/sponsor-dashboard/grant-application/ai/commit-reviewed/route.ts`
around lines 87 - 89, The code unconditionally writes notes:
convertTextToNotesHTML(formatGrantApplicationAiReviewNotes(aiReview)) which
becomes an empty string when aiReview is undefined and can wipe existing notes;
change the update logic in route.ts so the notes field is only set when aiReview
is present (e.g., conditionally include notes or set notes to undefined) —
reference the aiReview variable and the functions
formatGrantApplicationAiReviewNotes and convertTextToNotesHTML and ensure
existing notes are preserved by omitting the notes property when aiReview is
falsy.

Comment on lines +83 to +110
recommendation?: 'Accept' | 'Reject' | 'Needs_Review';
confidence?: 'low' | 'medium' | 'high';
reasoning: string;
decisionReason?: string;
risks?: string[];
colosseum?: {
enabled: boolean;
summary: string;
error?: string;
};
solanaTechnical?: {
isSolanaTechnical: boolean;
capabilityAreas: string[];
technicalCoherence: 'low' | 'medium' | 'high' | 'not_applicable';
missingImplementationDetails: string[];
reviewerRisks: string[];
summary: string;
};
totalCostInUSD: number;
totalTimeinMs: number;
shortNote: string;
scores?: {
pow: number;
activity: number;
core: number;
feasibility: number;
impact: number;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify optional properties in changed EvaluationResult block
rg -n "recommendation\\?:|confidence\\?:|decisionReason\\?:|risks\\?:|colosseum\\?:|solanaTechnical\\?:|scores\\?:" src/features/grants/types/index.ts

Repository: SuperteamDAO/earn

Length of output: 280


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '60,130p' src/features/grants/types/index.ts | cat -n

Repository: SuperteamDAO/earn

Length of output: 1779


🏁 Script executed:

#!/bin/bash
set -euo pipefail

nl -ba src/features/grants/types/index.ts | sed -n '70,120p'

Repository: SuperteamDAO/earn

Length of output: 104


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n src/features/grants/types/index.ts | sed -n '60,120p'

Repository: SuperteamDAO/earn

Length of output: 1779


Replace ?: optional properties with | undefined in EvaluationResult.

type EvaluationResult uses optional props (recommendation?, confidence?, decisionReason?, risks?, colosseum? incl. colosseum.error?, solanaTechnical? incl. ..., and scores?). Switch to property: T | undefined (and for nested optionals, e.g. error: string | undefined) to match the repo TS type-definition rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/grants/types/index.ts` around lines 83 - 110, The
EvaluationResult type uses optional properties (e.g. recommendation?,
confidence?, decisionReason?, risks?, colosseum?, colosseum.error?,
solanaTechnical? and its nested fields, and scores?) which must be converted to
explicit union-with-undefined form; update the EvaluationResult definition so
each optional becomes property: T | undefined (and for arrays or nested fields
use string[] | undefined or error: string | undefined etc.), preserving the
existing types/values but replacing every "?" optional marker with the explicit
"| undefined" form for top-level and nested fields like colosseum.error and
solanaTechnical.* and scores.

Comment on lines +5 to +76
const labelText = (value?: string) => value?.replace(/_/g, ' ');

const compactText = (value?: string) =>
value
?.replace(/\s+/g, ' ')
.replace(/^[-*•]\s*/, '')
.trim();

const normalizeNote = (value?: string) =>
value
?.split('\n')
.map((line) => line.replace(/\s+/g, ' ').trim())
.filter(Boolean)
.join('\n')
.trim();

const truncate = (value: string, maxLength: number) => {
if (value.length <= maxLength) return value;
return `${value.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
};

const addMissingRisksToShortNote = (note: string, risks?: string[]) => {
const compactRisks = risks?.filter(Boolean);
if (!compactRisks?.length) return note;

const lowerNote = note.toLowerCase();
const alreadyIncluded = compactRisks.every((risk) =>
lowerNote.includes(risk.toLowerCase()),
);

if (alreadyIncluded) return note;

const riskText = `Calibrated risk: ${compactRisks.join(' ')}`;
const lines = note.split('\n');
const reviewerCheckIndex = lines.findIndex((line) =>
line.toLowerCase().startsWith('reviewer check:'),
);

if (reviewerCheckIndex >= 0) {
lines[reviewerCheckIndex] = (lines[reviewerCheckIndex] ?? '').replace(
/^reviewer check:\s*/i,
`Reviewer check: ${riskText} `,
);
return lines.join('\n');
}

return `${note}\nReviewer check: ${riskText}`;
};

const appendBullet = (
lines: string[],
title: string,
value?: string,
maxLength = 220,
) => {
const text = compactText(value);
if (text) lines.push(`• ${title}: ${truncate(text, maxLength)}`);
};

const appendListBullet = (
lines: string[],
title: string,
values?: string[],
maxItems = 2,
) => {
const items = values?.map(compactText).filter(Boolean) as
| string[]
| undefined;
if (items?.length) {
lines.push(`• ${title}: ${items.slice(0, maxItems).join('; ')}`);
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify top-level helper declarations without explicit return types in this file
rg -n "const (labelText|compactText|normalizeNote|truncate|addMissingRisksToShortNote|appendBullet|appendListBullet) = \\(" src/features/grants/utils/formatGrantApplicationAiReviewNotes.ts

Repository: SuperteamDAO/earn

Length of output: 407


Add explicit return types to top-level helpers in src/features/grants/utils/formatGrantApplicationAiReviewNotes.ts.

labelText, compactText, normalizeNote, truncate, addMissingRisksToShortNote, appendBullet, and appendListBullet are top-level const arrow functions without return type annotations; add explicit return types to each.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/grants/utils/formatGrantApplicationAiReviewNotes.ts` around
lines 5 - 76, Add explicit return type annotations to the top-level helper arrow
functions: labelText should be (value?: string) => string | undefined,
compactText should be (value?: string) => string | undefined, normalizeNote
should be (value?: string) => string | undefined, truncate should be (value:
string, maxLength: number) => string, addMissingRisksToShortNote should be
(note: string, risks?: string[]) => string, and both appendBullet and
appendListBullet should be typed as returning void; update the const
declarations for labelText, compactText, normalizeNote, truncate,
addMissingRisksToShortNote, appendBullet, and appendListBullet with these
explicit return types so TypeScript enforces the intended signatures.

Comment on lines +108 to +113
useEffect(() => {
if (previousApplicationIdRef.current !== selectedApplication?.id) {
previousApplicationIdRef.current = selectedApplication?.id;
setNotes(normalizeNotesForEditor(selectedApplication?.notes));
}
if (value.length <= MAX_CHARACTERS) {
}, [selectedApplication?.id, selectedApplication?.notes]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether selected grant application notes are updated for the same id in other flows.
# Expected: find same-id mutations to selectedGrantApplicationAtom that can change `notes`.
rg -n -C3 "setSelectedApplication\\(|selectedGrantApplicationAtom|getTextCharacterCount\\(|notes:" --type=tsx --type=ts

Repository: SuperteamDAO/earn

Length of output: 89


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the effect in the reviewed component
sed -n '80,150p' src/features/sponsor-dashboard/components/GrantApplications/Notes.tsx

# 2) Locate the state/atom backing `selectedApplication` and any setters
rg -n --glob='*.ts' --glob='*.tsx' -S "selectedGrantApplicationAtom|selectedApplicationAtom|setSelectedApplication|selectedApplication" src/features/sponsor-dashboard/components/GrantApplications
rg -n --glob='*.ts' --glob='*.tsx' -S "selectedGrantApplicationAtom|setSelectedApplication" src/features/sponsor-dashboard

# 3) Find other places that update notes on the same application record
rg -n --glob='*.ts' --glob='*.tsx' -S "notes\\s*:" src/features/sponsor-dashboard
rg -n --glob='*.ts' --glob='*.tsx' -S "set.*notes|update.*notes|normalizeNotesForEditor\\(" src/features/sponsor-dashboard

# 4) Search broadly for updates to the selected grant application object (including notes)
rg -n --glob='*.ts' --glob='*.tsx' -S "(selectedGrantApplication|selectedApplication)\\s*.*notes" src/features/sponsor-dashboard

# 5) If atoms live elsewhere, broaden search (still scoped)
rg -n --glob='*.ts' --glob='*.tsx' -S "selectedGrantApplicationAtom|setSelectedApplication|GrantApplication.*Atom|selectedApplication" src

Repository: SuperteamDAO/earn

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact useEffect + surrounding state/refs
sed -n '1,170p' src/features/sponsor-dashboard/components/GrantApplications/Notes.tsx

# Show selectedGrantApplicationAtom implementation
sed -n '1,140p' src/features/sponsor-dashboard/atoms/index.ts

# Show commit reviews mutation where notes may be updated for an existing application id
sed -n '1,140p' src/features/sponsor-dashboard/mutations/useCommitReviewsGrantApplications.ts

# Show any other mutations/components that update selected application object
rg -n --glob='*.ts' --glob='*.tsx' -S "setSelectedApplication\\(" src/features/sponsor-dashboard/mutations src/features/sponsor-dashboard/components/GrantApplications

# Specifically show where notes are updated in GrantApplications area
rg -n --glob='*.ts' --glob='*.tsx' -S "notes\\s*:" src/features/sponsor-dashboard/mutations/useCommitReviewsGrantApplications.ts src/features/sponsor-dashboard/components/GrantApplications/Notes.tsx src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx

Repository: SuperteamDAO/earn

Length of output: 12270


Sync Notes editor when selectedApplication.notes changes for the same application ID.

Notes.tsx only updates local notes when previousApplicationIdRef.current !== selectedApplication?.id (Lines 109-113). However useCommitReviewsGrantApplications updates selectedGrantApplicationAtom by replacing notes while keeping the same application object shape (so id can remain unchanged), causing the editor to display stale notes.

Proposed fix
-  const previousApplicationIdRef = useRef<string | undefined>(
-    selectedApplication?.id,
-  );
@@
   useEffect(() => {
-    if (previousApplicationIdRef.current !== selectedApplication?.id) {
-      previousApplicationIdRef.current = selectedApplication?.id;
-      setNotes(normalizeNotesForEditor(selectedApplication?.notes));
-    }
-  }, [selectedApplication?.id, selectedApplication?.notes]);
+    const normalized = normalizeNotesForEditor(selectedApplication?.notes) || '';
+    setNotes((prev) => (prev === normalized ? prev : normalized));
+  }, [selectedApplication?.id, selectedApplication?.notes]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/sponsor-dashboard/components/GrantApplications/Notes.tsx` around
lines 108 - 113, The effect in Notes.tsx only updates local notes when
previousApplicationIdRef.current !== selectedApplication?.id, so when the atom
replaces selectedApplication.notes but keeps the same id the editor stays stale;
modify the useEffect (the one that calls
setNotes(normalizeNotesForEditor(selectedApplication?.notes))) to also detect
changes to notes by adding a previousNotesRef (or comparing
previousNotesRef.current) and update when previousNotesRef.current !==
selectedApplication?.notes (updating previousNotesRef.current after setNotes),
so changes to selectedApplication.notes will sync into the editor even if id is
unchanged.

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