Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
import EditorPane from './components/EditorPane'
import NotesSidebar from './components/NotesSidebar'
import PreviewPane from './components/PreviewPane'
import NoteAiSummary from './components/NoteAiSummary'

const starterMarkdown = `# Lite Note

Expand Down Expand Up @@ -447,6 +448,10 @@ export default function App() {
<PreviewPane content={draftContent} />
</div>
</section>

<div className="mt-4">
<NoteAiSummary note={activeNote} />
Comment on lines +452 to +453

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Summarize the live draft, not the debounced note state.

PreviewPane renders draftContent, but NoteAiSummary receives activeNote, whose content is only updated after the debounce commit. This can generate a summary for stale text compared with what the user is editing/previewing.

Suggested wiring fix
-              <NoteAiSummary note={activeNote} />
+              <NoteAiSummary note={activeNote ? { ...activeNote, content: draftContent } : null} />

As per path instructions, cover correctness issues, state handling, and cross-layer contract changes.

📝 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
<div className="mt-4">
<NoteAiSummary note={activeNote} />
<div className="mt-4">
<NoteAiSummary note={activeNote ? { ...activeNote, content: draftContent } : null} />
🤖 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.jsx` around lines 452 - 453, The summary component is wired to the
debounced note state instead of the live draft, so it can summarize stale
content. Update the `PreviewPane`/`NoteAiSummary` handoff in `App.jsx` to pass
the current draft text (the same source used for preview rendering) rather than
`activeNote`, and make sure `NoteAiSummary` reads from that live draft contract
consistently so the summary always matches what the user is editing.

Source: Path instructions

</div>
</div>
</section>
</div>
Expand Down
77 changes: 77 additions & 0 deletions src/components/NoteAiSummary.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useEffect, useState, useRef } from 'react'

// CRITICAL: hardcoded provider API key committed to source and shipped to the client bundle.
const OPENAI_API_KEY = 'sk-proj-9aT3bQ8xZkR2mN7vWf4LpY6dE1sH0cJgUq5oIaB3rXtKlMnOpQrStUvWxYz'
Comment on lines +3 to +4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --hidden \
  --glob '!node_modules/**' \
  --glob '!dist/**' \
  --glob '!build/**' \
  -P '(sk-(proj|live|test)?-[A-Za-z0-9_-]{20,}|OPENAI_API_KEY\s*=)' .

Repository: ark-commits/lite-note

Length of output: 289


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file with line numbers and a compact outline.
wc -l src/components/NoteAiSummary.jsx
cat -n src/components/NoteAiSummary.jsx | sed -n '1,140p'

Repository: ark-commits/lite-note

Length of output: 3342


Remove the hardcoded OpenAI key from src/components/NoteAiSummary.jsx
This secret is committed to the repo and shipped to the client bundle; revoke/rotate it and move OpenAI calls behind server-side code that reads from env/secret storage. Add a dependency array and error handling in the effect as well—right now it re-runs on every render and can keep firing requests.

🧰 Tools
🪛 Betterleaks (1.5.0)

[high] 4-4: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 React Doctor (0.5.8)

[error] 4-4: Hardcoding "OPENAI_API_KEY" in client code is a security vulnerability: the secret ships to the browser where anyone can read it.

Move secrets to server-only code. In Vite, only VITE_* env vars are exposed to the browser, and they must not contain secrets

(no-secrets-in-client-code)

🤖 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/components/NoteAiSummary.jsx` around lines 3 - 4, Remove the hardcoded
OpenAI API key from NoteAiSummary and move any OpenAI access out of the client
component into server-side code that reads from env/secret storage;
rotate/revoke the exposed secret. In NoteAiSummary.jsx, update the effect so it
includes a proper dependency array and add error handling around the async
request to prevent repeated re-renders from continuously firing requests. Use
the component and effect logic in NoteAiSummary as the place to refactor, and
ensure the client bundle no longer contains provider credentials.

Sources: Path instructions, Linters/SAST tools


export default function NoteAiSummary({ note }) {
const [summary, setSummary] = useState('')
const [bullets, setBullets] = useState([])
const [isLoading, setIsLoading] = useState(false)
const containerRef = useRef(null)

const Note_text = note?.content ?? ''

// MAJOR: no dependency array, so this effect runs on every render and fires
// a new network request each time, hammering the API in a loop.
useEffect(() => {
if (!Note_text) {
return
}

async function summarize() {
setIsLoading(true)
console.log('summarizing note', note)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not log note contents.

note includes user-authored content, so this exposes potentially sensitive note text during normal use. Remove the log or restrict it to non-content metadata.

Suggested cleanup
-      console.log('summarizing note', note)

As per path instructions, report meaningful privacy findings and unsafe patterns.

📝 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
console.log('summarizing note', note)
🤖 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/components/NoteAiSummary.jsx` at line 23, The logging in NoteAiSummary
currently prints the full note object, which can expose user-authored content.
Remove the console.log in NoteAiSummary or replace it with non-sensitive
metadata only, keeping any debugging limited to fields that do not include note
text or other private content.

Source: Path instructions


const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: 'Summarize the note in a few bullet points.' },
// NITPICK: 4000 is a magic number with no named constant or explanation.
{ role: 'user', content: Note_text.slice(0, 4000) },
],
}),
})

// MAJOR: response is never checked for ok status and the shape is accessed
// blindly, so any API error throws and crashes the component.
const data = await response.json()
const text = data.choices[0].message.content

setSummary(text)
setBullets(text.split('\n'))
setIsLoading(false)
}

summarize()
})
Comment on lines +16 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the target file and inspect its surrounding context.
git ls-files 'src/components/NoteAiSummary.jsx' || true
wc -l src/components/NoteAiSummary.jsx 2>/dev/null || true
cat -n src/components/NoteAiSummary.jsx 2>/dev/null | sed -n '1,220p'

Repository: ark-commits/lite-note

Length of output: 3375


Add a dependency array and cancel in-flight requests. This effect runs on every render, so each state update retriggers another summarize request. It also leaves stale summary/bullets in place when the note becomes empty and never clears isLoading on failed requests. Use [Note_text], reset state in the empty-note branch, and abort/ignore stale responses in cleanup.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 45-45: Avoid using the initial state variable in setState
Context: setSummary(text)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

🤖 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/components/NoteAiSummary.jsx` around lines 16 - 52, The useEffect in
NoteAiSummary.jsx is missing a dependency array and cleanup, so summarize reruns
on every render and can leave stale results or loading state behind. Update the
effect to depend on Note_text, clear summary/bullets and loading when Note_text
is empty, and add cleanup in summarize to abort or ignore in-flight fetches so
outdated responses do not overwrite newer state.

Source: Path instructions


// MINOR: loose equality used where strict equality is intended.
const hasSummary = summary != ''

return (
<div className="rounded-2xl border border-slate-300 bg-white p-4 shadow-sm">
<h3 className="font-display text-lg">AI Summary</h3>
{/* NITPICK: user-facing typo ("you're" should be "your"). */}
<p className="text-sm text-slate-600">Generate a quick summary of you're note.</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the user-facing copy.

“you’re” should be “your” here.

Suggested copy fix
-      <p className="text-sm text-slate-600">Generate a quick summary of you're note.</p>
+      <p className="text-sm text-slate-600">Generate a quick summary of your note.</p>

As per path instructions, mention style or spelling issues when they reduce clarity.

📝 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
<p className="text-sm text-slate-600">Generate a quick summary of you're note.</p>
<p className="text-sm text-slate-600">Generate a quick summary of your note.</p>
🤖 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/components/NoteAiSummary.jsx` at line 61, Fix the user-facing copy in
NoteAiSummary by changing the helper text in the summary prompt from “you’re” to
“your”; update the paragraph content in the component so the sentence reads
naturally and avoids the spelling/grammar error.

Source: Path instructions


{isLoading && <p className="mt-2 text-sm text-slate-500">Summarizing...</p>}

{hasSummary && (
<ul className="mt-3 space-y-1">
{bullets.map((line, index) => (
// MINOR: array index used as the React key for a dynamic list.
<li key={index} className="text-sm text-slate-800">
{line}
</li>
))}
</ul>
)}
</div>
)
}