Skip to content

Initial MVP: Local-First Smart Notes with RAG, Summarization, and Electron#38

Open
KRISHNPRIY2820 wants to merge 1 commit intoAOSSIE-Org:mainfrom
KRISHNPRIY2820:main
Open

Initial MVP: Local-First Smart Notes with RAG, Summarization, and Electron#38
KRISHNPRIY2820 wants to merge 1 commit intoAOSSIE-Org:mainfrom
KRISHNPRIY2820:main

Conversation

@KRISHNPRIY2820
Copy link
Copy Markdown

@KRISHNPRIY2820 KRISHNPRIY2820 commented Mar 29, 2026

Closes #1


📝 Description

Hi, I’ve been exploring the Smart Notes idea and built a small MVP to better understand the problem space.

This PR introduces an initial implementation of a local-first AI-powered note system that focuses on semantic search, contextual question answering, and automatic linking between notes.

The goal here is not to present a complete solution, but to explore a possible direction while keeping everything local and privacy-focused.


🔧 Changes Made

  • Added basic note creation and storage
  • Implemented semantic retrieval using embeddings
  • Built a simple Q&A system over notes (RAG-style)
  • Added note summarization
  • Implemented related note suggestions
  • Wrapped the app using Electron for a desktop experience
  • Updated README with MVP details

📷 Screenshots or Visual Changes

Screenshot 2026-03-30 034044 Screenshot 2026-03-30 034107 Screenshot 2026-03-30 034127 Screenshot 2026-03-30 034328

🤝 Collaboration

None


✅ Checklist

  • I have read the contributing guidelines
  • I have added necessary documentation
  • The changes are tested locally
  • I will share this PR in the Discord server

⚠️ Notes

This is an exploratory MVP to understand the Smart Notes idea better.

I would really appreciate feedback on:

  • Whether this direction aligns with the project goals
  • What I should improve next

Summary by CodeRabbit

  • New Features

    • Launched Smart Notes MVP—a local, privacy-first note-taking app with on-device AI capabilities
    • Added note creation and storage functionality
    • Added Q&A feature to answer questions based on note content
    • Added note summarization feature
    • Added related-note suggestion functionality
    • Launched Electron desktop wrapper and web interface with integrated forms
  • Documentation

    • Added comprehensive project README with setup instructions, feature overview, and contribution guidelines

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Walkthrough

The PR introduces an MVP for "Smart Notes," a local AI note-taking application combining a Flask backend with RAG capabilities (question answering, note summarization, related note suggestions), an Electron desktop frontend, embedding-based semantic search, and a web UI built with HTML/CSS.

Changes

Cohort / File(s) Summary
Documentation
MVP_README.md
Project overview, feature description, tech stack specification (Python/Flask, HuggingFace, Transformers, Electron), setup instructions, limitations, and contribution guidance with GPL v3 license.
Flask Backend
app.py
Entry point defining a single route (/) handling GET/POST requests. Syncs embeddings on every request and branches on form submissions: note creates a note and suggests related links, question generates an answer via QA logic, summarize produces a summary. Renders responses into index.html.
Electron Frontend
frontend/main.js, frontend/package.json
Electron setup: main.js creates a BrowserWindow (1200×800) loading http://127.0.0.1:5000, and package.json declares electron dependency (^41.1.0) with a start script.
Embedding & Linking Logic
logic/context.py, logic/linking.py
context.py loads SentenceTransformer (all-MiniLM-L6-v2), maintains global note_embeddings, and provides embedding, cosine similarity, and top-3 retrieval functions. linking.py wraps retrieval as suggest(note, notes).
Note Management
logic/notes.py
In-memory note storage with add_note(text) (appends and syncs embeddings) and get_notes() (returns current list).
AI Inference Modules
logic/qa.py, logic/summarizer.py
qa.py loads google/flan-t5-base and provides answer(query, notes) with context-aware token generation (max 120 tokens, temp 0.2). summarizer.py loads the same model and provides summarize_notes(notes) deduplicating input and generating one-sentence summaries (max 30 tokens, temp 0.2).
UI & Styling
templates/index.html, static/style.css
HTML template with three POST forms (add note, ask question, summarize) and dynamic sections for notes and related notes lists. CSS stylesheet with gradient backgrounds, flexbox layouts, card styling, form element customization, and responsive container constraints.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Frontend as Electron UI
    participant Server as Flask App
    participant Notes as Note Storage
    participant Embeddings as Embedding Engine
    participant QA as QA Model
    participant Related as Linking Logic

    User->>Frontend: Enter question
    Frontend->>Server: POST question
    Server->>Notes: get_notes()
    Notes-->>Server: notes list
    Server->>Embeddings: update_embeddings(notes)
    Embeddings->>Embeddings: Load embeddings
    Server->>QA: answer(question, notes)
    QA->>Embeddings: find_relevant(question)
    Embeddings-->>QA: relevant notes
    QA->>QA: Generate response
    QA-->>Server: answer text
    Server->>Frontend: Render response
    Frontend-->>User: Display answer

    User->>Frontend: Add note
    Frontend->>Server: POST note
    Server->>Notes: add_note(text)
    Notes->>Embeddings: update_embeddings()
    Embeddings-->>Notes: embeddings updated
    Server->>Related: suggest(note, notes)
    Related->>Embeddings: find_relevant(note)
    Embeddings-->>Related: related notes
    Related-->>Server: related list
    Server->>Frontend: Render with links
    Frontend-->>User: Display note + related
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

Python Lang, Typescript Lang, Documentation

Poem

🐰 Hops through notes with RAG so bright,
Local embeddings, no cloud in sight,
Questions answered, summaries made—
A privacy-first note crusade!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing an initial MVP for a local-first AI notes app with RAG, summarization, and an Electron desktop wrapper.
Linked Issues check ✅ Passed The PR successfully fulfills issue #1 by transferring the codebase for a single-subject UI demonstration, including all necessary application logic, frontend components, and documentation.
Out of Scope Changes check ✅ Passed All changes are in scope: documentation, backend Flask app, embedding/RAG logic, Q&A/summarization, Electron frontend, and UI styling directly support the MVP implementation objective.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an exploratory “Smart Notes” MVP that runs locally: a Flask UI for creating notes plus embedding-based retrieval, FLAN-T5-based Q&A and summarization, and an Electron wrapper for a desktop experience.

Changes:

  • Added Flask template/CSS UI for adding notes, asking questions, summarizing, and showing related notes.
  • Implemented local semantic retrieval (SentenceTransformer embeddings) plus FLAN-T5 Q&A and summarization helpers.
  • Added an Electron “shell” that loads the local Flask server, plus dependency manifests.

Reviewed changes

Copilot reviewed 11 out of 22 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
app.py Flask route wiring for notes, Q&A, summarization, and related-note suggestions
logic/context.py Embedding model load + cosine similarity + relevant-note retrieval
logic/notes.py In-memory note list and embedding refresh on insert
logic/qa.py FLAN-T5 prompt + generation for question answering over retrieved notes
logic/summarizer.py FLAN-T5 prompt + generation for note summarization
logic/linking.py Related-note suggestions via the retrieval helper
templates/index.html UI template for notes, Q&A, summary, and links
static/style.css Styling for the MVP UI
static/images/stability.svg Added image asset
requirements.txt Python dependencies for Flask + transformers stack
frontend/main.js Electron window that loads the local Flask URL
frontend/package.json Electron app manifest
frontend/package-lock.json Locked NPM dependency tree
logic/__pycache__/context.cpython-312.pyc Compiled Python artifact (should not be committed)
logic/__pycache__/linking.cpython-312.pyc Compiled Python artifact (should not be committed)
logic/__pycache__/notes.cpython-312.pyc Compiled Python artifact (should not be committed)
logic/__pycache__/qa.cpython-312.pyc Compiled Python artifact (should not be committed)
logic/__pycache__/summarizer.cpython-312.pyc Compiled Python artifact (should not be committed)
MVP_README.md MVP documentation and run instructions
Files not reviewed (1)
  • frontend/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +54 to +55
if __name__ == "__main__":
app.run(debug=True) No newline at end of file
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

app.run(debug=True) enables the Flask debugger and interactive console, which should not be committed as the default for anything beyond local development. Please default to debug=False (or read from an environment variable) and document how to enable debug mode locally.

Copilot uses AI. Check for mistakes.
Comment on lines +2 to +9

function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
});

win.loadURL('http://127.0.0.1:5000');
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

BrowserWindow is created without explicit security-related webPreferences. For Electron apps, it's recommended to explicitly set contextIsolation, sandbox, and nodeIntegration (disabled) and to restrict navigation/openExternal behavior to reduce risk if any content is compromised. Please harden the BrowserWindow configuration accordingly.

Suggested change
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
});
win.loadURL('http://127.0.0.1:5000');
const { shell } = require('electron');
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
const appUrl = 'http://127.0.0.1:5000';
win.webContents.on('will-navigate', (event, url) => {
if (!url.startsWith(appUrl)) {
event.preventDefault();
shell.openExternal(url);
}
});
win.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith(appUrl)) {
return { action: 'allow' };
}
shell.openExternal(url);
return { action: 'deny' };
});
win.loadURL(appUrl);

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +21
# Ensure embeddings are synced
update_embeddings(notes)

Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

update_embeddings(notes) is called on every request, even for GETs and even though add_note() already updates embeddings on note insertion. Since embedding generation is expensive, consider only syncing embeddings when notes change (or only on first request/startup) to avoid unnecessary recomputation.

Suggested change
# Ensure embeddings are synced
update_embeddings(notes)

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +34
<form method="POST">
<textarea name="note" placeholder="Write your note here..."></textarea>
<button type="submit">Save Note</button>
</form>
</div>

<!-- ASK QUESTION -->
<div class="section">
<h2>Ask Question</h2>
<form method="POST">
<input type="text" name="question" placeholder="Ask something about your notes...">
<button type="submit">Ask</button>
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The textarea and question input rely on placeholders only; there are no associated elements. Placeholders are not a reliable substitute for labels for accessibility. Please add (with matching id) or aria-label/aria-labelledby so the fields are announced correctly by assistive tech.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +29
for i, note_embedding in enumerate(note_embeddings):
score = cosine_similarity(query_embedding, note_embedding)
scores.append((score, notes[i]))
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

find_relevant() indexes into the passed-in notes list using indices from the global note_embeddings list. If update_embeddings() was last called with a different notes list/length, this can raise IndexError or return mismatched notes. Consider making find_relevant() derive embeddings from the provided notes (or accept the embeddings list explicitly) and/or assert lengths match before indexing.

Suggested change
for i, note_embedding in enumerate(note_embeddings):
score = cosine_similarity(query_embedding, note_embedding)
scores.append((score, notes[i]))
# Derive embeddings from the provided notes to avoid mismatch with global state
note_embeddings_local = [embed(note) for note in notes]
for note, note_embedding in zip(notes, note_embeddings_local):
score = cosine_similarity(query_embedding, note_embedding)
scores.append((score, note))

Copilot uses AI. Check for mistakes.

def add_note(text):
notes.append(text)
update_embeddings(notes)
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

add_note() recomputes embeddings for the entire notes list on every insert, which becomes O(n^2) as notes grow and can make the UI feel slow. Consider updating embeddings incrementally for just the new note (append its embedding) or moving to a persistent vector store/index that supports incremental updates.

Suggested change
update_embeddings(notes)
update_embeddings(text)

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +8
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM

tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-base")
model = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-base")

Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The flan-t5 model/tokenizer are loaded at import time. This makes app startup slow, increases memory use, and can break environments without model cache/network access during import. Consider lazy-loading/caching the model inside the function (or via a shared singleton module) and running generation under inference/no-grad with model.eval() for faster, deterministic inference.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +17
<img src="{{ url_for('static', filename='images/logo-icon.svg') }}" class="icon">
<h1>Smart Notes</h1>
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The decorative images are missing alt text, which hurts screen-reader users. Please add appropriate alt attributes (empty alt="" for purely decorative images, descriptive alt for meaningful logos) and consider adding elements or aria-labels for the textarea/input fields.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +6
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM

# reuse same model
tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-base")
model = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-base")

Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This module loads flan-t5 at import time and duplicates the same model load done in logic/qa.py, which increases startup time and memory footprint. Consider sharing a single cached model/tokenizer (e.g., a dedicated model_loader module) and performing generation under inference/no-grad with model.eval().

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@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: 28

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app.py`:
- Line 20: The call to update_embeddings in app.py is redundant because add_note
in logic/notes.py already calls update_embeddings; remove the
update_embeddings(...) invocation(s) in app.py (including the ones corresponding
to lines 28-31) so embeddings are only recomputed inside add_note (or other
note-changing functions) to avoid duplicate O(n) work; if any other route in
app.py must trigger embeddings, replace the direct call with a clear delegate to
the logic layer (e.g., call a single exported function in logic/notes.py) or add
a guard to avoid double computation.
- Around line 54-55: The startup currently forces Flask into debug mode via the
hardcoded debug=True in the "__main__" block (app.run), which must be removed;
change the app.run call to read the debug flag from configuration or environment
(e.g. app.config['DEBUG'] or an env var like FLASK_DEBUG) and pass that value to
app.run (or omit the debug argument entirely and ensure config sets DEBUG), so
debug is enabled only when explicitly configured for development rather than
always.

In `@frontend/main.js`:
- Around line 3-10: The BrowserWindow created in createWindow is missing
explicit security webPreferences; update the BrowserWindow options in
createWindow to include secure settings such as webPreferences: {
nodeIntegration: false, contextIsolation: true, enableRemoteModule: false,
sandbox: true, nativeWindowOpen: false, webSecurity: true,
allowRunningInsecureContent: false } and ensure any devtools enablement is
explicit (only enable when needed) before calling
win.loadURL('http://127.0.0.1:5000').
- Line 12: The app currently only calls app.whenReady().then(createWindow) and
lacks macOS lifecycle handling; add standard Electron event handlers for
'window-all-closed' (call app.quit() on non-darwin platforms) and for 'activate'
(recreate the window by calling createWindow if no BrowserWindow exists) so the
createWindow function is used on activate and the app stays running on macOS
when all windows are closed; reference the existing createWindow and the
app.whenReady().then(createWindow) call to insert the 'window-all-closed' and
'activate' listeners.
- Line 9: The call to win.loadURL('http://127.0.0.1:5000') has no handling for a
down Flask backend; update the startup to detect load failures (use
win.webContents.on('did-fail-load', ...) and/or the Promise returned by
win.loadURL) and implement fallback/retry behavior: attempt a few retries with
backoff to the same URL, and if still failing load a local error page (via
win.loadFile or serve an inline HTML) that explains the backend is unreachable
and provides a retry button; reference the BrowserWindow instance variable win,
its webContents event 'did-fail-load', and the win.loadURL call when
implementing these changes.

In `@frontend/package.json`:
- Around line 6-8: The JSON in the "scripts" block has inconsistent indentation;
fix the "scripts" object so keys and braces use consistent 2-space indentation
(align the "start" key and the closing brace with 2 spaces), ensuring valid,
uniformly formatted JSON for the "scripts" object and preserving the existing
"start" value ("electron .").

In `@logic/context.py`:
- Around line 16-18: The function update_embeddings currently mutates the
module-level variable note_embeddings via a global statement; replace this
global pattern by encapsulating state in a small container (for example an
EmbeddingStore class or a module-level dict/object) that holds note_embeddings
and exposes an update_embeddings method which calls embed for each note; remove
the global note_embeddings usage inside update_embeddings, instantiate the store
(or export the container) and update all call sites to use
store.update_embeddings(notes) or read store.note_embeddings so state is no
longer mutated via a global statement.
- Around line 13-14: The cosine_similarity function can divide by zero when
np.linalg.norm(a) or np.linalg.norm(b) is 0; modify cosine_similarity to compute
na = np.linalg.norm(a) and nb = np.linalg.norm(b) and if either is 0 return 0.0
(or use a small epsilon in the denominator) so you never produce NaN; update the
function cosine_similarity(a, b) accordingly and ensure callers that expect
floats continue to work with the 0.0 fallback.
- Around line 20-29: In find_relevant, avoid indexing mismatch by not iterating
over the global note_embeddings while indexing into the notes parameter; either
validate that len(notes) == len(note_embeddings) at the top or change the loop
to iterate over pairs (e.g., zip(notes, note_embeddings)) or accept embeddings
alongside notes as a parameter; update references to note_embeddings and notes
in the function (and any callers) to use the paired iteration or the new
parameter, and raise/log a clear error if lengths differ when performing the
length-check approach.

In `@logic/linking.py`:
- Around line 3-4: The suggest function currently returns results that can
include the input note itself; update suggest(note, notes) so it excludes the
input note before calling or from the results of find_relevant: either filter
the notes list by identity or unique identifier (e.g., note.id or note.title) to
remove the same note object, then call find_relevant(filtered_notes) (or remove
any item equal to note from the returned list) to ensure the input note is not
suggested as a related note.

In `@logic/notes.py`:
- Around line 9-10: get_notes currently returns the internal mutable list notes
which lets callers mutate shared state and desynchronize note_embeddings; change
get_notes to return an immutable or defensive copy (e.g., return notes.copy() or
tuple(notes)) so external code cannot modify the internal notes list, and add a
brief comment in get_notes explaining the defensive copy to prevent
note_embeddings from getting out of sync.
- Around line 5-7: The add_note function currently accepts any input and passes
it to update_embeddings; validate the input in add_note by trimming whitespace
(e.g., text = text.strip()), reject empty or non-string inputs (raise ValueError
or return a clear error/False), and only append and call
update_embeddings(notes) when the cleaned text is non-empty; reference the
add_note function and the update_embeddings(notes) call so you modify the input
handling before notes.append(text).

In `@logic/qa.py`:
- Around line 6-7: qa.py and summarizer.py each instantiate AutoTokenizer and
AutoModelForSeq2SeqLM for "google/flan-t5-base", causing duplicate ~1GB model
loads; to fix, create a single shared module (e.g., logic.model) that constructs
and exposes tokenizer and model once (symbols: tokenizer, model) and replace the
local loads in logic/qa.py and logic/summarizer.py with "from logic.model import
tokenizer, model", removing their AutoTokenizer.from_pretrained and
AutoModelForSeq2SeqLM.from_pretrained calls so only the shared instances are
loaded.
- Around line 1-2: Remove the unnecessary leading blank lines at the top of
qa.py so the file begins immediately with the first import statement (i.e.,
delete the empty lines before the import block) to follow standard file
formatting and avoid stray whitespace before module-level imports.
- Around line 35-39: The generation call in outputs = model.generate(**inputs,
max_new_tokens=120, temperature=0.2) is using temperature but not sampling;
update the call to enable sampling by adding do_sample=True so temperature takes
effect (i.e., modify the model.generate invocation to include do_sample=True
alongside max_new_tokens and temperature).

In `@logic/summarizer.py`:
- Around line 11-12: The deduplication uses set(notes) which loses original
order; change the dedupe to preserve the original note order before joining into
context (e.g., build unique_notes from notes while preserving first occurrences
using dict.fromkeys or a simple seen-set filter) so that unique_notes and the
resulting context remain deterministic; update the lines assigning unique_notes
and context accordingly where unique_notes, notes, and context are used.
- Around line 28-32: The call to model.generate (the outputs =
model.generate(...) invocation) sets temperature but doesn't enable sampling, so
add do_sample=True to the generate kwargs so temperature is applied; update the
generate call where outputs is produced (using the inputs variable) to include
do_sample=True alongside max_new_tokens and temperature.
- Around line 4-5: The module currently calls AutoTokenizer.from_pretrained and
AutoModelForSeq2SeqLM.from_pretrained at import time (tokenizer, model) which
blocks startup and hard-fails; refactor by replacing the eager variables with
lazy, cached loader functions (e.g., get_tokenizer() and get_model()) that load
on first call, wrap the from_pretrained calls in try/except to log and surface
errors, and cache the loaded instances (use a simple module-level None check or
functools.lru_cache) so subsequent calls return the same object; update any code
that referenced tokenizer or model to call these loader functions instead.

In `@MVP_README.md`:
- Around line 71-109: Update the fenced code blocks in the README to include
language specifiers for syntax highlighting (e.g., change the triple-backtick
blocks that contain the git clone command, the virtualenv/activation commands,
the pip install line, the python app.py run command, the URL block, and the
Electron frontend commands to use appropriate languages like bash or shell for
shell commands and possibly http for the URL); ensure every fenced block in
MVP_README.md that shows commands or snippets starts with ```bash or ```shell
(or another suitable language tag) to satisfy MD040.
- Around line 71-74: The README has a directory name mismatch: the clone command
uses smart-notes-mvp while the following cd uses smart-notes; update the cd
command in MVP_README.md to "cd smart-notes-mvp" so the two commands match
(adjust the line containing "cd smart-notes" to "cd smart-notes-mvp" next to the
existing "git clone https://github.com/KRISHNPRIY2820/smart-notes-mvp.git"
command).
- Around line 78-81: The README currently shows a Windows-only activation
command ("venv\Scripts\activate"); update the instructions to be explicit and
cross-platform by keeping the Python venv creation step and adding separate
activation commands for Windows ("venv\\Scripts\\activate") and for macOS/Linux
("source venv/bin/activate"), and label each platform so users know which
activation command to run.

In `@static/style.css`:
- Around line 55-63: The textarea rule uses width: 100% with padding/border and
disables resizing; change the textarea CSS (selector "textarea") to use
box-sizing: border-box to prevent overflow from padding/border and restore user
resize by allowing at least vertical resizing (e.g., remove "resize: none" or
replace with "resize: vertical") so users can resize height while preserving
layout.
- Around line 75-88: Add explicit keyboard focus-visible styles for interactive
controls: define a :focus-visible rule for button, input, and textarea (in
addition to existing button:hover) that applies a high-contrast outline or
box-shadow, preserves existing border-radius, and ensures the focus indicator is
visible against the current gradients (e.g., a 3px solid or 0 0 0 3px box-shadow
using an accessible contrast color). Update the selectors (button:focus-visible,
input:focus-visible, textarea:focus-visible) and ensure focus styles do not rely
on :hover, do not remove focus outlines, and are consistent with the existing UI
tokens so keyboard users have a clear, accessible indicator.

In `@templates/index.html`:
- Around line 23-26: The POST forms in templates/index.html lack CSRF tokens;
update each form (the <form> elements around the note textarea and similar forms
at lines referenced) to include your framework's CSRF token (for Flask-WTF,
inject the token via the template — e.g. render or call {{ csrf_token() }} or
include form.csrf_token()) as a hidden field so the server can validate
requests; ensure the view handlers that render/validate the forms use Flask-WTF
Form classes or call validate_csrf() so the token is checked on submit.
- Line 17: Replace hardcoded user-visible strings in the template by loading
them from the i18n resource API and referencing keys instead of literals: remove
literal text like "Smart Notes", "Add Note", "No notes added yet." (and the
other listed occurrences) and replace each with the appropriate i18n lookup
(e.g., using your template's i18n helper or function) so the <h1> title,
buttons, and messages render via resource keys; ensure keys are unique and
meaningful (e.g., title.smartNotes, button.addNote, message.noNotes) and update
any template bindings or attributes to use the i18n function so strings are
externalized for translation.
- Line 16: The <img> element that uses src="{{ url_for('static',
filename='images/logo-icon.svg') }}" and class="icon" is missing an alt
attribute; update that <img> tag to include an appropriate alt text (e.g.,
"Company logo" or a brief descriptive string) or use alt="" if it is purely
decorative, ensuring the change appears on the <img> with class "icon" so screen
readers receive the correct description.
- Line 83: The AOSSIE logo image tag (<img ... class="aossie-logo">) is missing
an alt attribute; update the template image element (class "aossie-logo") to
include a descriptive alt text (e.g., alt="AOSSIE logo" or a context-appropriate
description) to improve accessibility.
- Around line 32-35: The input with name="question" in the form is missing an
associated label which breaks accessibility; fix by adding a <label> tied to the
input (give the input a unique id, e.g., id="question") and reference that id
from the label (use a visible label or a visually-hidden class for
screen-reader-only text), and add the recommended .visually-hidden CSS rule to
your stylesheet so the label is available to assistive tech without altering
layout.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ade606f2-5a73-4ee2-8646-8802c02404ed

📥 Commits

Reviewing files that changed from the base of the PR and between 4bb58bd and f577be5.

⛔ Files ignored due to path filters (10)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • logic/__pycache__/context.cpython-312.pyc is excluded by !**/*.pyc
  • logic/__pycache__/linking.cpython-312.pyc is excluded by !**/*.pyc
  • logic/__pycache__/notes.cpython-312.pyc is excluded by !**/*.pyc
  • logic/__pycache__/qa.cpython-312.pyc is excluded by !**/*.pyc
  • logic/__pycache__/summarizer.cpython-312.pyc is excluded by !**/*.pyc
  • static/images/aossie-logo.svg is excluded by !**/*.svg
  • static/images/logo-full.svg is excluded by !**/*.svg
  • static/images/logo-icon.svg is excluded by !**/*.svg
  • static/images/stability.svg is excluded by !**/*.svg
📒 Files selected for processing (12)
  • MVP_README.md
  • app.py
  • frontend/main.js
  • frontend/package.json
  • logic/context.py
  • logic/linking.py
  • logic/notes.py
  • logic/qa.py
  • logic/summarizer.py
  • requirements.txt
  • static/style.css
  • templates/index.html

notes = get_notes()

# Ensure embeddings are synced
update_embeddings(notes)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove redundant embedding recomputation on request entry.

Line 20 recomputes embeddings for every request, and the add-note flow recomputes again via logic/notes.py (add_noteupdate_embeddings). This doubles O(n) work on POST and grows poorly with note count.

Proposed fix
-    # Ensure embeddings are synced
-    update_embeddings(notes)
@@
         elif "question" in request.form and request.form["question"].strip():
             question = request.form["question"].strip()
+            update_embeddings(notes)
 
             answer_text = answer(question, notes)

Also applies to: 28-31

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

In `@app.py` at line 20, The call to update_embeddings in app.py is redundant
because add_note in logic/notes.py already calls update_embeddings; remove the
update_embeddings(...) invocation(s) in app.py (including the ones corresponding
to lines 28-31) so embeddings are only recomputed inside add_note (or other
note-changing functions) to avoid duplicate O(n) work; if any other route in
app.py must trigger embeddings, replace the direct call with a clear delegate to
the logic layer (e.g., call a single exported function in logic/notes.py) or add
a guard to avoid double computation.

Comment on lines +54 to +55
if __name__ == "__main__":
app.run(debug=True) No newline at end of file
Copy link
Copy Markdown

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
# Verify whether debug mode is hardcoded in Flask startup paths.
rg -nP --type=py -C2 'app\.run\([^)]*debug\s*=\s*True'

Repository: AOSSIE-Org/SmartNotes

Length of output: 146


Remove hardcoded debug=True from Flask startup.

Line 55 unconditionally enables debug mode, which exposes sensitive data if used outside local development.

Proposed fix
+import os
@@
 if __name__ == "__main__":
-    app.run(debug=True)
+    app.run(debug=os.getenv("FLASK_DEBUG") == "1")
📝 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
if __name__ == "__main__":
app.run(debug=True)
if __name__ == "__main__":
app.run(debug=os.getenv("FLASK_DEBUG") == "1")
🧰 Tools
🪛 ast-grep (0.41.1)

[warning] 54-54: Detected Flask app with debug=True. Do not deploy to production with this flag enabled as it will leak sensitive information. Instead, consider using Flask configuration variables or setting 'debug' using system environment variables.
Context: app.run(debug=True)
Note: [CWE-489] Active Debug Code. [REFERENCES]
- https://labs.detectify.com/2015/10/02/how-patreon-got-hacked-publicly-exposed-werkzeug-debugger/

(debug-enabled-python)

🪛 Ruff (0.15.7)

[error] 55-55: Use of debug=True in Flask app detected

(S201)

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

In `@app.py` around lines 54 - 55, The startup currently forces Flask into debug
mode via the hardcoded debug=True in the "__main__" block (app.run), which must
be removed; change the app.run call to read the debug flag from configuration or
environment (e.g. app.config['DEBUG'] or an env var like FLASK_DEBUG) and pass
that value to app.run (or omit the debug argument entirely and ensure config
sets DEBUG), so debug is enabled only when explicitly configured for development
rather than always.

Comment on lines +3 to +10
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
});

win.loadURL('http://127.0.0.1:5000');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add explicit security settings for BrowserWindow.

The BrowserWindow lacks explicit webPreferences security settings. While recent Electron defaults are secure (nodeIntegration: false), explicitly setting security options is a best practice and makes the security posture clear.

🔒 Proposed security hardening
 function createWindow() {
   const win = new BrowserWindow({
     width: 1200,
     height: 800,
+    webPreferences: {
+      nodeIntegration: false,
+      contextIsolation: true,
+    },
   });

   win.loadURL('http://127.0.0.1:5000');
 }
📝 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
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
});
win.loadURL('http://127.0.0.1:5000');
}
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
},
});
win.loadURL('http://127.0.0.1:5000');
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/main.js` around lines 3 - 10, The BrowserWindow created in
createWindow is missing explicit security webPreferences; update the
BrowserWindow options in createWindow to include secure settings such as
webPreferences: { nodeIntegration: false, contextIsolation: true,
enableRemoteModule: false, sandbox: true, nativeWindowOpen: false, webSecurity:
true, allowRunningInsecureContent: false } and ensure any devtools enablement is
explicit (only enable when needed) before calling
win.loadURL('http://127.0.0.1:5000').

height: 800,
});

win.loadURL('http://127.0.0.1:5000');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

No error handling if Flask backend is unavailable.

win.loadURL() will show an error page if the Flask server isn't running. Consider adding error handling to provide a better user experience or retry logic.

🔧 Proposed error handling
-  win.loadURL('http://127.0.0.1:5000');
+  win.loadURL('http://127.0.0.1:5000').catch((err) => {
+    console.error('Failed to load backend:', err);
+    // Optionally show a helpful error page or retry
+  });
📝 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
win.loadURL('http://127.0.0.1:5000');
win.loadURL('http://127.0.0.1:5000').catch((err) => {
console.error('Failed to load backend:', err);
// Optionally show a helpful error page or retry
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/main.js` at line 9, The call to win.loadURL('http://127.0.0.1:5000')
has no handling for a down Flask backend; update the startup to detect load
failures (use win.webContents.on('did-fail-load', ...) and/or the Promise
returned by win.loadURL) and implement fallback/retry behavior: attempt a few
retries with backoff to the same URL, and if still failing load a local error
page (via win.loadFile or serve an inline HTML) that explains the backend is
unreachable and provides a retry button; reference the BrowserWindow instance
variable win, its webContents event 'did-fail-load', and the win.loadURL call
when implementing these changes.

win.loadURL('http://127.0.0.1:5000');
}

app.whenReady().then(createWindow); No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Missing macOS app lifecycle handling.

On macOS, apps typically stay active even when all windows are closed. The standard Electron pattern handles window-all-closed and activate events for cross-platform behavior.

🍎 Proposed lifecycle handling
 app.whenReady().then(createWindow);
+
+app.on('window-all-closed', () => {
+  if (process.platform !== 'darwin') {
+    app.quit();
+  }
+});
+
+app.on('activate', () => {
+  if (BrowserWindow.getAllWindows().length === 0) {
+    createWindow();
+  }
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/main.js` at line 12, The app currently only calls
app.whenReady().then(createWindow) and lacks macOS lifecycle handling; add
standard Electron event handlers for 'window-all-closed' (call app.quit() on
non-darwin platforms) and for 'activate' (recreate the window by calling
createWindow if no BrowserWindow exists) so the createWindow function is used on
activate and the app stays running on macOS when all windows are closed;
reference the existing createWindow and the app.whenReady().then(createWindow)
call to insert the 'window-all-closed' and 'activate' listeners.


<!-- HEADER -->
<div class="header">
<img src="{{ url_for('static', filename='images/logo-icon.svg') }}" class="icon">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing alt attribute on logo image.

The <img> element lacks an alt attribute, which is required for accessibility. Screen readers need this to describe the image.

🔧 Proposed fix
-        <img src="{{ url_for('static', filename='images/logo-icon.svg') }}" class="icon">
+        <img src="{{ url_for('static', filename='images/logo-icon.svg') }}" class="icon" alt="Smart Notes logo">

As per coding guidelines: "Review the HTML code against the google html style guide" and Lighthouse best practices require alt attributes on images.

🧰 Tools
🪛 HTMLHint (1.9.2)

[warning] 16-16: An alt attribute must be present on elements.

(alt-require)

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

In `@templates/index.html` at line 16, The <img> element that uses src="{{
url_for('static', filename='images/logo-icon.svg') }}" and class="icon" is
missing an alt attribute; update that <img> tag to include an appropriate alt
text (e.g., "Company logo" or a brief descriptive string) or use alt="" if it is
purely decorative, ensuring the change appears on the <img> with class "icon" so
screen readers receive the correct description.

<!-- HEADER -->
<div class="header">
<img src="{{ url_for('static', filename='images/logo-icon.svg') }}" class="icon">
<h1>Smart Notes</h1>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

User-visible strings are hardcoded.

Per i18n guidelines, user-visible strings should be externalized to resource files. Currently strings like "Smart Notes", "Add Note", "No notes added yet." are hardcoded. Consider this for future internationalization support.

As per coding guidelines: "User-visible strings should be externalized to resource files (i18n)".

Also applies to: 22-22, 31-31, 43-43, 54-54, 62-62, 68-68, 76-76, 82-82

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

In `@templates/index.html` at line 17, Replace hardcoded user-visible strings in
the template by loading them from the i18n resource API and referencing keys
instead of literals: remove literal text like "Smart Notes", "Add Note", "No
notes added yet." (and the other listed occurrences) and replace each with the
appropriate i18n lookup (e.g., using your template's i18n helper or function) so
the <h1> title, buttons, and messages render via resource keys; ensure keys are
unique and meaningful (e.g., title.smartNotes, button.addNote, message.noNotes)
and update any template bindings or attributes to use the i18n function so
strings are externalized for translation.

Comment on lines +23 to +26
<form method="POST">
<textarea name="note" placeholder="Write your note here..."></textarea>
<button type="submit">Save Note</button>
</form>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding CSRF protection to forms.

The forms submit via POST but have no visible CSRF token. While this MVP runs locally, CSRF protection is a security best practice for form submissions. If using Flask-WTF, tokens can be added easily.

As per coding guidelines: Security checks should include "CSRF (Cross-Site Request Forgery)".

Also applies to: 32-35, 44-46

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

In `@templates/index.html` around lines 23 - 26, The POST forms in
templates/index.html lack CSRF tokens; update each form (the <form> elements
around the note textarea and similar forms at lines referenced) to include your
framework's CSRF token (for Flask-WTF, inject the token via the template — e.g.
render or call {{ csrf_token() }} or include form.csrf_token()) as a hidden
field so the server can validate requests; ensure the view handlers that
render/validate the forms use Flask-WTF Form classes or call validate_csrf() so
the token is checked on submit.

Comment on lines +32 to +35
<form method="POST">
<input type="text" name="question" placeholder="Ask something about your notes...">
<button type="submit">Ask</button>
</form>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing label for question input field.

The <input> element lacks an associated <label>, which impacts accessibility. Screen readers and assistive technologies rely on labels to identify form fields.

🔧 Proposed fix
     <h2>Ask Question</h2>
     <form method="POST">
-        <input type="text" name="question" placeholder="Ask something about your notes...">
+        <label for="question" class="visually-hidden">Question</label>
+        <input type="text" id="question" name="question" placeholder="Ask something about your notes...">
         <button type="submit">Ask</button>
     </form>

Add to CSS for screen-reader-only text:

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

As per coding guidelines: "The code adheres to best practices recommended by lighthouse or similar tools for performance" — accessibility is part of Lighthouse audits.

📝 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
<form method="POST">
<input type="text" name="question" placeholder="Ask something about your notes...">
<button type="submit">Ask</button>
</form>
<form method="POST">
<label for="question" class="visually-hidden">Question</label>
<input type="text" id="question" name="question" placeholder="Ask something about your notes...">
<button type="submit">Ask</button>
</form>
🧰 Tools
🪛 HTMLHint (1.9.2)

[warning] 33-33: No matching [ label ] tag found.

(input-requires-label)

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

In `@templates/index.html` around lines 32 - 35, The input with name="question" in
the form is missing an associated label which breaks accessibility; fix by
adding a <label> tied to the input (give the input a unique id, e.g.,
id="question") and reference that id from the label (use a visible label or a
visually-hidden class for screen-reader-only text), and add the recommended
.visually-hidden CSS rule to your stylesheet so the label is available to
assistive tech without altering layout.

<!-- FOOTER -->
<div class="footer">
<p>Powered by AOSSIE</p>
<img src="{{ url_for('static', filename='images/aossie-logo.svg') }}" class="aossie-logo">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing alt attribute on AOSSIE logo.

Same accessibility issue as the header logo—add an alt attribute.

🔧 Proposed fix
-        <img src="{{ url_for('static', filename='images/aossie-logo.svg') }}" class="aossie-logo">
+        <img src="{{ url_for('static', filename='images/aossie-logo.svg') }}" class="aossie-logo" alt="AOSSIE logo">
📝 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
<img src="{{ url_for('static', filename='images/aossie-logo.svg') }}" class="aossie-logo">
<img src="{{ url_for('static', filename='images/aossie-logo.svg') }}" class="aossie-logo" alt="AOSSIE logo">
🧰 Tools
🪛 HTMLHint (1.9.2)

[warning] 83-83: An alt attribute must be present on elements.

(alt-require)

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

In `@templates/index.html` at line 83, The AOSSIE logo image tag (<img ...
class="aossie-logo">) is missing an alt attribute; update the template image
element (class "aossie-logo") to include a descriptive alt text (e.g.,
alt="AOSSIE logo" or a context-appropriate description) to improve
accessibility.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants