Skip to content

Enhance source and admin operations UI - #26

Merged
tomnguyen103 merged 5 commits into
mainfrom
codex/modernize-local-workspace-ui
Jun 7, 2026
Merged

Enhance source and admin operations UI#26
tomnguyen103 merged 5 commits into
mainfrom
codex/modernize-local-workspace-ui

Conversation

@tomnguyen103

@tomnguyen103 tomnguyen103 commented Jun 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • Upgrade Sources into a source/file management workspace with admin-guarded rename, delete, and content-edit flows.
  • Upgrade Admin into a governance/data-safety console with source selection, impact preview, retention validation, and clearer API/admin token guidance.
  • Refresh source-ingest navigation/copy and document the SECOND_BRAIN_API_TOKEN sidebar usage in README.
  • Harden backend test settings so local .env admin/API tokens do not change test expectations.

Verification

  • npm run lint
  • npm run build
  • SECOND_BRAIN_LLM_PROVIDER=fake SECOND_BRAIN_TEST_DATABASE_URL=postgresql+psycopg://second_brain:second_brain@localhost:5433/second_brain python -m pytest tests/unit/test_api_auth.py tests/integration/test_sources_api.py
  • git diff --check

Notes

  • Direct push to main was rejected by repository rules requiring PRs, so this is opened as a draft.
  • Existing Next.js multiple-lockfile workspace-root warning remains during npm run build.

Summary by CodeRabbit

Release Notes

  • New Features

    • Upgraded admin console into a governance/data-safety dashboard with export, purge, and safer delete flows.
    • Full Sources page management: rename/delete sources and documents, edit stored file content, and view document previews; admin-token gating and typed confirmations added.
    • Chat “New chat” now fully resets conversation state and navigates reliably.
  • Bug Fixes

    • Fixed new chat button to properly reset conversation state and URL.
  • Documentation

    • README token setup clarified for local and cloud usage; implementation notes and progress log updated.
  • Tests

    • Expanded integration and unit tests for sources/documents and admin routes.

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds admin-protected source/document APIs (read/preview/update content/rename/delete) with audit logging and reindexing; refactors frontend admin and Sources UIs for token-gated mutations and content editing; updates API client/types, auth tests, chat/sidebar reset behavior, and docs.

Changes

Source and Document Management Console

Layer / File(s) Summary
README token guidance
README.md
Adds local keyless usage note and instructs pasting SECOND_BRAIN_API_TOKEN into the web UI API access field.
Backend API schemas and request/response contracts
backend/app/schemas/sources.py
Pydantic models for source/document operations with whitespace trimming, content validation, content-hash handling, and deletion metadata.
Backend helpers for document summaries & content responses
backend/app/api/sources.py
Helpers to count chunks, build DocumentSummary, eager-load relationships, and construct DocumentContentResponse with raw/chunk selection and truncation metadata.
Backend source update endpoint
backend/app/api/sources.py
Admin-protected PATCH /sources/{id}: update name, audit, commit, refresh, return SourceOut.
Backend documents listing refactor
backend/app/api/sources.py
GET /sources/{id}/documents now uses chunk-count helpers and _document_summary assembly instead of relying on loaded doc.chunks.
Backend document content read & preview
backend/app/api/sources.py
GET /documents/{id}/content and /preview return DocumentContentResponse with configurable max_chars and truncation flags.
Backend document metadata PATCH (admin)
backend/app/api/sources.py
PATCH /documents/{id} updates title with admin protection, audits change, commits, and returns DocumentSummary.
Backend document content PATCH (admin) & reindex
backend/app/api/sources.py
PATCH /documents/{id}/content detects duplicate hashes (409), chunks & embeds content (cached encoder), validates vector counts (502), deletes/recreates chunks & embeddings, updates document fields/status/ingested_at, records audit detail, bumps search-cache epoch, and returns truncated content response.
Backend document DELETE (admin)
backend/app/api/sources.py
DELETE /documents/{id} verifies existence, counts chunks_deleted, deletes document, records audit, commits, bumps search-cache epoch, returns DeleteDocumentResponse.
Backend test infra & integration tests
backend/tests/conftest.py, backend/tests/integration/test_sources_api.py
Test settings fixture change, admin test helpers (TOKEN, _enable_admin), stub embedder, multi-ingest helpers, and comprehensive integration tests covering rename/audit, preview/read, content update/rebuild/audit, mismatch handling, duplicate-hash rejection, deletion cascade, and 404 cases.
Backend unit auth tests
backend/tests/unit/test_api_auth.py
Extends route matrix and adds parameterized tests ensuring admin-only endpoints return 401 with exact admin-token error detail when API token is present.
Frontend API response types
frontend/lib/api/types.ts
Adds SourceRecord, normalizes SourceSummary to extend it, updates DocumentList/Content responses to use SourceRecord, and adds DeleteDocumentResponse with chunks_deleted.
Frontend API client imports & formatting
frontend/lib/api/client.ts
Adds missing type imports (DeleteDocumentResponse, DocumentSummary, SourceRecord) and reformats client method/type signatures without logic changes.
Frontend conversation sidebar & chat reset
frontend/components/ConversationSidebar.tsx, frontend/app/chat/page.tsx
Adds NavItem typing/activePrefixes, changes New chat controls to dispatch second-brain-new-chat and navigate, centralizes resetChatState/startNewChat, and wires window event listener for chat reset navigation.
Frontend ingest wording updates
frontend/app/ingest/page.tsx
Updates UI copy from “Ingest” to “Sources” / “Add source” and adjusts button/empty-state text.
Frontend sources management console
frontend/app/sources/page.tsx
Transforms Sources page into a client-side management console with admin-token input, data loading, token-gated rename/delete/update mutations, content editing with truncation indicator, delete confirmation typing, aggregated metrics, and unified error display.
Frontend admin governance console
frontend/app/admin/page.tsx
Refactors Admin page into governance/data-safety console with GovernanceTile, status/sources polling, stricter validation, admin-guarded export/delete/purge mutations, runtime guardrails, local-first guidance, and export result rendering.
Progress and implementation notes
docs/PROGRESS.md, docs/implementation-notes.md
Documents the admin console, sources workspace, content edit/reindex flow, admin-guarded endpoints, and new-chat navigation reset behavior.

Sequence Diagram(s)

sequenceDiagram
  participant AdminUI
  participant API
  participant DB
  participant Embedder
  participant SearchCache
  AdminUI->>API: PATCH documents content
  API->>DB: load document and chunks
  API->>Embedder: request embeddings for chunks
  Embedder->>API: return vectors
  API->>DB: delete old chunks and insert new chunks/embeddings
  API->>DB: update document and insert AuditLog
  API->>SearchCache: bump epoch and invalidate
  API->>AdminUI: return DocumentContentResponse
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I hopped through sources, tidied every file,
With admin keys and audits, I worked with style.
Chunks rebuilt and hashes checked, embeddings in tow,
The console hums softly as the indexes grow.
A rabbit's small cheer for tidy data flow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 35.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'Enhance source and admin operations UI' directly reflects the main changes: upgrading the Sources workspace and Admin console with new management capabilities.
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 codex/modernize-local-workspace-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@tomnguyen103

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@tomnguyen103
tomnguyen103 marked this pull request as ready for review June 7, 2026 00:18
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/components/ConversationSidebar.tsx (1)

376-382: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply consistent new-chat behavior to mobile.

The mobile "New chat" button uses a plain Link and doesn't dispatch the second-brain-new-chat event, while the desktop version (lines 159-166) does. This inconsistency means mobile users won't trigger the chat reset logic registered in chat/page.tsx, potentially leaving stale conversation state.

🔧 Proposed fix for consistency
 <Link
   href="/chat"
+  onClick={(e) => {
+    e.preventDefault();
+    window.dispatchEvent(new Event("second-brain-new-chat"));
+  }}
   className="flex h-9 w-9 items-center justify-center rounded-lg bg-foreground text-background transition-colors hover:opacity-90 focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-primary/20"
   aria-label="New chat"
 >
🤖 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 `@frontend/components/ConversationSidebar.tsx` around lines 376 - 382, The
mobile "New chat" Link in ConversationSidebar doesn't dispatch the same
'second-brain-new-chat' CustomEvent as the desktop button, so update the mobile
control in ConversationSidebar (the Link rendering the Plus icon) to dispatch
window.dispatchEvent(new CustomEvent('second-brain-new-chat')) on activation
(e.g., onClick handler) before navigating; ensure the element still navigates to
"/chat" and preserves accessibility attributes so the chat reset logic
registered in chat/page.tsx receives the event consistently on both mobile and
desktop.
🧹 Nitpick comments (1)
frontend/app/chat/page.tsx (1)

44-48: ⚡ Quick win

Consider removing redundant history manipulation.

Both window.history.replaceState and router.replace are called to navigate to /chat. Mixing manual History API calls with Next.js router navigation can lead to unexpected behavior, as the router maintains its own history state. router.replace("/chat", { scroll: false }) should be sufficient.

♻️ Simplified version
 const startNewChat = useCallback(() => {
   resetChatState();
-  window.history.replaceState(window.history.state, "", "/chat");
   router.replace("/chat", { scroll: false });
 }, [resetChatState, router]);
🤖 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 `@frontend/app/chat/page.tsx` around lines 44 - 48, The startNewChat callback
currently calls both window.history.replaceState and router.replace which is
redundant and can conflict with Next.js navigation; remove the manual history
API call (the window.history.replaceState(...) line) and keep
router.replace("/chat", { scroll: false }) inside startNewChat (referencing the
startNewChat function, resetChatState, and router.replace) so navigation is
handled solely through Next.js router.
🤖 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 `@backend/app/api/sources.py`:
- Around line 33-48: _document_summary currently computes chunk_count using
len(doc.chunks), which forces selectinload of Document.chunks and hydrates full
chunk bodies; change it to accept a pre-fetched chunk_count (do not iterate
relationship) and stop relying on doc.chunks for counts. In
list_source_documents(), update_document() and the other call sites that
currently selectinload(Document.chunks) (and any code around the symbols
mentioned) replace the relationship load with an aggregate/subquery that returns
chunk_count per document (e.g. SQLAlchemy func.count or a correlated subquery)
and pass that integer into _document_summary when constructing DocumentSummary;
remove or conditionalize any selectinload(Document.chunks) so chunk bodies are
only loaded by content-specific endpoints.
- Around line 354-387: delete_document() currently removes the Document and its
Chunks but never invalidates the search cache; add the same search-epoch bump
that update_document_content() performs immediately after the db.commit() in
delete_document() so cached searches stop returning the removed document—call
the identical function used by update_document_content() (the search epoch/cache
bump helper) with the appropriate source_id (and ensure the helper is imported)
right after the commit and before returning the DeleteDocumentResponse.
- Around line 278-329: The code currently deletes existing Chunk rows and
updates doc even if encode_with_cache returned fewer vectors than pieces (zip
silently truncates); before deleting/rebuilding embeddings, verify that
len(vectors) == len(pieces) (and treat the vectors==[] case if pieces is empty),
and if they differ log an error and abort (raise/return without mutating DB or
setting doc.status/doc.content_hash) so the existing data remains intact;
perform this check after the encode_with_cache call and before
db.execute(delete(Chunk)...), referencing encode_with_cache, pieces, vectors,
zip, doc, Chunk and Embedding in your changes.

In `@backend/tests/integration/test_sources_api.py`:
- Around line 13-16: _enable_admin currently mutates global
app.dependency_overrides permanently and omits _env_file=None, causing state
leakage across tests; change _enable_admin into a context manager that
temporarily sets app.dependency_overrides[deps.get_settings] to a lambda
returning Settings(llm_provider="fake", api_token="test-api-token",
admin_token=TOKEN, _env_file=None) and restores the original override (or
deletes it) on exit, and then wrap admin-only tests with with _enable_admin():
so the override is test-local and .env leakage is prevented.

In `@backend/tests/unit/test_api_auth.py`:
- Around line 65-74: The test matrix in backend/tests/unit/test_api_auth.py only
checks bearer token rejection for admin-only endpoints; add additional
assertions that call the admin-only routes ("patch" "/documents/1", "patch"
"/documents/1/content", "delete" "/documents/1", "delete" "/data/sources/1")
using a valid API bearer token but without the X-Second-Brain-Admin-Token header
and assert the request is rejected (403/unauthorized per app behavior) to ensure
the require_admin protection is enforced; update or add test cases around those
route entries so each has both a bearer-invalid check and a
bearer-valid/no-admin-token check referencing the same route strings to locate
the code.

In `@frontend/components/ConversationSidebar.tsx`:
- Around line 159-166: The anchor with href="/chat" triggers native navigation
alongside the onClick handler startNewChat, causing a race; update the click
handling so the default navigation is prevented and only your custom flow runs —
either modify the startNewChat function to accept an event and call
event.preventDefault(), or change the onClick to an inline handler that calls
event.preventDefault() then invokes startNewChat(); keep the anchor semantics
(or replace with a button) and ensure references to startNewChat and the <a> in
ConversationSidebar.tsx are updated accordingly.

---

Outside diff comments:
In `@frontend/components/ConversationSidebar.tsx`:
- Around line 376-382: The mobile "New chat" Link in ConversationSidebar doesn't
dispatch the same 'second-brain-new-chat' CustomEvent as the desktop button, so
update the mobile control in ConversationSidebar (the Link rendering the Plus
icon) to dispatch window.dispatchEvent(new CustomEvent('second-brain-new-chat'))
on activation (e.g., onClick handler) before navigating; ensure the element
still navigates to "/chat" and preserves accessibility attributes so the chat
reset logic registered in chat/page.tsx receives the event consistently on both
mobile and desktop.

---

Nitpick comments:
In `@frontend/app/chat/page.tsx`:
- Around line 44-48: The startNewChat callback currently calls both
window.history.replaceState and router.replace which is redundant and can
conflict with Next.js navigation; remove the manual history API call (the
window.history.replaceState(...) line) and keep router.replace("/chat", {
scroll: false }) inside startNewChat (referencing the startNewChat function,
resetChatState, and router.replace) so navigation is handled solely through
Next.js router.
🪄 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: CHILL

Plan: Pro

Run ID: 8feb0f7e-396c-4a6f-8e91-72e900f72a3b

📥 Commits

Reviewing files that changed from the base of the PR and between 2138b23 and 3bbe4f8.

📒 Files selected for processing (15)
  • README.md
  • backend/app/api/sources.py
  • backend/app/schemas/sources.py
  • backend/tests/conftest.py
  • backend/tests/integration/test_sources_api.py
  • backend/tests/unit/test_api_auth.py
  • docs/PROGRESS.md
  • docs/implementation-notes.md
  • frontend/app/admin/page.tsx
  • frontend/app/chat/page.tsx
  • frontend/app/ingest/page.tsx
  • frontend/app/sources/page.tsx
  • frontend/components/ConversationSidebar.tsx
  • frontend/lib/api/client.ts
  • frontend/lib/api/types.ts

Comment thread backend/app/api/sources.py Outdated
Comment thread backend/app/api/sources.py
Comment thread backend/app/api/sources.py
Comment thread backend/tests/integration/test_sources_api.py
Comment thread backend/tests/unit/test_api_auth.py
Comment thread frontend/components/ConversationSidebar.tsx
@tomnguyen103

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/app/api/sources.py (2)

46-57: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop computing raw_text_available by touching doc.raw_text.

DocumentSummary only needs a boolean, but _document_summary() still reads the full text field. In both list_source_documents() and update_document(), that makes summary-only requests resolve every document body just to populate raw_text_available (or trigger one lazy load per row if raw_text is deferred). Please pass a projected boolean into the helper and keep raw_text reserved for the content endpoints.

Illustrative direction
-def _document_summary(doc: Document, *, chunk_count: int) -> DocumentSummary:
+def _document_summary(
+    doc: Document,
+    *,
+    chunk_count: int,
+    raw_text_available: bool | None = None,
+) -> DocumentSummary:
     return DocumentSummary(
         id=doc.id,
         source_id=doc.source_id,
         title=doc.title,
@@
-        raw_text_available=doc.raw_text is not None,
+        raw_text_available=(
+            doc.raw_text is not None
+            if raw_text_available is None
+            else raw_text_available
+        ),

Then have the summary queries project Document.raw_text.is_not(None) and pass that value into _document_summary() instead of materializing raw_text.

Also applies to: 191-206, 249-265

🤖 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 `@backend/app/api/sources.py` around lines 46 - 57, The helper
_document_summary currently reads doc.raw_text to compute raw_text_available;
change its signature to accept a raw_text_available: bool parameter (e.g., def
_document_summary(doc: Document, *, chunk_count: int, raw_text_available: bool)
-> DocumentSummary) and use that boolean rather than touching doc.raw_text.
Update every caller (including list_source_documents, update_document and the
other summary call sites around the ranges noted) to project a boolean
expression (Document.raw_text.is_not(None)) in the query and pass that projected
boolean into _document_summary; ensure no code reads doc.raw_text in
summary-only flows so raw text stays deferred and only content endpoints load
it.

64-72: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't eager-load chunk bodies on every content read.

_document_content_response() prefers raw_text, but _load_document() always selectinloads Document.chunks and the raw-text path only uses them for len(doc.chunks). That means both /documents/{id}/content and /documents/{id}/preview hydrate every chunk row and its text payload even when the response is built entirely from raw_text. Split chunk_count from chunk-body fallback so chunk bodies are only fetched when raw_text is missing.

Also applies to: 76-100, 224-230

🤖 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 `@backend/app/api/sources.py` around lines 64 - 72, _load_document currently
eager-loads Document.chunks (including full chunk bodies) even when callers like
_document_content_response only need chunk count and prefer raw_text; change
_load_document to remove selectinload(Document.chunks) so chunk bodies are not
hydrated by default, and instead add a lightweight chunk count query (e.g.,
SELECT COUNT(*) FROM Chunk WHERE document_id=...) where only the count is
needed, and only fetch chunk rows/bodies (via a separate query or a new loader
function) in the code path that falls back to assembling content from chunks
when raw_text is missing; update the other places that call _load_document (the
content/preview endpoints and the other affected call sites) to use the count
query or to explicitly load chunks when required.
🤖 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 `@backend/tests/integration/test_sources_api.py`:
- Around line 279-301: The test compares two unordered lists of chunk IDs
(old_chunk_ids and new_chunk_ids) which can produce false negatives; update the
assertion in test_sources_api.py to be order-insensitive by comparing sorted
lists or sets (e.g., assert set(new_chunk_ids) == set(old_chunk_ids) or assert
sorted(new_chunk_ids) == sorted(old_chunk_ids)) after the PATCH flow that
populates new_chunk_ids so the test only verifies preservation of the same rows
regardless of order.

---

Outside diff comments:
In `@backend/app/api/sources.py`:
- Around line 46-57: The helper _document_summary currently reads doc.raw_text
to compute raw_text_available; change its signature to accept a
raw_text_available: bool parameter (e.g., def _document_summary(doc: Document,
*, chunk_count: int, raw_text_available: bool) -> DocumentSummary) and use that
boolean rather than touching doc.raw_text. Update every caller (including
list_source_documents, update_document and the other summary call sites around
the ranges noted) to project a boolean expression
(Document.raw_text.is_not(None)) in the query and pass that projected boolean
into _document_summary; ensure no code reads doc.raw_text in summary-only flows
so raw text stays deferred and only content endpoints load it.
- Around line 64-72: _load_document currently eager-loads Document.chunks
(including full chunk bodies) even when callers like _document_content_response
only need chunk count and prefer raw_text; change _load_document to remove
selectinload(Document.chunks) so chunk bodies are not hydrated by default, and
instead add a lightweight chunk count query (e.g., SELECT COUNT(*) FROM Chunk
WHERE document_id=...) where only the count is needed, and only fetch chunk
rows/bodies (via a separate query or a new loader function) in the code path
that falls back to assembling content from chunks when raw_text is missing;
update the other places that call _load_document (the content/preview endpoints
and the other affected call sites) to use the count query or to explicitly load
chunks when required.
🪄 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: CHILL

Plan: Pro

Run ID: 811509e2-0cde-416c-8a7e-481df8951c7a

📥 Commits

Reviewing files that changed from the base of the PR and between 3bbe4f8 and 3607b7a.

📒 Files selected for processing (6)
  • backend/app/api/sources.py
  • backend/tests/integration/test_sources_api.py
  • backend/tests/unit/test_api_auth.py
  • docs/PROGRESS.md
  • frontend/app/chat/page.tsx
  • frontend/components/ConversationSidebar.tsx
💤 Files with no reviewable changes (1)
  • frontend/app/chat/page.tsx
✅ Files skipped from review due to trivial changes (1)
  • docs/PROGRESS.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/components/ConversationSidebar.tsx

Comment on lines +279 to +301
old_chunk_ids = [
c.id for c in db_session.query(Chunk).filter(Chunk.document_id == document_id)
]
assert old_chunk_ids

previous_embedder = app.dependency_overrides.get(deps.get_embedder)
app.dependency_overrides[deps.get_embedder] = lambda: _ShortVectorEmbedder()
try:
resp = client.patch(
f"/documents/{document_id}/content",
json={"content": "new content that cannot be fully embedded " * 60},
headers=ADMIN,
)

assert resp.status_code == 502
db_session.expire_all()
doc = db_session.get(Document, document_id)
assert doc.content_hash == old_hash
assert doc.raw_text == old_raw_text
new_chunk_ids = [
c.id for c in db_session.query(Chunk).filter(Chunk.document_id == document_id)
]
assert new_chunk_ids == old_chunk_ids

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 | ⚡ Quick win

Make the preserved-chunk assertion order-insensitive.

Both chunk-ID queries are unordered, so new_chunk_ids == old_chunk_ids can fail even when the same rows are preserved. Sort them or compare sets here to avoid a flaky test.

Suggested fix
-        old_chunk_ids = [
-            c.id for c in db_session.query(Chunk).filter(Chunk.document_id == document_id)
-        ]
+        old_chunk_ids = {
+            c.id for c in db_session.query(Chunk).filter(Chunk.document_id == document_id)
+        }
         assert old_chunk_ids
@@
-            new_chunk_ids = [
-                c.id for c in db_session.query(Chunk).filter(Chunk.document_id == document_id)
-            ]
+            new_chunk_ids = {
+                c.id for c in db_session.query(Chunk).filter(Chunk.document_id == document_id)
+            }
             assert new_chunk_ids == old_chunk_ids
📝 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
old_chunk_ids = [
c.id for c in db_session.query(Chunk).filter(Chunk.document_id == document_id)
]
assert old_chunk_ids
previous_embedder = app.dependency_overrides.get(deps.get_embedder)
app.dependency_overrides[deps.get_embedder] = lambda: _ShortVectorEmbedder()
try:
resp = client.patch(
f"/documents/{document_id}/content",
json={"content": "new content that cannot be fully embedded " * 60},
headers=ADMIN,
)
assert resp.status_code == 502
db_session.expire_all()
doc = db_session.get(Document, document_id)
assert doc.content_hash == old_hash
assert doc.raw_text == old_raw_text
new_chunk_ids = [
c.id for c in db_session.query(Chunk).filter(Chunk.document_id == document_id)
]
assert new_chunk_ids == old_chunk_ids
old_chunk_ids = {
c.id for c in db_session.query(Chunk).filter(Chunk.document_id == document_id)
}
assert old_chunk_ids
previous_embedder = app.dependency_overrides.get(deps.get_embedder)
app.dependency_overrides[deps.get_embedder] = lambda: _ShortVectorEmbedder()
try:
resp = client.patch(
f"/documents/{document_id}/content",
json={"content": "new content that cannot be fully embedded " * 60},
headers=ADMIN,
)
assert resp.status_code == 502
db_session.expire_all()
doc = db_session.get(Document, document_id)
assert doc.content_hash == old_hash
assert doc.raw_text == old_raw_text
new_chunk_ids = {
c.id for c in db_session.query(Chunk).filter(Chunk.document_id == document_id)
}
assert new_chunk_ids == old_chunk_ids
🤖 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 `@backend/tests/integration/test_sources_api.py` around lines 279 - 301, The
test compares two unordered lists of chunk IDs (old_chunk_ids and new_chunk_ids)
which can produce false negatives; update the assertion in test_sources_api.py
to be order-insensitive by comparing sorted lists or sets (e.g., assert
set(new_chunk_ids) == set(old_chunk_ids) or assert sorted(new_chunk_ids) ==
sorted(old_chunk_ids)) after the PATCH flow that populates new_chunk_ids so the
test only verifies preservation of the same rows regardless of order.

@tomnguyen103
tomnguyen103 merged commit 0b83dd2 into main Jun 7, 2026
11 checks passed
@tomnguyen103
tomnguyen103 deleted the codex/modernize-local-workspace-ui branch June 7, 2026 00:51
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