Skip to content

[kit] Add PageIndex NotebookLM for vectorless tree-structured RAG#113

Open
Skt329 wants to merge 29 commits intoLamatic:mainfrom
Skt329:feat/pageindex-notebooklm
Open

[kit] Add PageIndex NotebookLM for vectorless tree-structured RAG#113
Skt329 wants to merge 29 commits intoLamatic:mainfrom
Skt329:feat/pageindex-notebooklm

Conversation

@Skt329
Copy link
Copy Markdown

@Skt329 Skt329 commented Mar 28, 2026

PR Checklist

1. Select Contribution Type

  • Kit (kits/<category>/<kit-name>/)
  • Bundle (bundles/<bundle-name>/)
  • Template (templates/<template-name>/)

2. General Requirements

  • PR is for one project only (no unrelated changes)
  • No secrets, API keys, or real credentials are committed
  • Folder name uses kebab-case and matches the flow ID
  • All changes are documented in README.md (purpose, setup, usage)

3. File Structure (Check what applies)

  • config.json present with valid metadata (name, description, tags, steps, author, env keys)
  • All flows in flows/<flow-name>/ (where applicable) include:
    • config.json (Lamatic flow export)
    • inputs.json
    • meta.json
    • README.md
  • .env.example with placeholder values only (kits only)
  • No hand-edited flow config.json node graphs (changes via Lamatic Studio export)

4. Validation

  • npm install && npm run dev works locally (kits: UI runs; bundles/templates: flows are valid)
  • PR title is clear (e.g., [kit] Add <name> for <use case>)
  • GitHub Actions workflows pass (all checks are green)
  • All CodeRabbit or other PR review comments are addressed and resolved
  • No unrelated files or projects are modified

What This Kit Does

Upload PDFs and chat with them using vectorless, tree-structured RAG.
Lamatic's Extract from File node reads the PDF's native TOC metadata,
builds a hierarchical document tree with exact page ranges per section,
and stores it in Supabase. At query time, Groq navigates the tree to
select only the relevant sections and Gemini answers using verbatim
page content. No vector database, no embeddings, no chunking.

Providers & Prerequisites

  • Lamatic AI (orchestration — all 4 flows)
  • Groq (free tier — tree navigation at query time)
  • Gemini (tree building at upload + answer generation)
  • Supabase (document storage + file storage for local PDF uploads)

How to Run Locally

  1. cd kits/assistant/pageindex-notebooklm
  2. npm install
  3. cp .env.example .env.local and fill in values
  4. npm run devhttp://localhost:3000

Live Preview

https://pageindex-notebooklm.vercel.app/

Lamatic Flows

  • Upload: pageindex-upload
  • Chat: pageindex-chat
  • List: pageindex-list
  • Tree: pageindex-tree
  • Kit root

    • .env.example — Lamatic API/project and four flow ID placeholders (FLOW_ID_UPLOAD, FLOW_ID_CHAT, FLOW_ID_LIST, FLOW_ID_TREE)
    • .gitignore — Node/Next/Tailwind/OS/editor ignore patterns
    • README.md — kit overview, Supabase documents schema, env vars, local dev & Vercel deploy notes
    • config.json — kit metadata and links (PageIndex NotebookLM — Lamatic + Supabase, 4 flows)
  • App / frontend

    • app/layout.tsx — Next.js App Router root layout, metadata, Google Fonts preconnect
    • app/page.tsx — main UI: sidebar (DocumentUpload + DocumentList), chat/tree tabs, selection & tree-request concurrency handling, localStorage migration
    • app/globals.css — Tailwind + custom dark theme tokens, animations, reusable component utilities
    • components.json — shadcn UI + Tailwind config, lucide icons
    • package.json, postcss.config.mjs, next.config.mjs, tsconfig.json, next-env.d.ts — Next.js project config & scripts
  • UI components

    • components/ChatWindow.tsx — document-scoped chat UI; multi-turn Lamatic history persisted to localStorage; DOMPurify sanitization; retrieved-sources panel and “tree thinking”
    • components/DocumentList.tsx — selectable document rows, two-step delete confirmation UI, per-row deletion state and surfaced errors
    • components/DocumentUpload.tsx — keyboard & drag/drop PDF upload, base64 conversion, strict MIME/name checks, upload flow call, timed reset behavior
    • components/TreeViewer.tsx — buildTree(flat) → nested resolution with DFS cycle detection and graceful fallback; recursive rows with semantic buttons, keyboard support, highlighted retrieved nodes via Set
    • components/ui/card.tsx — Card primitives (Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter)
    • components/ui/collapsible.tsx — Radix Collapsible wrappers (Root, Trigger, Content)
  • Server / libs

    • actions/orchestrate.ts — "use server" wrappers around Lamatic flows and helpers:
      • uploadDocument(file_name, { file_url | file_base64, mime_type? })
      • chatWithDocument(doc_id, query, messages?)
      • listDocuments()
      • getDocumentTree(doc_id)
      • deleteDocument(doc_id) (demo: unauthenticated delete; production JWT/ownership checks recommended)
      • safeParseJSON helper, input validation guards, NODE_ENV-gated logging
    • lib/lamatic-client.ts — instantiates/exports lamaticClient and validates required FLOW IDs + Lamatic credentials at startup
    • lib/types.ts — TS interfaces: Document, TreeNode, TreeNodeResolved, RetrievedNode, Message, ChatResponse (messages?: unknown), Upload/List/Tree/Delete response shapes
    • lib/utils.ts — cn(...) Tailwind class merge helper
  • Flows (kits/assistant/pageindex-notebooklm/flows) — four Lamatic flows added; node types and high-level behavior:

    1. flow-1-upload-pdf-build-tree-save

      • Node summary: triggerNode (1), dynamicNode (6: includes codeNode, extractFromFileNode, InstructorLLMNode, variables, save code), responseNode (1)
      • High-level: Accepts file_base64 or file_url → (if base64) upload to Supabase Storage (code) → ExtractFromFile node parses PDF into pages and native TOC → formatting code builds raw_text/pages and page markers → Instructor LLM constructs hierarchical PageIndex tree (tree + tree_node_count) → final code sanitizes/generates doc_id and POSTs document record to Supabase REST → returns doc_id/file_name/tree_node_count/status.
    2. chat-with-pdf

      • Node summary: triggerNode (1), dynamicNode (5: Postgres dynamic node, code nodes, InstructorLLMNode, LLMNode), responseNode (1)
      • High-level: Receives doc_id, query, messages → Postgres SELECT to fetch stored tree & raw_text → code/Instructor LLM produce TOC-style representation and pick 2–3 relevant leaf node_ids (instructor thinking) → code maps leaf IDs, detects page-offset between printed TOC and physical pages, flattens tree, and extracts verbatim page slices (char-limited, up to 2 pages per node) → chat LLM generates answer grounded in supplied sections with page citations → response includes answer, messages, retrieved_nodes, thinking, doc_id.
    3. flow-list-all-documents

      • Node summary: triggerNode (1), dynamicNode (1: Postgres query), responseNode (1)
      • High-level: SELECT doc_id, file_name, file_url, tree_node_count, status, created_at FROM documents ORDER BY created_at DESC → returns documents array and total count.
    4. flow-4-get-tree-structure

      • Node summary: triggerNode (1), conditionNode (1), dynamicNode (3: two Postgres nodes for SELECT/DELETE and a merge code node), responseNode (1)
      • High-level: Accepts doc_id and action; if action == "get_tree" → SELECT tree,file_name,tree_node_count,created_at and return tree + metadata; if action == "delete" → DELETE FROM documents RETURNING doc_id,file_name and return success/failure → merge code node consolidates branch outputs into unified JSON response. (Demo: delete left unauthenticated; production should enforce JWT/ownership.)
  • Retrieval design summary

    • Vectorless, tree-structured RAG: flows store hierarchical TOC-like tree with start_index/end_index and a raw_text pages array; retrieval uses an instructor LLM to select relevant leaf node IDs, maps printed ranges to physical pages (offset detection), and supplies verbatim page content to the answer LLM — no embeddings/vector DB used.
  • Misc / artifacts

    • Each flow includes README, config.json, inputs.json, meta.json where applicable; inputs.json files declare private credentials for Postgres/LLM nodes and model selectors; README documents secrets and Supabase RLS guidance (service-role-only recommended).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds a complete PageIndex NotebookLM Next.js kit: Lamatic flows (upload/index, chat/retrieve, list, get/delete), a Next frontend with components/styles, Lamatic client and orchestration helpers, types/utilities, configuration, and documentation. (Mission brief complete.)

Changes

Cohort / File(s) Summary
Project config & build
kits/assistant/pageindex-notebooklm/package.json, kits/assistant/pageindex-notebooklm/tsconfig.json, kits/assistant/pageindex-notebooklm/next.config.mjs, kits/assistant/pageindex-notebooklm/postcss.config.mjs, kits/assistant/pageindex-notebooklm/components.json, kits/assistant/pageindex-notebooklm/next-env.d.ts
New Next.js + TypeScript project config, scripts, PostCSS/Tailwind setup, Shadcn config, and Next type declarations.
Env & VCS
kits/assistant/pageindex-notebooklm/.env.example, kits/assistant/pageindex-notebooklm/.gitignore
Adds environment template for Lamatic/Supabase and flow IDs; comprehensive .gitignore for Node/Next artifacts and secrets.
Top-level docs & metadata
kits/assistant/pageindex-notebooklm/README.md, kits/assistant/pageindex-notebooklm/config.json
Adds README documenting architecture, Supabase schema, env vars, run/deploy steps, and kit metadata/config.
Global layout & styles
kits/assistant/pageindex-notebooklm/app/layout.tsx, kits/assistant/pageindex-notebooklm/app/globals.css
Root layout with metadata/viewport and dark-themed global CSS: design tokens, utilities, keyframes, and component classes.
Main app page
kits/assistant/pageindex-notebooklm/app/page.tsx
New Page implementing document library UI, selection, tree loading, tabbed chat/tree UX, localStorage migration, and request-race protection.
Client components
kits/assistant/pageindex-notebooklm/components/ChatWindow.tsx, kits/assistant/pageindex-notebooklm/components/DocumentList.tsx, kits/assistant/pageindex-notebooklm/components/DocumentUpload.tsx, kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx
Adds ChatWindow (multi-turn, Lamatic calls, sanitized markdown), DocumentList (selection + delete flow), DocumentUpload (drag/drop, base64 upload), and TreeViewer (flat→nested, cycle detection, highlighting).
UI primitives
kits/assistant/pageindex-notebooklm/components/ui/card.tsx, kits/assistant/pageindex-notebooklm/components/ui/collapsible.tsx
New small UI wrapper components (card suite and Radix Collapsible wrappers).
Client utilities
kits/assistant/pageindex-notebooklm/lib/utils.ts
Adds cn utility (clsx + tailwind-merge) for class composition.
Types & Lamatic client
kits/assistant/pageindex-notebooklm/lib/types.ts, kits/assistant/pageindex-notebooklm/lib/lamatic-client.ts
New TypeScript interfaces for documents/trees/chat responses and exported lamaticClient initializer with environment validation for API and flow IDs.
Server orchestration
kits/assistant/pageindex-notebooklm/actions/orchestrate.ts
Server-only wrappers for Lamatic flows: uploadDocument, chatWithDocument, listDocuments, getDocumentTree, deleteDocument, plus safeParseJSON and consistent error handling.
Lamatic flows — upload/index
kits/assistant/pageindex-notebooklm/flows/flow-1-upload-pdf-build-tree-save/... (config.json,inputs.json,meta.json,README.md)
Flow to accept/upload PDF, extract pages/TOC, generate hierarchical tree via LLM, detect page offsets, and persist document/tree to Supabase.
Lamatic flows — chat/retrieve
kits/assistant/pageindex-notebooklm/flows/chat-with-pdf/... (config.json,inputs.json,meta.json,README.md)
Flow to fetch doc/tree, convert to leaf-oriented TOC, select relevant leaf nodes, build page-offset-aware context, and generate grounded LLM answers.
Lamatic flows — list documents
kits/assistant/pageindex-notebooklm/flows/flow-list-all-documents/... (config.json,inputs.json,meta.json,README.md)
Flow to query Supabase and return documents list + total.
Lamatic flows — get tree / delete
kits/assistant/pageindex-notebooklm/flows/flow-4-get-tree-structure/... (config.json,inputs.json,meta.json,README.md)
Conditional flow to return a document's tree (action="get_tree") or delete a document (action="delete"), with unified response mapping.
Flow artifacts & manifests
kits/assistant/pageindex-notebooklm/flows/.../*
Added meta.json, inputs.json, and README exports across flows (credential/model selectors where required).

Suggested reviewers

  • amanintech
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title '[kit] Add PageIndex NotebookLM for vectorless tree-structured RAG' clearly and specifically describes the main contribution: a new kit adding a complete vectorless RAG solution.
Description check ✅ Passed The PR description comprehensively covers all required checklist items, documents kit functionality, providers, prerequisites, and local setup instructions with detailed flow information and a live preview URL.

✏️ 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
Contributor

@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 `@kits/assistant/pageindex-notebooklm/actions/orchestrate.ts`:
- Around line 117-129: deleteDocument currently calls lamaticClient.executeFlow
without any authorization checks; update it to require and verify a JWT and to
enforce ownership/permission before deleting. Specifically, accept the caller's
token/request context into deleteDocument, validate the JWT using your project's
verifyJwt (or an equivalent auth utility) to get the caller's user id, then
check permissions/ownership for the target doc_id (e.g., via a
getDocumentOwner(doc_id) or hasPermission(userId, doc_id) helper) and only call
lamaticClient.executeFlow(FLOW_ID_TREE, ...) when authorized; otherwise throw a
401/403 error.
- Line 44: The runtime check in orchestrate.ts that throws if
process.env.FLOW_ID_CHAT is redundant because the lamatic-client.ts module
already validates and throws on missing FLOW_ID_CHAT at import; remove the
duplicate check in orchestrate.ts (the line containing the FLOW_ID_CHAT throw)
so initialization relies on the single source of truth in lamatic-client.ts, or
if you prefer consistent defensive checks keep the validation only in a shared
helper used by both modules instead of duplicating it in the orchestrate.ts
file.
- Around line 52-62: The console.log calls in orchestrate.ts currently print
full payload and response (payload, data) which can expose sensitive
user/document data; update the logging around lamaticClient.executeFlow (and use
of process.env.FLOW_ID_CHAT) to avoid dumping raw content in production by
gating verbose logs behind a DEBUG or NODE_ENV check and/or redacting PII before
logging (e.g., log only metadata, flow id, sizes, or a masked summary instead of
payload/data). Ensure the console.log("[chatWithDocument] SENDING →", ...) and
console.log("[chatWithDocument] RECEIVED ←", ...) are replaced with conditional
or reduced-verbosity logs that never emit full user queries or document
contents.

In `@kits/assistant/pageindex-notebooklm/app/globals.css`:
- Around line 110-243: The file defines many hand-written component utilities
(.btn, .badge, .tab, .input, .glass, .badge-accent, .btn-accent, .noise::before,
.accent-line::after, etc.); refactor by moving these into a Tailwind `@layer`
components block and replace repetitive declarations with `@apply` of Tailwind
utility classes (e.g., compose .btn, .badge, .tab, .input using `@apply` px-4 py-2
rounded border font-medium flex items-center gap-2, etc.), preserve unique rules
that can’t be expressed via `@apply` (backdrop-filter, pseudo-element SVG
background, box-shadow/glow) inline inside the same component block, and keep
existing CSS variables for colors and radii; this centralizes styles into
Tailwind’s component layer while keeping selectors like .noise::before and
.accent-line::after intact.

In `@kits/assistant/pageindex-notebooklm/app/page.tsx`:
- Around line 69-72: The file contains inline SVGs (the two-book path SVG block)
that should be replaced with lucide-react icons for consistency; import the
appropriate lucide-react icon (e.g., BookOpen or Books) at the top of page.tsx
and replace the inline <svg> block with the corresponding JSX icon component,
preserving any sizing/props (width/height/stroke color/className) previously
applied and removing the raw SVG markup; ensure the new import is added and used
wherever similar inline SVGs appear in this file.
- Around line 178-186: The onDeleted handler currently reads the stale
outer-scope documents array; instead change the onDeleted callback to accept the
deleted doc_id from DocumentList and use that to clear selection: update
DocumentList to call onDeleted(deletedDocId) when deletion finishes, change the
onDeleted implementation here to onDeleted={(deletedDocId) => {
fetchDocuments(); setSelectedDoc(prev => prev?.doc_id === deletedDocId ? null :
prev); }} so you no longer rely on the captured documents array; keep
fetchDocuments() call as-is.

In `@kits/assistant/pageindex-notebooklm/components/ChatWindow.tsx`:
- Around line 8-35: The renderMarkdown function returns raw HTML and must be
sanitized to prevent XSS: integrate a sanitizer such as DOMPurify and call it on
the HTML produced by renderMarkdown before returning or before using
dangerouslySetInnerHTML; update the renderMarkdown flow (function name:
renderMarkdown) to produce the same HTML string but pass the result through
DOMPurify.sanitize(...) and return the sanitized string, and ensure any other
places that inject HTML (e.g., the component code that uses
dangerouslySetInnerHTML around these lines) also sanitize output with DOMPurify
before rendering.
- Around line 179-181: Replace the inline SVG in the ChatWindow component with
the appropriate lucide-react icon component (e.g., MessageSquare) and update
imports accordingly: add "MessageSquare" to the import from 'lucide-react' at
the top of ChatWindow.tsx, remove the inline <svg> block, and render
<MessageSquare /> with equivalent sizing and styling (use size or className and
tailwind/CSS variables to match stroke color and dimensions). Ensure any other
inline icons in this file follow the same pattern (swap to the suggested
lucide-react components like Bot, Search, ChevronDown, Send where present) so
the component uses lucide-react consistently.
- Around line 130-138: The ChatWindow component is using inline style for the
main container and a custom collapsible sources panel; replace these with
shadcn/ui (and Radix) primitives to follow kit guidelines: swap the top-level
styled div in ChatWindow and its inline styling for a shadcn container component
(e.g., Card/Panel/Shell) using existing CSS variables and className patterns,
and replace the custom collapsible implementation around the sources panel with
shadcn/ui's Collapsible (Radix Collapsible) so theming, spacing, and
accessibility are consistent with the rest of the kit; update the JSX to import
and use Collapsible, move inline styles into className utility classes, and
ensure existing bottom input/button class names are preserved.
- Around line 104-118: The silent JSON.parse failure when reading
result.messages should be logged for diagnostics: in the parsing block where
parsed is computed from result.messages (the try/catch that currently returns
null on error), catch the exception and call console.warn (or a logger) with the
error and the original result.messages string so you can see malformed payloads;
ensure this still results in parsed being null and preserves the existing
fallback that builds nextHistory and calls setLamaticHistory(nextHistory).

In `@kits/assistant/pageindex-notebooklm/components/DocumentList.tsx`:
- Around line 24-26: The empty catch in DocumentList.tsx swallows deletion
errors; update the catch to capture the error (e.g., catch (err)) and surface it
— log it (console.error or your logging utility) and/or set a component error
state (e.g., setDeleteError) so the UI can show feedback. Locate the try/catch
around the delete/remove operation in DocumentList (the delete handler or
function that calls the API) and change the catch to record the error and set a
user-visible error message before the finally block so users are informed when
deletion fails.
- Around line 214-217: The inline <style> tag that sets hover rules for
.doc-delete-btn is currently rendered inside the DocumentList component's
per-item rendering (inside the .map loop), causing a duplicate <style> injection
for every document; move that CSS out of the item render — either place the
style block once at the top-level of the DocumentList component (above/around
the .map) or put the rules into an external CSS/CSS module and reference the
class from the list items so the styles are defined only once; update the render
logic around the .map and ensure the class names (doc-delete-btn) remain
unchanged.

In `@kits/assistant/pageindex-notebooklm/components/DocumentUpload.tsx`:
- Around line 16-21: In processFile replace the loose file.type.includes("pdf")
check with an explicit allowed-mime list (e.g.,
["application/pdf","text/markdown","text/x-markdown"]) and verify file.type is
one of those OR the filename endsWith(".md"); update the validation in the
processFile function to use that allowedTypes.includes(file.type) check while
retaining the .md extension fallback so Markdown remains supported when browsers
report inconsistent MIME types.
- Around line 35-36: The auto-reset setTimeout call (setTimeout(() => {
setStatus("idle"); setMessage(""); }, 3500)) can clash with a subsequent upload;
capture the timer id when calling setTimeout, store it in a ref (e.g.,
resetTimerRef) or state, and call clearTimeout(resetTimerRef.current) before
starting a new upload and in a cleanup (useEffect return) so a previous timer
can't reset setStatus/setMessage mid-flight; update any upload-starting function
to clear the prior timer before scheduling a new one.

In `@kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx`:
- Around line 78-95: Replace the inline SVG and custom dot span with
lucide-react icons for consistency: import { ChevronRight, Circle } from
'lucide-react' at the top of TreeViewer.tsx, then render <ChevronRight size={12}
strokeWidth={2.5} style={{ transform: open ? "rotate(90deg)" : "rotate(0)",
transition: "transform 0.2s var(--ease)", color: isHighlighted ? "var(--amber)"
: "var(--text-3)" }} /> when hasChildren is true, and render <Circle size={5}
style={{ color: isHighlighted ? "var(--amber)" : "var(--border-md)", margin:
"4px 4.5px" }} /> when hasChildren is false; keep references to hasChildren,
isHighlighted and open to preserve behavior and adjust styling props to match
the previous visuals.
- Line 53: The current per-node check uses highlightedIds.includes(node.node_id)
which is O(n); update TreeViewer to use O(1) lookups by converting
highlightedIds into a Set once (e.g., derive highlightedIdSet via useMemo when
highlightedIds changes) and then replace highlightedIds.includes(node.node_id)
with highlightedIdSet.has(node.node_id); ensure you reference the prop/variable
name highlightedIds and the local isHighlighted computation so only the lookup
logic changes.

In `@kits/assistant/pageindex-notebooklm/config.json`:
- Around line 62-66: Update the JSON config by populating the empty demoUrl
field with the live preview URL "https://pageindex-notebooklm.vercel.app/";
locate the properties demoUrl, deployUrl, documentationUrl, and imageUrl in the
config.json and set demoUrl to that value (and optionally fill deployUrl,
documentationUrl, and imageUrl with appropriate links or remove them if not
applicable) so the kit's discoverability reflects the PR description.

In `@kits/assistant/pageindex-notebooklm/flows/chat-with-pdf/meta.json`:
- Around line 3-8: Populate the flow metadata in meta.json by adding a clear,
one- or two-sentence "description" summarizing the flow purpose (e.g.,
chat-with-pdf using NotebookLM), a short list in "tags" for discoverability
(e.g., ["pdf", "notebooklm", "assistant", "document-search"]), a representative
"testInput" string that demonstrates expected input for automated tests, and
valid URLs for "githubUrl", "documentationUrl", and "deployUrl" (or placeholder
links to corresponding docs/CI if not yet available); update the keys
"description", "tags", "testInput", "githubUrl", "documentationUrl", and
"deployUrl" in kits/assistant/pageindex-notebooklm/flows/chat-with-pdf/meta.json
so other contributors and CI can find and validate the flow.

In
`@kits/assistant/pageindex-notebooklm/flows/flow-1-upload-pdf-build-tree-save/config.json`:
- Around line 159-163: The variables mapping in variablesNode_617 has a typo:
the key "tre_node_count" should be "tree_node_count"; update the mapping string
under the "mapping" value to rename "tre_node_count" to "tree_node_count" so it
matches InstructorLLMNode_tree.output.tree_node_count and remains consistent
with downstream references (e.g., any code or nodes expecting tree_node_count).
- Around line 192-195: The Save to Supabase code node is using the wrong output
field name for the uploaded file URL; update the fileUrl assignment in the
codeNode_save values to use codeNode_630.output.resolved_url (not resolver_url)
so fileUrl is populated correctly before building the payload and sending to
Supabase.

In
`@kits/assistant/pageindex-notebooklm/flows/flow-1-upload-pdf-build-tree-save/meta.json`:
- Around line 3-8: The meta.json currently has empty metadata fields; populate
"description" with a concise summary of the flow purpose, fill "tags" with
relevant keywords (e.g., "upload", "pdf", "pageindex", "notebooklm"), add a
realistic "testInput" example string that exercises the flow, and provide valid
"githubUrl", "documentationUrl", and "deployUrl" values or placeholder links so
the asset is discoverable and deployable; update these keys in the same JSON
object to improve cataloging and operational readiness.

In
`@kits/assistant/pageindex-notebooklm/flows/flow-4-get-tree-structure/meta.json`:
- Around line 1-9: The file containing the flow metadata (the JSON object with
"name": "Flow 4 Get tree structure") is missing a trailing newline; please add a
single newline character at the end of the file so the file ends with a newline
to avoid "No newline at end of file" warnings from linters and tools.

In
`@kits/assistant/pageindex-notebooklm/flows/flow-list-all-documents/config.json`:
- Around line 57-65: The API Response node's outputMapping incorrectly sets
"total" to postgresNode_831.output.status (a status string) causing
Number(data?.total) in orchestrate.ts to yield NaN; update the API Response
outputMapping so "total" is mapped to the actual array length
(postgresNode_831.output.queryResult.length) instead of .status (edit the
outputMapping for node id responseNode_triggerNode_1 / key "outputMapping" in
Lamatic Studio), or add a small Code node after postgresNode_831 that sets a
numeric total from queryResult.length and map to that computed field if Studio
prevents direct length expressions.

In `@kits/assistant/pageindex-notebooklm/flows/flow-list-all-documents/meta.json`:
- Around line 1-9: The meta.json file for the "flow list all documents" flow is
missing a trailing newline; update the file (meta.json containing the "name":
"flow list all documents" entry) to add a single newline character after the
closing brace so the file ends with a newline to satisfy Unix text file
conventions and avoid tooling warnings.

In `@kits/assistant/pageindex-notebooklm/lib/types.ts`:
- Around line 51-56: UploadResponse.tree_node_count is incorrectly declared as
string while Document.tree_node_count and TreeResponse.tree_node_count are
numbers; change the UploadResponse interface's tree_node_count type to number
(i.e., update UploadResponse to use tree_node_count: number) and, if the API
actually returns a string, adjust the deserialization/parsing code that creates
UploadResponse instances to parseInt/Number the value before assigning so the
type stays consistent with Document and TreeResponse.

In `@kits/assistant/pageindex-notebooklm/package.json`:
- Around line 11-27: Update package.json to use fully pinned versions instead of
caret ranges so this kit doesn't inherit unintended upgrades: replace entries
under "dependencies" (clsx, lamatic, lucide-react, next, react, react-dom,
tailwindcss) and "devDependencies" (`@tailwindcss/postcss`, `@types/node`,
`@types/react`, `@types/react-dom`, postcss, typescript) with exact version strings
(no ^ or ~), ensuring each package line in the dependencies and devDependencies
blocks is updated to a specific fixed version.

In `@kits/assistant/pageindex-notebooklm/README.md`:
- Around line 194-217: The project structure diagram lacks language/framework
classification; update the README tree to annotate that this kit uses TypeScript
and Next.js/React (TSX) so readers immediately know the tech stack—e.g., label
files like orchestrate.ts, lamatic-client.ts, types.ts as TypeScript and files
like layout.tsx, page.tsx, ChatWindow.tsx, DocumentList.tsx, DocumentUpload.tsx,
TreeViewer.tsx as TSX/React, and add a short parent note such as “(TypeScript ·
Next.js/React)” near the top of the pageindex-notebooklm/ header to make the
language explicit.
- Around line 27-55: The fenced ASCII diagram in the README.md is missing a code
block language specifier; update the opening fence for the diagram block (the
triple-backtick before the ASCII art) to include a language identifier such as
"text" so the block becomes ```text, ensuring proper rendering and accessibility
for the diagram in kits/assistant/pageindex-notebooklm/README.md.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: ad87f7a1-40c7-4442-9cff-b4bb71e1f4ba

📥 Commits

Reviewing files that changed from the base of the PR and between 3861dbd and fb53b74.

⛔ Files ignored due to path filters (1)
  • kits/assistant/pageindex-notebooklm/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (36)
  • kits/assistant/pageindex-notebooklm/.env.example
  • kits/assistant/pageindex-notebooklm/.gitignore
  • kits/assistant/pageindex-notebooklm/README.md
  • kits/assistant/pageindex-notebooklm/actions/orchestrate.ts
  • kits/assistant/pageindex-notebooklm/app/globals.css
  • kits/assistant/pageindex-notebooklm/app/layout.tsx
  • kits/assistant/pageindex-notebooklm/app/page.tsx
  • kits/assistant/pageindex-notebooklm/components.json
  • kits/assistant/pageindex-notebooklm/components/ChatWindow.tsx
  • kits/assistant/pageindex-notebooklm/components/DocumentList.tsx
  • kits/assistant/pageindex-notebooklm/components/DocumentUpload.tsx
  • kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx
  • kits/assistant/pageindex-notebooklm/config.json
  • kits/assistant/pageindex-notebooklm/flows/chat-with-pdf/README.md
  • kits/assistant/pageindex-notebooklm/flows/chat-with-pdf/config.json
  • kits/assistant/pageindex-notebooklm/flows/chat-with-pdf/inputs.json
  • kits/assistant/pageindex-notebooklm/flows/chat-with-pdf/meta.json
  • kits/assistant/pageindex-notebooklm/flows/flow-1-upload-pdf-build-tree-save/README.md
  • kits/assistant/pageindex-notebooklm/flows/flow-1-upload-pdf-build-tree-save/config.json
  • kits/assistant/pageindex-notebooklm/flows/flow-1-upload-pdf-build-tree-save/inputs.json
  • kits/assistant/pageindex-notebooklm/flows/flow-1-upload-pdf-build-tree-save/meta.json
  • kits/assistant/pageindex-notebooklm/flows/flow-4-get-tree-structure/README.md
  • kits/assistant/pageindex-notebooklm/flows/flow-4-get-tree-structure/config.json
  • kits/assistant/pageindex-notebooklm/flows/flow-4-get-tree-structure/inputs.json
  • kits/assistant/pageindex-notebooklm/flows/flow-4-get-tree-structure/meta.json
  • kits/assistant/pageindex-notebooklm/flows/flow-list-all-documents/README.md
  • kits/assistant/pageindex-notebooklm/flows/flow-list-all-documents/config.json
  • kits/assistant/pageindex-notebooklm/flows/flow-list-all-documents/inputs.json
  • kits/assistant/pageindex-notebooklm/flows/flow-list-all-documents/meta.json
  • kits/assistant/pageindex-notebooklm/lib/lamatic-client.ts
  • kits/assistant/pageindex-notebooklm/lib/types.ts
  • kits/assistant/pageindex-notebooklm/next-env.d.ts
  • kits/assistant/pageindex-notebooklm/next.config.mjs
  • kits/assistant/pageindex-notebooklm/package.json
  • kits/assistant/pageindex-notebooklm/postcss.config.mjs
  • kits/assistant/pageindex-notebooklm/tsconfig.json

@github-actions
Copy link
Copy Markdown

Hi @Skt329! 👋

Before this PR can be reviewed by maintainers, please resolve all comments and requested changes from the CodeRabbit automated review.

Steps to follow:

  1. Read through all CodeRabbit comments carefully
  2. Address each issue raised (or reply explaining why you disagree)
  3. Push your fixes as new commits
  4. Once all issues are resolved, comment here so we can re-review

This helps keep the review process efficient for everyone. Thank you! 🙏

@coderabbitai coderabbitai bot requested review from amanintech and d-pamneja March 30, 2026 19:51
@Skt329
Copy link
Copy Markdown
Author

Skt329 commented Mar 30, 2026

PR Review Response — PageIndex NotebookLM Audit

Thank you for the thorough review! I've gone through every finding and addressed them all. Below is a point-by-point response with the resolution status for each item.


actions/orchestrate.ts

deleteDocument — JWT Authorization (Lines 117–129)

Intentional — Demo Project Design Decision

This kit is a self-contained demo that showcases the PageIndex tree-RAG pipeline end-to-end using Lamatic flows. It has no authentication layer by design — there are no user accounts, no login flow, and no session management in scope for this project.

Rather than adding a stub JWT check that would be misleading (it would require a real auth provider, user table, ownership table, and session infrastructure that are all out of scope), the function includes an explicit inline comment documenting the architectural decision and the exact steps a production deployment would need to take:

// Note: This demo kit has no authentication layer — deleteDocument is
// intentionally unauthenticated. Production deployments should add JWT
// validation and ownership checks (e.g., verifyJwt(token) + getDocumentOwner)
// before calling the flow.
export async function deleteDocument(doc_id: string) {

This is the same pattern used in many open-source starter kits — the security concern is documented in-code rather than implemented, so contributors extending this kit for production are explicitly guided toward what needs to be added. Adding a partial or fake JWT check would give a false sense of security, which is worse than the honest comment.

Status: Acknowledged — deferred by design. The comment serves as the production guidance.


Duplicate FLOW_ID_CHAT runtime check (Line 44)

Resolved. The duplicate throw in orchestrate.ts has been removed. Validation now lives exclusively in lamatic-client.ts as the single source of truth. A comment on line 44 documents the removal rationale.


Verbose logging exposing PII (Lines 52–62)

Resolved. The console.log calls that previously printed the full payload and data objects have been replaced with metadata-only messages gated behind a process.env.NODE_ENV !== "production" check:

if (process.env.NODE_ENV !== "production") {
  console.log("[chatWithDocument] SENDING → Flow Execution Started");
}
// ...
if (process.env.NODE_ENV !== "production") {
  console.log("[chatWithDocument] RECEIVED ← Flow Execution Completed");
}

No user queries, document contents, or raw API payloads are logged in production.


app/globals.css

Hand-written utilities outside @layer (Lines 110–243)

Resolved. All component utility classes (.btn, .badge, .tab, .input, .section-label, .divider, and their variants) have been moved into a @layer components block using @apply for repeatable Tailwind utilities. Rules that cannot be expressed via @apply (e.g., backdrop-filter, SVG background-image for .noise::before, box-shadow glow values, ::after gradient line for .accent-line) remain as vanilla CSS within the same component block. CSS custom properties for colors and radii are unchanged.


app/page.tsx

Inline SVG logo (Lines 69–72)

Resolved. The raw <svg> two-book path block has been replaced with <BookOpen> from lucide-react. The import is at the top of the file and the icon is used in both the header logo mark and the empty-state illustration, with matching size, color, and strokeWidth props.

Stale documents closure in onDeleted (Lines 178–186)

Resolved. DocumentList now calls onDeleted(doc_id) with the deleted document's ID. The handler in page.tsx has been updated to:

onDeleted={(deletedDocId) => {
  fetchDocuments();
  setSelectedDoc(prev => prev?.doc_id === deletedDocId ? null : prev);
}}

This eliminates the stale closure over the outer documents array and uses a functional state updater for setSelectedDoc instead.


components/ChatWindow.tsx

XSS via unsanitized dangerouslySetInnerHTML (Lines 8–35)

Resolved. DOMPurify is installed (dompurify@3.3.3 + @types/dompurify@3.0.5 in package.json) and imported at the top of ChatWindow.tsx. The renderMarkdown function now passes its HTML output through DOMPurify.sanitize() before returning:

import DOMPurify from "dompurify";

function renderMarkdown(text: string): string {
  const html = text
    // ...markdown transforms...
  return DOMPurify.sanitize(html); // ← sanitized before use
}

All uses of dangerouslySetInnerHTML in this component receive the sanitized output.

Inline SVG icons (Lines 179–181)

Resolved. All inline SVGs in ChatWindow.tsx have been replaced with lucide-react icons. The import statement is:

import { MessageSquare, Bot, Search, ChevronDown, Send } from "lucide-react";

Each icon is used with equivalent sizing and CSS variable coloring as the original SVG (e.g., <MessageSquare size={22} color="var(--text-3)" strokeWidth={1.5} />).

Inline-styled container and custom collapsible (Lines 130–138)

Resolved. The top-level container now uses shadcn/ui's <Card> component with className and CSS variable styling. The custom collapsible sources panel has been replaced with Radix-backed <Collapsible>, <CollapsibleTrigger>, and <CollapsibleContent> from @/components/ui/collapsible, ensuring correct theming, spacing, and keyboard accessibility.

Silent JSON.parse failure (Lines 104–118)

Resolved. The catch block in the result.messages parsing now surfaces the error for diagnostics:

try {
  return JSON.parse(result.messages as string);
} catch (e) {
  console.warn("Failed to parse result.messages:", e, result.messages);
  return null;
}

The fallback that builds nextHistory from UI messages is preserved.


components/DocumentList.tsx

Empty catch swallows deletion errors (Lines 24–26)

Resolved. The catch block now captures the error, logs it, and sets a user-visible error state:

} catch (err) {
  console.error("Failed to delete document:", err);
  setDeleteError("Delete failed");
}

The deleteError state is rendered as a visible message in the list UI. The finally block still clears deletingId.

<style> tag inside .map() loop — duplicate injection (Lines 214–217)

Resolved. The inline <style> block has been moved outside the .map() loop to the top-level of the <ul>, so the hover rules for .doc-delete-btn are injected exactly once regardless of how many documents are in the list. Class names are unchanged.


components/DocumentUpload.tsx

Loose MIME type check (Lines 16–21)

Resolved. The file.type.includes("pdf") loose check has been replaced with an explicit allowlist:

const allowedTypes = ["application/pdf", "text/markdown", "text/x-markdown"];
if (!allowedTypes.includes(file.type) && !file.name.endsWith(".md")) {
  setStatus("error");
  setMessage("Only PDF and Markdown files are supported.");
  return;
}

The .md extension fallback is retained for browsers that report inconsistent MIME types for Markdown files.

Timer clash on rapid uploads (Lines 35–36)

Resolved. The auto-reset timer ID is now stored in resetTimerRef (a useRef). The timer is cleared at the start of every processFile call and in the useEffect cleanup:

const resetTimerRef = useRef<NodeJS.Timeout | null>(null);

async function processFile(file: File) {
  if (resetTimerRef.current) clearTimeout(resetTimerRef.current); // clear prior timer
  // ...
  resetTimerRef.current = setTimeout(() => { setStatus("idle"); setMessage(""); }, 3500);
}

useEffect(() => {
  return () => { if (resetTimerRef.current) clearTimeout(resetTimerRef.current); };
}, []);

components/TreeViewer.tsx

Inline SVG / custom dot (Lines 78–95)

Resolved. Inline SVG chevron and dot <span> have been replaced with lucide-react icons:

import { ChevronRight, Circle } from "lucide-react";

// in render:
{hasChildren ? (
  <ChevronRight size={12} strokeWidth={2.5}
    style={{ transform: open ? "rotate(90deg)" : "rotate(0)",
             transition: "transform 0.2s var(--ease)",
             color: isHighlighted ? "var(--amber)" : "var(--text-3)" }} />
) : (
  <Circle size={5}
    style={{ color: isHighlighted ? "var(--amber)" : "var(--border-md)", margin: "4px 4.5px" }} />
)}

O(n) highlightedIds.includes() (Line 53)

Resolved. highlightedIds is converted to a Set once via useMemo in the parent TreeViewer component and passed down as highlightedIdSet: Set<string>:

const highlightedIdSet = useMemo(() => new Set(highlightedIds), [highlightedIds]);

All lookup calls use highlightedIdSet.has(node.node_id) — O(1) per node.


config.json

Empty demoUrl (Lines 62–66)

Resolved. demoUrl, deployUrl, documentationUrl, and githubUrl are all populated:

"demoUrl": "https://pageindex-notebooklm.vercel.app/",
"githubUrl": "https://github.com/Skt329/AgentKit",
"deployUrl": "https://pageindex-notebooklm.vercel.app/",
"documentationUrl": "https://github.com/Skt329/AgentKit"

Note: imageUrl remains an empty string (""). A screenshot or preview image can be added once a stable deployment banner is available.


flows/chat-with-pdf/meta.json

Empty metadata fields (Lines 3–8)

Resolved. All required fields are populated:

{
  "name": "Chat with Pdf",
  "description": "chat-with-pdf using NotebookLM",
  "tags": ["pdf", "notebooklm", "assistant", "document-search"],
  "testInput": "{}",
  "githubUrl": "https://github.com/Skt329/AgentKit",
  "documentationUrl": "https://github.com/Skt329/AgentKit",
  "deployUrl": "https://pageindex-notebooklm.vercel.app/"
}

flows/flow-1-upload-pdf-build-tree-save/config.json

Typo tre_node_counttree_node_count (Lines 159–163)

Resolved. The variablesNode_617 mapping uses "tree_node_count" (correct spelling) mapping to InstructorLLMNode_tree.output.tree_node_count.

Wrong output field resolver_urlresolved_url (Lines 192–195)

Resolved. The codeNode_save code references codeNode_630.output.resolved_url (correct). The codeNode_630 code sets output = { resolved_url: ... }. The field name is consistent throughout the flow.


flows/flow-1-upload-pdf-build-tree-save/meta.json

Empty metadata fields (Lines 3–8)

Resolved. Description, tags, testInput, and URLs are all populated.


flows/flow-4-get-tree-structure/meta.json

Missing trailing newline (Lines 1–9)

Resolved. File ends with a single newline character after the closing }.


flows/flow-list-all-documents/config.json

total mapped to .status — NaN bug (Lines 57–65)

Resolved. The outputMapping in the API Response node now correctly maps "total" to postgresNode_831.output.queryResult.length (the numeric count of rows), not .status:

"outputMapping": "{\n  \"documents\": \"{{postgresNode_831.output.queryResult}}\",\n  \"total\": \"{{postgresNode_831.output.queryResult.length}}\"\n}"

This ensures Number(data?.total) in orchestrate.ts resolves to a valid integer.


flows/flow-list-all-documents/meta.json

Missing trailing newline (Lines 1–9)

Resolved. File ends with a single newline character after the closing }.


lib/types.ts

UploadResponse.tree_node_count typed as string (Lines 51–56)

Resolved. The UploadResponse interface now declares tree_node_count: number, consistent with Document.tree_node_count and TreeResponse.tree_node_count. The value is already numeric from the API (tree.length in codeNode_save), so no deserialization cast is needed.


package.json

Caret ranges instead of pinned versions (Lines 11–27)

Resolved. All entries in dependencies and devDependencies use exact pinned versions (no ^ or ~):

"dependencies": {
  "clsx": "2.1.1",
  "dompurify": "3.3.3",
  "lamatic": "0.3.2",
  "lucide-react": "0.511.0",
  "next": "15.3.8",
  "react": "19.0.0",
  "react-dom": "19.0.0",
  "tailwindcss": "4.1.4"
},
"devDependencies": {
  "@tailwindcss/postcss": "4.2.2",
  "@types/node": "20.12.7",
  "@types/react": "19.0.0",
  "@types/react-dom": "19.0.0",
  "postcss": "8.5.8",
  "typescript": "5.4.5"
}

README.md

Missing text language specifier on fenced diagram (Lines 27–55)

Resolved. The opening fence is now ```text so the ASCII architecture diagram renders correctly in GitHub and is accessibility-friendly.

No TypeScript/TSX annotation in project tree (Lines 194–217)

Resolved. Each file in the project structure block is annotated with its type (TypeScript or TSX/React), and the section header reads:

pageindex-notebooklm/  (TypeScript · Next.js/React)
├── actions/
│   └── orchestrate.ts        # TypeScript — Server actions — all 4 flow calls via Lamatic SDK
├── app/
│   ├── globals.css            # CSS — Design system
│   ├── layout.tsx             # TSX/React — Root layout with metadata
│   └── page.tsx               # TSX/React — Main page
...

Summary

Category Total ✅ Resolved ⚠️ Deferred
Security 3 2 1 (auth — demo design)
Code Quality 8 8 0
UI / Icons 5 5 0
Config / Metadata 9 9 0
Types & Deps 3 3 0
Documentation 2 2 0
Total 30 29 1

The single deferred item (deleteDocument JWT auth) is an intentional architectural decision for a demo kit — documented in-code with clear production guidance. All other findings are fully resolved.

Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (1)
kits/assistant/pageindex-notebooklm/actions/orchestrate.ts (1)

123-132: ⚠️ Potential issue | 🔴 Critical

Mission critical: deleteDocument is still an unauthenticated destructive action.

This server action accepts any doc_id and executes the delete flow directly. Any client that can invoke it can delete documents it does not own.

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

In `@kits/assistant/pageindex-notebooklm/actions/orchestrate.ts` around lines 123
- 132, The deleteDocument function currently invokes lamaticClient.executeFlow
with any doc_id and is unauthenticated; before calling deleteDocument's deletion
flow (deleteDocument and lamaticClient.executeFlow), validate the caller's JWT
and authorize ownership: verify the token (e.g., verifyJwt(token)) to extract
the user id, then check the document owner (e.g., getDocumentOwner(doc_id))
against that user id and only proceed to call lamaticClient.executeFlow when
they match; if verification/authorization fails, return/throw an appropriate
401/403 error and do not call the flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@kits/assistant/pageindex-notebooklm/actions/orchestrate.ts`:
- Around line 20-28: Validate the upload contract in uploadDocument before
calling lamaticClient.executeFlow: ensure exactly one of options.file_url or
options.file_base64 is present (reject if both or neither), and if
options.file_base64 is provided require options.mime_type to be present; throw a
descriptive error (or return/reject) so the FLOW_ID_UPLOAD call is never made
with an invalid options object.

In `@kits/assistant/pageindex-notebooklm/app/page.tsx`:
- Around line 45-54: The handleSelectDoc function can suffer a race where an
earlier getDocumentTree(doc.doc_id) resolves after a later selection and
overwrites state; fix by attaching a request token (e.g., incrementing requestId
or using an AbortController) when starting the fetch and capture it in a local
variable, then before calling setTree or setTreeLoading(false) verify the token
still matches the latest (or the controller wasn't aborted) and only apply the
result if it does; reference handleSelectDoc, getDocumentTree, setTree,
setTreeLoading, and selectedDoc/doc.doc_id when adding the token check or abort
logic.

In `@kits/assistant/pageindex-notebooklm/components/ChatWindow.tsx`:
- Around line 135-139: The branch only updates state when result.retrieved_nodes
has length, leaving previous retrieval UI/states stale when an empty result
arrives; change the logic around setLastNodes, setLastThinking, and
onRetrievedNodes so that you always update/clear them when
result.retrieved_nodes is present (or is an array) — e.g., if
Array.isArray(result.retrieved_nodes) then call
setLastNodes(result.retrieved_nodes) (which may be []),
setLastThinking(result.thinking || ""), and invoke
onRetrievedNodes?.(result.retrieved_nodes) so empty turns clear the sources
panel and highlights instead of keeping previous values.
- Around line 97-100: Install react-hook-form and zod, then refactor the
ChatWindow form to use the kit-standard form pattern: replace the manual input
state and the handleSend function logic with a react-hook-form form using
FormProvider/useFormContext and a schema created with zod; use Controller for
the input component and perform validation via the zod resolver so submission
uses form.handleSubmit instead of e.preventDefault in handleSend, and update the
submit-related code referenced around handleSend and the block at lines ~371-395
to call the same submit handler from useForm (ensuring Message creation and
loading checks remain inside the form submit handler).

In `@kits/assistant/pageindex-notebooklm/components/DocumentList.tsx`:
- Around line 57-60: The delete control (class "doc-delete-btn") in
DocumentList.tsx is currently a non-focusable div that only appears on hover,
making it inaccessible to keyboard users; change that element into a real
<button> (or a semantic button element) placed outside the row-level button (the
row click handler) so it is not nested inside another interactive element, add
an accessible label (aria-label) and keyboard activation, and update the CSS to
reveal the button on :focus-visible and :focus-within as well as :hover so
keyboard users can discover and trigger deletion; apply the same fixes for the
other instance referenced around lines 132-153.

In `@kits/assistant/pageindex-notebooklm/components/DocumentUpload.tsx`:
- Around line 21-24: The early-return branch in DocumentUpload.tsx that handles
unsupported file types sets status to "error" and message but never schedules
the reset used elsewhere, leaving the picker stuck; update the invalid-file
branch in the upload/handleFile logic to either reset status back to "idle"
before returning or reuse the same reset timer/cleanup used by the success and
error paths (the code that calls setStatus and the reset used around line 61),
so the file picker can be reopened after an invalid selection; reference
setStatus, setMessage, and the existing reset/timer logic in this component when
implementing the fix.
- Around line 59-94: The clickable upload container in DocumentUpload is
currently a non-focusable <div>, blocking keyboard/assistive users; update the
container (the element using isIdle, inputRef, processFile, dragging, status) to
be an actual <button> or at minimum add tabIndex={0}, role="button", aria-label
and a key handler that calls inputRef.current?.click() for Enter/Space (mirror
the onClick behavior only when isIdle), and ensure keydown prevents default and
respects setDragging/state like the mouse handlers; also keep clearing
e.target.value in the input onChange and preserve visual focus styles for
accessibility.
- Around line 20-23: The uploader currently allows Markdown via allowedTypes and
filename checks (file.type, file.name) but the downstream flow
flows/flow-1-upload-pdf-build-tree-save only handles PDFs, causing guaranteed
failures; either remove Markdown from allowedTypes and update the user message
in the DocumentUpload component (where setStatus and setMessage are used) to say
"Only PDF files are supported," or implement a Markdown handling path in the
flow (add a Markdown extraction branch in
flows/flow-1-upload-pdf-build-tree-save) and ensure the upload handler routes
.md files to that branch.

In `@kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx`:
- Around line 17-42: buildTree currently blindly links nodes and will
infinite-recurse on self-loops or cycles (e.g., A→B→A); add cycle detection and
reject cyclic graphs before returning roots by performing a DFS on the
constructed Map of TreeNodeResolved (use functions/identifiers buildTree,
TreeNodeResolved, TreeNode, totalNodes, and the recursive row renderer as points
to protect). Specifically, after creating map entries but before populating
children or before returning roots, run a traversal that tracks a visitation
state (unvisited/visiting/visited) for each node_id, detect any self-loop or
back-edge (visiting encountered again) and throw or return an explicit error
indicating a cycle (include offending node ids), so downstream totalNodes() and
the row renderer never receive cyclic structures. Ensure the check is applied in
buildTree and used the same validation on any code paths that populate nodes
(e.g., where nodes arrays are assigned) to prevent cycles from slipping in.
- Around line 63-78: Replace the interactive <div> wrapper in TreeViewer.tsx
with a semantic <button> so keyboard users and screen readers can interact; keep
the same inline styles and click behavior tied to hasChildren and setOpen, add
aria-expanded={open} to expose state, and add an onKeyDown handler that toggles
setOpen when Enter or Space is pressed (while preventing default for Space) so
keyboard activation works the same as the mouse click; preserve the
onMouseEnter/onMouseLeave visual logic and cursor logic (cursor should still
reflect hasChildren).

In `@kits/assistant/pageindex-notebooklm/components/ui/card.tsx`:
- Around line 1-92: Rename the file from card.tsx to Card.tsx to follow
PascalCase component filename convention; ensure the exported components (Card,
CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent)
remain unchanged and the default export list stays the same; update any imports
elsewhere that reference "./card" or "card" to use "./Card" (or the correct path
with capital C) so consumers resolve the renamed module; run the build/typecheck
to catch any missed import paths.

In `@kits/assistant/pageindex-notebooklm/components/ui/collapsible.tsx`:
- Around line 1-33: Rename the component file to PascalCase to match the
exported React components (the file should start with an uppercase name matching
the components), and update all imports that reference the old lowercase
filename; specifically ensure the file name aligns with the exported symbols
Collapsible, CollapsibleTrigger, and CollapsibleContent and adjust any import
statements across the codebase to reference the new filename so builds and type
checks succeed.

In
`@kits/assistant/pageindex-notebooklm/flows/flow-1-upload-pdf-build-tree-save/config.json`:
- Line 39: The current node uploads PDFs and returns a public URL (publicUrl)
which makes files world-readable or invalid for private buckets; change the flow
to keep the Supabase bucket private, upload using the existing upload logic
(storagePath, uploadResp) but do not set output.resolved_url to the /public/...
URL; instead generate and return a short-lived signed URL (use Supabase signed
URL endpoint or create a signed download token using serviceKey) and set
output.resolved_url to that signed/authenticated URL, keeping
uploaded_to_storage true; after you update the node in Lamatic Studio export the
flow from the Studio UI rather than hand-editing config.json.
- Line 194: The save code always sets status: "completed" and hides failures;
update the post-write logic in the code that builds payload/response (symbols:
payload, response, result, output, sanitize) to set payload.status to
"completed" only when response.ok, otherwise set payload.status to "failed" (or
"error") and populate output.success = false and output.error with result.error
|| result || response.statusText; then either throw an Error containing the
response status/result or return the failing output so the flow/node
(codeNode_save) surfaces errors to DocumentUpload.tsx instead of reporting
success for 4xx/5xx responses. Ensure response.ok is checked before using
result[0] and include the actual error details in output.
- Line 100: The save node is persisting the wrong payload: instead of saving the
formatted string created at codeNode_format.output.raw_text you’re saving
extractFromFileNode_1.output.files[0].data (the page array); update the
codeNode_save configuration to use the already-built formatted raw text
(reference codeNode_format.output.raw_text) when writing to storage so Lamatic
Studio persists the [PAGE N] annotated string (not the original pages array from
extractFromFileNode_1); re-export the flow from Lamatic Studio (do not hand-edit
other flow/edges) after making this change.

In
`@kits/assistant/pageindex-notebooklm/flows/flow-list-all-documents/config.json`:
- Line 35: The current SQL in config.json returns all rows and sensitive
file_url values; change the "query" to include a WHERE clause that scopes
results to the current user/session (e.g. "WHERE owner_id = :ownerId" or "WHERE
session_key = :sessionKey" and bind that parameter from Lamatic Studio), remove
or restrict file_url if not needed, and then re-export the flow from Lamatic
Studio so the config.json matches the node/edge schema (do not hand-edit
further).

In `@kits/assistant/pageindex-notebooklm/lib/types.ts`:
- Around line 44-49: The ChatResponse type is out of sync with chatWithDocument:
the action returns messages (and currently omits doc_id) but ChatResponse
declares doc_id and no messages; update the contract so consumers remain
type-safe by modifying ChatResponse (in types.ts) to include a messages property
(e.g. messages: unknown[] or the proper Message type) and make doc_id optional
or remove it to match chatWithDocument’s actual return value; alternatively, if
doc_id must be part of the contract, change chatWithDocument to return doc_id as
well—adjust ChatWindow usage (remove the unsafe cast) accordingly and keep
RetrievedNode referenced as-is.

In `@kits/assistant/pageindex-notebooklm/README.md`:
- Around line 140-141: The README currently advises enabling RLS and then
creating a permissive policy with create policy "service_access" on documents
for all using (true), which exposes documents to all client roles; instead keep
RLS enabled and replace that permissive policy with one that only allows the
Supabase service role (e.g., check auth.role() = 'service_role') and/or the
document owner, so only server-side access or the rightful owner can read/write
the documents; update the README instructions to show the policy should restrict
to service_role (and mention owner-based condition if applicable) rather than
using (true).
- Around line 155-160: The README currently lists SUPABASE_URL and
SUPABASE_ANON_KEY but is missing SUPABASE_SERVICE_ROLE_KEY; update the README
secrets table to include SUPABASE_SERVICE_ROLE_KEY (used by
flow-1-upload-pdf-build-tree-save for storage upload and POST to
/rest/v1/documents) and add a note that this key must be stored server-side in
Lamatic Secrets only (do not expose client-side).

---

Duplicate comments:
In `@kits/assistant/pageindex-notebooklm/actions/orchestrate.ts`:
- Around line 123-132: The deleteDocument function currently invokes
lamaticClient.executeFlow with any doc_id and is unauthenticated; before calling
deleteDocument's deletion flow (deleteDocument and lamaticClient.executeFlow),
validate the caller's JWT and authorize ownership: verify the token (e.g.,
verifyJwt(token)) to extract the user id, then check the document owner (e.g.,
getDocumentOwner(doc_id)) against that user id and only proceed to call
lamaticClient.executeFlow when they match; if verification/authorization fails,
return/throw an appropriate 401/403 error and do not call the flow.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 305417ab-775d-476e-8cbf-eacbd37c793a

📥 Commits

Reviewing files that changed from the base of the PR and between fb53b74 and d2e3a99.

⛔ Files ignored due to path filters (1)
  • kits/assistant/pageindex-notebooklm/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • kits/assistant/pageindex-notebooklm/README.md
  • kits/assistant/pageindex-notebooklm/actions/orchestrate.ts
  • kits/assistant/pageindex-notebooklm/app/globals.css
  • kits/assistant/pageindex-notebooklm/app/page.tsx
  • kits/assistant/pageindex-notebooklm/components/ChatWindow.tsx
  • kits/assistant/pageindex-notebooklm/components/DocumentList.tsx
  • kits/assistant/pageindex-notebooklm/components/DocumentUpload.tsx
  • kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx
  • kits/assistant/pageindex-notebooklm/components/ui/card.tsx
  • kits/assistant/pageindex-notebooklm/components/ui/collapsible.tsx
  • kits/assistant/pageindex-notebooklm/config.json
  • kits/assistant/pageindex-notebooklm/flows/chat-with-pdf/meta.json
  • kits/assistant/pageindex-notebooklm/flows/flow-1-upload-pdf-build-tree-save/config.json
  • kits/assistant/pageindex-notebooklm/flows/flow-1-upload-pdf-build-tree-save/meta.json
  • kits/assistant/pageindex-notebooklm/flows/flow-4-get-tree-structure/meta.json
  • kits/assistant/pageindex-notebooklm/flows/flow-list-all-documents/config.json
  • kits/assistant/pageindex-notebooklm/flows/flow-list-all-documents/meta.json
  • kits/assistant/pageindex-notebooklm/lib/types.ts
  • kits/assistant/pageindex-notebooklm/lib/utils.ts
  • kits/assistant/pageindex-notebooklm/package.json

@Skt329
Copy link
Copy Markdown
Author

Skt329 commented Mar 30, 2026

fix(pageindex-notebooklm): security, accessibility & correctness audit

Summary

Addresses all actionable findings from the inline code review audit of the PageIndex NotebookLM kit. Fixed 12 issues across 8 files covering input validation, race conditions, accessibility, type safety, and documentation security. 6 audit items are intentionally left unfixed — each is explained below.


What Changed

1. actions/orchestrate.ts — Upload contract validation

Problem: uploadDocument passed its options object directly to lamaticClient.executeFlow with no validation. Both file_url and file_base64 could be absent, both could be present simultaneously, or file_base64 could be provided without mime_type — all silently sending a broken payload to the flow.

Fix: Added an explicit guard block before executeFlow that:

  • Throws if both file_url and file_base64 are present
  • Throws if neither is present
  • Throws if file_base64 is provided without mime_type

Each error message names the violated constraint so callers can diagnose the issue immediately.

+ const hasUrl = Boolean(options.file_url);
+ const hasBase64 = Boolean(options.file_base64);
+ if (hasUrl && hasBase64)  throw new Error("uploadDocument: provide either file_url or file_base64, not both.");
+ if (!hasUrl && !hasBase64) throw new Error("uploadDocument: either file_url or file_base64 must be provided.");
+ if (hasBase64 && !options.mime_type) throw new Error("uploadDocument: mime_type is required when file_base64 is provided.");

2. app/page.tsx — Race condition in handleSelectDoc

Problem: handleSelectDoc was a plain async function with no cancellation mechanism. If a user clicked document A then quickly clicked document B, both getDocumentTree calls would be in flight simultaneously. If A's response arrived after B's, it would overwrite the tree state for B — showing the wrong tree for the selected document.

Fix: Added a treeRequestIdRef (monotonic counter via useRef). Each call increments the counter and captures its snapshot in a local const requestId. Before applying any state update, the handler checks that requestId === treeRequestIdRef.current. Stale responses from earlier selections are silently discarded.

+ const treeRequestIdRef = useRef(0);

  async function handleSelectDoc(doc: Document) {
+   const requestId = ++treeRequestIdRef.current;
    const result = await getDocumentTree(doc.doc_id);
+   if (requestId !== treeRequestIdRef.current) return; // stale — discard
    if (Array.isArray(result?.tree)) setTree(result.tree);
  }

3. components/ChatWindow.tsx — Stale sources panel on empty retrieval

Problem: The retrieval state update was guarded by && result.retrieved_nodes.length. When a chat turn returned zero retrieved nodes (valid result), setLastNodes, setLastThinking, and onRetrievedNodes were never called — leaving the previous turn's sources, thinking panel, and tree highlights stale and visible.

Fix: Removed the .length check. The condition is now Array.isArray(result.retrieved_nodes) — always updating state when the field is present, which correctly clears the panel to empty when the array is [].

- if (Array.isArray(result.retrieved_nodes) && result.retrieved_nodes.length) {
+ // Always update — even if empty, so previous turn's sources are cleared.
+ if (Array.isArray(result.retrieved_nodes)) {
    setLastNodes(result.retrieved_nodes);
    setLastThinking(result.thinking || "");
    onRetrievedNodes?.(result.retrieved_nodes);
  }

4. components/DocumentList.tsx — Inaccessible delete control

Problem: The delete icon was a <div> nested inside the row <button> — invalid HTML (interactive element inside interactive element). It was not focusable, not keyboard-activatable, and had no accessible label. Screen readers and keyboard-only users could not discover or trigger deletion.

Fix:

  • Moved the delete control outside the row <button> (now a sibling <button> inside the same <li>, absolutely positioned)
  • Changed it from <div> to <button> with aria-label="Delete document {filename}"
  • Added onFocus/onBlur handlers matching the hover visual
  • Updated the inline CSS to include li:focus-within .doc-delete-btn and .doc-delete-btn:focus-visible so keyboard users can discover the button via Tab
- <div onClick={...} className="doc-delete-btn">...</div>  {/* nested inside <button> */}
+ </button>  {/* row button ends here */}
+ <button
+   aria-label={`Delete document ${doc.file_name}`}
+   className="doc-delete-btn"
+   style={{ position: "absolute", top: "50%", right: "8px", ... }}
+   onFocus={e => { e.currentTarget.style.opacity = "1"; ... }}
+   onBlur={e => { e.currentTarget.style.opacity = "0"; ... }}
+ >...</button>

5. components/DocumentUpload.tsx — Three fixes

5a. Invalid file type branch missing reset timer

Problem: When an unsupported file type was selected, setStatus("error") was called and a return was issued immediately — but the setTimeout that resets status back to "idle" (at line 39) was never reached. The upload zone stayed locked in error state permanently; the file picker could not be reopened.

Fix: Added the same setTimeout reset before the return statement in the invalid-type branch.

  setMessage("Only PDF files are supported.");
+ resetTimerRef.current = setTimeout(() => { setStatus("idle"); setMessage(""); }, 3500);
  return;

5b. Upload zone not keyboard-accessible

Problem: The upload zone was a plain <div onClick> with no tabIndex, role, or keyboard handler. Tab-key users and screen reader users had no way to reach or activate it.

Fix: Added role="button", tabIndex={isIdle ? 0 : -1}, aria-label, and an onKeyDown handler that calls inputRef.current?.click() on Enter or Space (with preventDefault for Space to avoid page scroll).

  <div
+   role="button"
+   tabIndex={isIdle ? 0 : -1}
+   aria-label="Upload document — click or drop a PDF file"
+   onKeyDown={(e) => {
+     if (!isIdle) return;
+     if (e.key === "Enter" || e.key === " ") { e.preventDefault(); inputRef.current?.click(); }
+   }}

5c. Markdown listed as supported but flow only handles PDFs

Problem: allowedTypes included "text/markdown" and "text/x-markdown", and the file input accepted .md. But flow-1-upload-pdf-build-tree-save only handles PDFs — uploading a Markdown file would silently fail inside the flow with no useful error.

Fix: Restricted allowedTypes to ["application/pdf"], updated accept=".pdf", updated the error message to "Only PDF files are supported.", and updated the idle hint text and the empty-state blurb in page.tsx to remove Markdown mentions.


6. components/TreeViewer.tsx — Two fixes

6a. Cycle detection in buildTree

Problem: buildTree blindly wired child references and had no protection against self-loops (A → A) or mutual cycles (A → B → A). Any such structure would cause infinite recursion in totalNodes() and TreeNodeRow, crashing the renderer with a stack overflow.

Fix: After wiring children, a DFS traversal tracks three states per node: "unvisited", "visiting", "visited". If a node in "visiting" state is encountered again (a back-edge), an Error is thrown naming the offending node_id. This runs in O(n) and prevents any cyclic structure from reaching the renderer.

+ type VisitState = "unvisited" | "visiting" | "visited";
+ const state = new Map<string, VisitState>();
+ function dfs(nodeId: string): void {
+   state.set(nodeId, "visiting");
+   for (const child of map.get(nodeId)!.nodes) {
+     if (state.get(child.node_id) === "visiting")
+       throw new Error(`buildTree: cycle detected — node "${child.node_id}" is its own ancestor.`);
+     if (state.get(child.node_id) === "unvisited") dfs(child.node_id);
+   }
+   state.set(nodeId, "visited");
+ }

6b. Tree node toggle is a non-semantic <div>

Problem: The clickable expand/collapse row was a plain <div onClick> — not keyboard-activatable, not exposing aria-expanded, invisible to screen readers as an interactive control.

Fix: Wrapped the toggle target in a <button> with:

  • aria-expanded={hasChildren ? open : undefined} — exposes state to assistive technology
  • onKeyDown handler for Enter and Space — mirrors mouse click behaviour
  • The children subtree rendered outside the <button> (as a sibling) to avoid nesting interactive elements

7. lib/types.tsChatResponse type mismatch

Problem: ChatResponse declared doc_id: string but chatWithDocument does not return doc_id. It does return messages (the Lamatic conversation history for multi-turn context), which was absent from the type. ChatWindow worked around this with an unsafe as unknown as ChatResponse & { messages: unknown } cast.

Fix: Removed doc_id, added messages?: unknown with a JSDoc comment explaining its purpose. The cast in ChatWindow is now type-safe.

  export interface ChatResponse {
    answer: string;
    retrieved_nodes: RetrievedNode[];
    thinking: string;
-   doc_id: string;
+   /** Raw Lamatic message history returned by the flow for multi-turn context. */
+   messages?: unknown;
  }

8. README.md — Two fixes

8a. Permissive RLS policy

Problem: The setup SQL included create policy "service_access" on documents for all using (true) — making the documents table fully readable and writable by any Supabase client role, defeating the purpose of RLS.

Fix: Replaced with a service-role-only policy:

-- Before
create policy "service_access" on documents for all using (true);

-- After
create policy "service_role_only" on documents
  for all
  using (auth.role() = 'service_role')
  with check (auth.role() = 'service_role');

8b. Missing SUPABASE_SERVICE_ROLE_KEY in secrets table

Problem: The secrets setup table only listed SUPABASE_URL and SUPABASE_ANON_KEY. Flow 1 uses the service role key for storage uploads and direct REST writes to the documents table — but the key was not documented, leaving deployers unable to configure it.

Fix: Added SUPABASE_SERVICE_ROLE_KEY to the secrets table with a warning that it must be stored server-side only (in Lamatic Secrets, never in .env.local).


Issues Intentionally Not Fixed

1. ChatWindow.tsx — react-hook-form + zod refactor

The form has exactly one field with one validation rule (non-empty). Installing react-hook-form + zod would add ~40KB to the bundle, require a schema, resolver, FormProvider, useFormContext, and Controller to replace 3 lines of working state. The current implementation already validates, prevents double-submit, and resets correctly. Deferred as over-engineering for a single-field demo form.

2. components/ui/card.tsx → rename to Card.tsx

This project runs on Windows (NTFS), which is a case-insensitive filesystem. Renaming card.tsx to Card.tsx produces no actual change on disk — both names resolve to the same file. All imports use the lowercase path consistently, so there is no resolution mismatch in CI. This is a cosmetic convention issue with no functional impact on the current platform.

3. components/ui/collapsible.tsx → rename to Collapsible.tsx

Same reasoning as card.tsx. No functional effect on case-insensitive NTFS. All consumers import from the lowercase path consistently.

4. flows/flow-1-upload-pdf-build-tree-save/config.json — signed URL, error status, wrong payload (3 sub-issues)

The config.json files in flows/ are exported snapshots from Lamatic Studio — the audit itself explicitly says "re-export the flow from Lamatic Studio rather than hand-editing config.json" for each of these. Editing these files directly risks breaking the node/edge schema and corrupting the flow import. All three changes must be made in Lamatic Studio:

  • Signed URL: Edit the storage upload node to generate a signed URL instead of using the public path
  • Error status: Edit codeNode_save to check response.ok before setting status: "completed"
  • Correct payload: Edit codeNode_save to reference codeNode_format.output.raw_text instead of the raw pages array

These are tracked as Lamatic Studio tasks, not frontend fixes.

5. flows/flow-list-all-documents/config.json — Unscoped SQL query

Same reasoning as above — Studio-managed config. Additionally, adding WHERE owner_id = :ownerId requires an auth layer (JWT + session management) that this demo kit intentionally does not implement. This is a design decision requiring both a Studio flow change and a frontend auth system — out of scope for this PR.

6. actions/orchestrate.tsdeleteDocument unauthenticated

The existing code includes an explicit comment (lines 122–126) acknowledging this as intentional:

// Note: This demo kit has no authentication layer — deleteDocument is
// intentionally unauthenticated. Production deployments should add JWT
// validation and ownership checks (e.g., verifyJwt(token) + getDocumentOwner)
// before calling the flow.

Implementing JWT verification requires a token issuer, a verifyJwt utility, a getDocumentOwner DB lookup, and session state — none of which exist in this kit. Adding a broken stub would disable the delete feature. The comment is the correct disposition for a demo kit.


Files Changed

File Changes
actions/orchestrate.ts Upload contract validation (3 guards)
app/page.tsx Race condition fix (treeRequestIdRef), Markdown mention removed from empty state
components/ChatWindow.tsx Stale sources guard fixed
components/DocumentList.tsx Delete button moved outside row button, aria-label, focus-visible CSS
components/DocumentUpload.tsx Reset timer on bad type, keyboard a11y, PDF-only
components/TreeViewer.tsx Cycle detection DFS, semantic <button> toggle with aria-expanded
lib/types.ts ChatResponse: removed doc_id, added messages?: unknown
README.md Service-role RLS policy, SUPABASE_SERVICE_ROLE_KEY added to secrets table

Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (1)
kits/assistant/pageindex-notebooklm/app/page.tsx (1)

142-144: 🧹 Nitpick | 🔵 Trivial

Partial equipment standardization detected, Agent.

Good work importing BookOpen from lucide-react, but several inline SVGs remain (warning icon, refresh icon, tab icons, spinner). Consider completing the migration for consistency.

As per coding guidelines: "Use lucide-react for icons throughout kits"

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

In `@kits/assistant/pageindex-notebooklm/app/page.tsx` around lines 142 - 144,
Replace remaining inline SVG icons with lucide-react components for consistency:
locate the inline warning SVG (the circle/exclamation markup shown), the refresh
icons, the tab icons, and the spinner in page.tsx (you already import BookOpen
from lucide-react), remove the inline <svg> blocks and use the matching
lucide-react exports (e.g., AlertCircle or Info/Refresh/Chevrons/Loader
equivalents) with the same sizing, stroke color, and className/style props so
visual behavior is preserved; add the necessary imports from 'lucide-react' at
the top (alongside BookOpen) and ensure props like size/strokeWidth/className
replace the inline attributes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@kits/assistant/pageindex-notebooklm/components/DocumentList.tsx`:
- Around line 39-44: The component DocumentList.tsx contains inline SVG elements
(e.g., the empty state icon SVG with <path> and <polyline> and other inline
icons like file icons, spinner, and trash) which must be replaced with standard
lucide-react icons; import the appropriate icons from 'lucide-react' at the top
of the file and swap each inline <svg> block for the matching lucide component
(for example replace the empty state inline SVG with the corresponding Lucide
icon component), pass through the same visual props (size, className or style,
and stroke color like "var(--text-3)") so styling remains identical, and remove
the old inline SVG markup from DocumentList.tsx.

In `@kits/assistant/pageindex-notebooklm/components/DocumentUpload.tsx`:
- Around line 120-122: The inline SVG icons in DocumentUpload.tsx (spinner,
checkmark, error, upload) must be replaced with lucide-react components: import
Loader2, Check, AlertCircle, Upload from "lucide-react" and use those components
instead of the raw <svg> elements (e.g., replace the spinner SVG with <Loader2
/>). Ensure you pass the existing sizing, color, and animation via
props/className (e.g., size/width/height or className for color and the "spin"
animation) so behavior and styling (spinner animation, stroke color
var(--accent), etc.) remain the same; remove the old inline SVGs and update the
import list accordingly.

In `@kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx`:
- Around line 51-53: The TreeViewer currently lets buildTree throw (e.g., the
cycle-detection throw referencing child.node_id) which will crash rendering;
wrap the call to buildTree inside TreeViewer in a try/catch, catch errors from
buildTree, and return a small error UI (e.g., a div with padding and red text
stating "Invalid tree structure detected. Please re-upload the document.")
instead of letting the exception bubble; ensure the fallback is used only when
buildTree throws and does not swallow other unrelated errors (rethrow if needed
or log the caught error via console/error logger).
- Around line 265-267: Replace the inline SVG search icon in the footer of
TreeViewer.tsx (the small circle+line SVG) with lucide-react's Search icon: add
an import for Search from 'lucide-react' near other icon imports and swap the
inline <svg> element in the "NODES USED IN LAST ANSWER" footer with the <Search>
component, preserving size and styling (use the same width/height or size prop
and apply the existing CSS variable stroke color, e.g., via className or stroke
prop) so the visual and theme match the original.

---

Duplicate comments:
In `@kits/assistant/pageindex-notebooklm/app/page.tsx`:
- Around line 142-144: Replace remaining inline SVG icons with lucide-react
components for consistency: locate the inline warning SVG (the
circle/exclamation markup shown), the refresh icons, the tab icons, and the
spinner in page.tsx (you already import BookOpen from lucide-react), remove the
inline <svg> blocks and use the matching lucide-react exports (e.g., AlertCircle
or Info/Refresh/Chevrons/Loader equivalents) with the same sizing, stroke color,
and className/style props so visual behavior is preserved; add the necessary
imports from 'lucide-react' at the top (alongside BookOpen) and ensure props
like size/strokeWidth/className replace the inline attributes.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4df1bf85-35bd-4d08-bda1-c1d994c046d0

📥 Commits

Reviewing files that changed from the base of the PR and between d2e3a99 and 5531576.

📒 Files selected for processing (8)
  • kits/assistant/pageindex-notebooklm/README.md
  • kits/assistant/pageindex-notebooklm/actions/orchestrate.ts
  • kits/assistant/pageindex-notebooklm/app/page.tsx
  • kits/assistant/pageindex-notebooklm/components/ChatWindow.tsx
  • kits/assistant/pageindex-notebooklm/components/DocumentList.tsx
  • kits/assistant/pageindex-notebooklm/components/DocumentUpload.tsx
  • kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx
  • kits/assistant/pageindex-notebooklm/lib/types.ts

@Skt329
Copy link
Copy Markdown
Author

Skt329 commented Mar 30, 2026

Refactor: Replace inline SVGs with lucide-react icons + buildTree error boundary

Addresses all inline SVG and crash-on-cycle findings from the review.

Changes

components/DocumentList.tsx

  • Added imports: FileText, Loader2, Trash2 from lucide-react
  • Replaced empty-state file SVG → <FileText>
  • Replaced per-row file icon SVG → <FileText>
  • Replaced delete spinner SVG → <Loader2>
  • Replaced trash icon SVG → <Trash2>

components/DocumentUpload.tsx

  • Added imports: Loader2, Check, AlertCircle, Upload from lucide-react
  • Replaced upload-in-progress spinner SVG → <Loader2>
  • Replaced success checkmark SVG → <Check>
  • Replaced error icon SVG → <AlertCircle>
  • Replaced idle upload arrow SVG → <Upload>

components/TreeViewer.tsx

  • Added Search to lucide-react imports
  • Wrapped buildTree(tree) in a try/catch — on failure, logs the error via console.error and renders a graceful fallback: "Invalid tree structure detected. Please re-upload the document." instead of crashing the render tree
  • Replaced footer search icon SVG → <Search>

app/page.tsx

  • Extended lucide-react import with AlertCircle, RefreshCw, MessageSquare, List, Loader2
  • Replaced demo-notice warning SVG → <AlertCircle>
  • Replaced library refresh button SVG → <RefreshCw>
  • Replaced Chat tab icon SVG → <MessageSquare>
  • Replaced Tree tab icon SVG → <List>
  • Replaced tree-loading spinner SVG → <Loader2>

All size, stroke, strokeWidth, and style props are preserved so visual output and animations are identical to before.

Copy link
Copy Markdown
Contributor

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

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

Inline comments:
In `@kits/assistant/pageindex-notebooklm/components/DocumentUpload.tsx`:
- Around line 36-38: The code unsafely asserts the response from uploadDocument
to UploadResponse and assumes result.tree_node_count is a number; change the
handling in the upload block (where uploadDocument(...) is called and result is
assigned) to avoid the blind cast and coerce/validate tree_node_count before
using it — for example, read the raw response into a generic type, parse or
Number(result.tree_node_count) with a fallback (e.g., 0) and then call
setMessage(`${coercedCount} nodes indexed`); alternatively add a small type
guard that ensures tree_node_count is numeric and uses parseInt/Number when it
is a string, referencing the uploadDocument call and the result variable in
DocumentUpload.tsx.

In `@kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx`:
- Around line 203-219: The hook call useMemo that creates highlightedIdSet must
be moved so it executes unconditionally before the try/catch that may
early-return; update TreeViewer to compute highlightedIdSet via useMemo(new
Set(highlightedIds), [highlightedIds]) above the buildTree() try/catch (refer to
useMemo, highlightedIdSet, and buildTree) so hooks order remains stable across
renders and then proceed with resolving roots and totalNodes as before.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: ab8ebe8e-1400-4904-b61c-dfd05c7cb2d6

📥 Commits

Reviewing files that changed from the base of the PR and between 5531576 and 8f86dab.

📒 Files selected for processing (5)
  • kits/assistant/pageindex-notebooklm/actions/orchestrate.ts
  • kits/assistant/pageindex-notebooklm/app/page.tsx
  • kits/assistant/pageindex-notebooklm/components/DocumentList.tsx
  • kits/assistant/pageindex-notebooklm/components/DocumentUpload.tsx
  • kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx

@Skt329
Copy link
Copy Markdown
Author

Skt329 commented Mar 30, 2026

fix(TreeViewer): move useMemo above try/catch to satisfy Rules of Hooks

Problem

useMemo was called on line 216 — after a conditional early return inside the catch block (lines 207–214). This violates React's Rules of Hooks:

Hooks must be called unconditionally on every render. React tracks hooks by call order; when buildTree throws, the early return skips useMemo, causing a hook count mismatch on the next successful render.

Concretely:

  • Render N: buildTree throws → early return → useMemo not called (0 hooks)
  • Render N+1: buildTree succeeds → useMemo called (1 hook)
  • React panics: "Rendered more hooks than during the previous render"

Fix

Moved highlightedIdSet declaration to the top of the component body, before the try/catch, so it is always executed unconditionally:

export default function TreeViewer({ tree, fileName, highlightedIds }: Props) {
  // Must be declared before any early return to satisfy Rules of Hooks
  const highlightedIdSet = useMemo(() => new Set(highlightedIds), [highlightedIds]);

  let resolvedRoots: TreeNodeResolved[];
  try {
    resolvedRoots = buildTree(tree);
  } catch (err) {
    // early return is now safe — all hooks already ran
    return <div>Invalid tree structure...</div>;
  }
  ...
}

Impact: No behaviour change on the happy path. Eliminates a class of hook-order crash that would surface in production when a malformed tree is uploaded followed by a valid one.

Copy link
Copy Markdown
Contributor

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

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

Inline comments:
In `@kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx`:
- Around line 121-122: The hover-only background change is applied on the inner
div via onMouseEnter/onMouseLeave so keyboard users won't see it when the
focusable element (the button) receives focus; update the styling to also apply
the same visual state for keyboard focus by adding a :focus-visible or
:focus-within rule (or a focus style on the button) that sets background to
var(--surface-2) when not isHighlighted, and ensure the logic that currently
checks isHighlighted (refer to the inner div mouse handlers and the button
element) is respected so the focus style matches the hover appearance for
keyboard navigation.
- Around line 91-109: Add an explicit type attribute to the toggle button in
TreeViewer.tsx: update the button element (the onClick/onKeyDown toggle handler
that uses hasChildren and setOpen) to include type="button" so it won't default
to type="submit" if the component is rendered inside a form.
- Around line 219-220: The totalNodes function (const totalNodes = (nodes:
TreeNodeResolved[]) => ...) is recreated each render; move or memoize its result
to avoid re-computation for large trees — either hoist the pure recursive helper
out of the component or wrap the computed total in a React.useMemo that depends
on the root nodes array (e.g., useMemo(() => totalNodes(rootNodes),
[rootNodes])); ensure you reference the existing totalNodes function name and
the TreeNodeResolved type when making the change.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: f3b31040-7997-4507-b329-5028a344715a

📥 Commits

Reviewing files that changed from the base of the PR and between 8f86dab and 711ee8a.

📒 Files selected for processing (1)
  • kits/assistant/pageindex-notebooklm/components/TreeViewer.tsx

…ization with cycle detection and highlighting support
@Skt329
Copy link
Copy Markdown
Author

Skt329 commented Mar 30, 2026

fix(TreeViewer): button type, keyboard focus parity, and hoist totalNodes

Three minor issues addressed — all verified against the current code before fixing.


1. type="button" on the toggle button (Minor)

Buttons without an explicit type default to type="submit" inside a <form>, which would trigger form submission instead of toggling the node. Simple defensive fix.

// before
<button onClick={...}>

// after
<button type="button" onClick={...}>

2. Keyboard focus has no hover visual (Trivial — accessibility)

The hover background effect was applied via onMouseEnter/onMouseLeave on the inner div, but the focusable element is the button. Tab-navigating users received no visual feedback beyond the browser's native focus ring (no hover background). Added onFocus/onBlur on the button, mirroring the existing mouse handler pattern and respecting the isHighlighted guard:

onFocus={(e) => {
  if (!isHighlighted)
    (e.currentTarget.firstElementChild as HTMLElement).style.background = "var(--surface-2)";
}}
onBlur={(e) => {
  if (!isHighlighted)
    (e.currentTarget.firstElementChild as HTMLElement).style.background = "transparent";
}}

3. totalNodes hoisted to module level (Trivial — code quality)

The function was a pure recursive helper with no closure dependencies, yet was redefined on every render. Hoisted to module level — same call site, zero behaviour change, no wasted allocations per render.

// module level — defined once
function totalNodes(nodes: TreeNodeResolved[]): number {
  return nodes.reduce((acc, n) => acc + 1 + totalNodes(n.nodes), 0);
}

export default function TreeViewer(...) {
  // ...
  // call unchanged
  totalNodes(resolvedRoots)
}

All three findings were verified against the actual code before applying any change.

@Skt329
Copy link
Copy Markdown
Author

Skt329 commented Mar 30, 2026

fix(flow-1/codeNode_save): surface Supabase write failures + fix resolver_url typo

Verified against the live YAML config. Two bugs found in codeNode_save — only this node needed changes, all other nodes are correct.


Bug 1 — Typo: resolver_urlresolved_url (pre-existing, unrelated to PR)

// before — undefined on every run
const fileUrl = {{codeNode_630.output.resolver_url}}

// after
const fileUrl = {{cparams.output.resolved_url}};

resolver_url does not exist on codeNode_630's output. This meant fileUrl was undefined on every upload, so file_url was stored as null in Supabase for every document.


Bug 2 — Silent failure: status always "completed" (the reported issue)

The original output block hardcoded status: "completed" regardless of response.ok. A 401 (bad key), 409 (conflict), or 500 (server error) from Supabase would silently appear as a successful upload in DocumentUpload.tsx.

Fixed by:

  • Checking response.ok before accessing result[0] (safe — Supabase returns a non-array body on error)
  • On failure: status = "failed", success = false, error populated from result.message || result.error || result.hint || response.statusText
  • throw new Error(...) so the flow node actually fails and the error propagates to the frontend
  • On success: doc_id taken from result[0]?.doc_id (the DB-persisted value, not just the locally generated one)

Other nodes reviewed and unchanged: codeNode_630, extractFromFileNode_1, codeNode_format, InstructorLLMNode_tree, variablesNode_617, responseNode_triggerNode_1 — all correct as-is.

@Skt329
Copy link
Copy Markdown
Author

Skt329 commented Mar 30, 2026

fix(flow-1/codeNode_save + flow-4/meta.json): correct raw_text source + trailing newline


1. flow-4/meta.json — trailing newline (Trivial)

File ended at } with no newline (0x7D confirmed via byte check). Added a single trailing newline to satisfy Unix text file conventions and silence linter warnings. No functional change.


2. flow-1/codeNode_save — wrong raw_text saved to Supabase (Critical)

Verified against both the config.json and the live YAML. The bug was confirmed:

// BEFORE — saves the raw page array from extractFromFileNode
const rawText = {{extractFromFileNode_1.output.files[0].data}};

// AFTER — saves the [PAGE N]-annotated string built by codeNode_format
const rawText = {{codeNode_format.output.raw_text}};

Impact of the bug: files[0].data is a JavaScript array of page strings. When serialized via JSON.stringify(payload), Supabase stores ["page1 text", "page2 text", ...] in the raw_text text column. The chat/retrieval flow searches for [PAGE N] markers that never exist, breaking all document Q&A for every upload.

Why the fix is safe — nothing else needs to change:

  • codeNode_format runs before codeNode_save in the pipeline, so codeNode_format.output.raw_text is already available
  • Supabase schema (raw_text text null) is correct as-is — only the data being stored was wrong
  • No other nodes, edges, or schema changes required
  • Sanitize logic updated from array.forEachsanitize(string) to match the new string type

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