-
Notifications
You must be signed in to change notification settings - Fork 1
Vibe/threadrefactor | Add vector RAG and document outline rendering #600
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
…tant” can be scope-filtered)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/pro-web/components/routes/chat/prompt-form/index.tsx (1)
96-105: SSR/CSR mismatch risk:new Date()at module level.The change to a constant ID is good for SSR stability. However,
expires: new Date().toISOString()at line 103 will still produce different values between server and client renders, potentially causing hydration warnings.Consider initializing
expireslazily or using a placeholder value since this default is immediately overwritten intriggerNewTextFileDialog(line 287) anyway.Suggested fix
const DEFAULT_FILE_ATTACHMENT: FileAttachment = { id: 'thread-context-default', name: 'Thread Context.txt', url: DEFAULT_TEXT_FILE_BASE64_CONTENT, contentType: 'text/plain', content: DEFAULT_TEXT_FILE_BASE64_CONTENT, messageIds: [], - expires: new Date().toISOString(), + expires: '', // Set at runtime in triggerNewTextFileDialog size: DEFAULT_TEXT_FILE_CONTENT.length, }
🤖 Fix all issues with AI agents
In @apps/hasura/migrations/masterbots/1766624501677_assistant_answers/down.sql:
- Around line 1-2: The leading comment in the migration is misleading—remove or
update the comment that says "drop index first (name it explicitly in up.sql so
this is stable)" because the migration only runs "DROP TABLE IF EXISTS
public.assistant_answers;" and PostgreSQL automatically removes indexes with the
table; either delete the comment or replace it with an accurate note (e.g.,
"indexes are dropped with the table; explicit index drops not required") so
future maintainers aren’t confused; target the comment immediately above the
DROP TABLE statement in this migration.
In
@apps/hasura/migrations/masterbots/1766957388317__op_meta_and_scoped_rag/up.sql:
- Around line 92-94: The WHERE clause comparing aa.scope_id = p_scope_id
incorrectly excludes rows when both are NULL; update the condition to use
NULL-safe comparison (aa.scope_id IS NOT DISTINCT FROM p_scope_id) so rows with
both NULL scope_id (used for the doc_root scope_type) are preserved; apply this
change in the query that includes the predicates aa.thread_id = p_thread_id AND
aa.scope_type = p_scope_type AND aa.scope_id = p_scope_id.
In
@apps/hasura/migrations/masterbots/1767498310895_add_public_message_docid/up.sql:
- Around line 38-43: Remove the garbled comment artifact
"contentReference[oaicite:0]{index=0}" and guard the two index creations
(assistant_answers_thread_doc_scope_idx and
assistant_answers_thread_doc_scope_turn_idx) with a defensive check that the
assistant_answers table actually has both scope_type and scope_id columns;
implement this by wrapping the CREATE INDEX statements in a conditional DO block
or IF EXISTS query that checks information_schema.columns (or pg_catalog) for
both column names on table assistant_answers and only runs the CREATE INDEX ...
statements when both columns are present.
In @apps/pro-web/app/actions/chat-memory/embedding.ts:
- Around line 10-34: The embedText1536 function lacks the input validation used
in the sibling implementation, so add trimming and empty-string handling at the
top of embedText1536: derive text = (input ?? '').trim() and if text is empty
return [] before fetching the OpenAI embeddings; this prevents calling the API
for empty/whitespace inputs and keeps the returned type consistent.
In @apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts:
- Around line 155-173: The catch block currently swallows the original error
when it isn't a schema-lag case; change it to preserve and surface that error:
capture the caught error in the catch (error) block, only attempt the retry
branch when docNodeId && error.message indicates schema issues (as currently
done for doc_node_id/field/assistant_answers_insert_input), but if the message
does not match those conditions rethrow the original error (or at minimum log
it) instead of returning; additionally, if the retry path is taken and the retry
fails, rethrow or attach both the retry error and the original error so callers
see both contexts (refer to docNodeId, baseObject, hasuraGql, and insert to
locate and update the relevant logic).
In @apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts:
- Around line 365-372: The normalize function is using an unsafe double-cast
("as unknown as AssistantAnswerMemory[]") which hides type mismatch between
SearchRow and AssistantAnswerMemory; remove the cast and return a properly typed
array instead (update normalize to have a return type of AssistantAnswerMemory[]
and let the mapped object conform to that type), ensuring the mapped properties
message_id, turn_index, content, cosine, and created_at are renamed/typed to
messageId, turnIndex, content, cosine, and createdAt to match
AssistantAnswerMemory.
- Around line 81-84: The estimateTokens function returns 0 for empty strings
which is inconsistent with the rest of the codebase; update estimateTokens to
never return 0 by clamping the result to a minimum of 1 (i.e., use Math.max(1,
Math.ceil((text ?? '').length / 4))) or, better, import and reuse the shared
token-estimation implementation used elsewhere to ensure consistency with
existing token budget logic; locate the function named estimateTokens in
retrieve-assistant-answer-memories.ts and replace its body or import the
canonical estimator used in simple-rag/prompt.ts.
- Around line 123-134: tryExtractExpectedType builds a RegExp by interpolating
varName directly which can introduce a ReDoS risk if varName contains regex
meta-characters; escape/sanitize varName before using it in the pattern (e.g.,
implement and use an escapeRegExp that backslashes regex metacharacters, or
otherwise construct the pattern without direct interpolation), then use the
escaped value when creating the RegExp in tryExtractExpectedType so the pattern
is safe and deterministic.
In @apps/pro-web/components/routes/chat/chat-list/index.tsx:
- Around line 109-114: The double type assertion on chatMessages and
previousChatMessages hides type errors; create a normalizeMessages(raw:
unknown[] | undefined): ThreadMessage[] helper that validates and maps each raw
item into a ThreadMessage (perform runtime checks for required keys like
messageId/id, content, role and provide sane defaults or skip invalid items),
replace the direct casts in the component to call normalizeMessages(messages)
and normalizeMessages(activeThread?.thread?.messages), and update the messages
prop usage so the component receives/handles unknown[] safely instead of using
"as unknown as ThreadMessage[]".
- Around line 117-118: The expression using "as unknown as any" to derive
threadAttachments is unsafe; instead ensure activeThread has a proper typed
interface (e.g., a ChatThread type with metadata?: { attachments?:
FileAttachment[] }) or perform a runtime type guard before casting: narrow
activeThread to the correct shape (or implement an isFileAttachmentArray(value):
value is FileAttachment[] check that validates Array.isArray and expected
fields) and then read metadata?.attachments as FileAttachment[] only after the
guard. Update the threadAttachments declaration to use the narrowed type or
guard so you avoid the double any cast and preserve type safety.
In
@apps/pro-web/components/routes/workspace/document-tab/document-thread-outline.tsx:
- Around line 113-117: The component returns early before calling the React hook
useThread(), violating the Rules of Hooks; move the call to useThread() (and any
other hooks like useMemo for validScopeIds) to the top of the component so hooks
are invoked unconditionally, then perform the conditional early return based on
outline (i.e., call useThread() first, then if (!outline?.length) return null,
and compute validScopeIds via useMemo(buildValidScopeIdSet, [outline]) after
hooks are declared).
In
@apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx:
- Around line 244-278: The debounced save timer stored in sectionSaveTimerRef
(used by scheduleSectionSave) isn't cleared on unmount; add a useEffect that
returns a cleanup function which checks sectionSaveTimerRef.current, calls
clearTimeout on it and sets it to null so no pending timeout fires after the
component unmounts (ensure the effect has no deps or uses refs only so it runs
once).
- Around line 304-324: The Sections/Source buttons in
WorkspaceDocumentTextEditor read viewMode for styling but never change it; pass
the parent's handleViewSourceToggle prop into WorkspaceDocumentTextEditor and
call handleViewSourceToggle('sections') for the Sections button and
handleViewSourceToggle('source') for the Source button (you can still call
setIsPreview(false) as before). Update the component signature to accept
handleViewSourceToggle and replace the onClick handlers that currently only call
setIsPreview with calls that invoke handleViewSourceToggle with the correct
mode.
In @apps/pro-web/components/shared/shared-accordion.tsx:
- Line 374: The current assignment to the local variable "category" can throw
when chatbot.categories is an empty array; change the access to safely handle
empty arrays by using optional chaining on the index (e.g.,
thread?.chatbot?.categories?.[0]?.category?.name) or guard with a length check
(e.g., const categories = thread?.chatbot?.categories; const category =
categories && categories.length ? categories[0].category?.name : undefined) so
that accessing categories[0] never throws; update the assignment where
"category" is declared.
In @apps/pro-web/lib/hooks/use-workspace-chat.tsx:
- Around line 643-645: The template literal assigned to newContentMarkdown
includes unintended tab characters and indentation around the newline, which
will embed tabs into the document content; update the assembly of
newContentMarkdown (using preservedBeforeSelectionRef.current, cleanedResponse,
preservedAfterSelectionRef.current) so there are no leading tabs or indentation
in the template—either remove the tabs in the backtick literal or build the
string with explicit newlines (e.g., concatenate with "\n\n") or trim the pieces
before joining to ensure clean, unindented content.
- Around line 745-746: The fix: stop calling useWorkspace() and useThread()
inside the async handler handleWorkspaceEdit; instead use the instances already
obtained at the top-level of the provider and add the missing methods to that
destructure. Update the top-level destructure of useWorkspace() to include
toggleWorkspace and the top-level destructure of useThread() to include
setIsOpenPopup (or their exact internal names), then replace the in-handler
calls in handleWorkspaceEdit with those top-level variables so no hooks are
invoked inside the event handler.
🧹 Nitpick comments (46)
packages/mb-env/src/config.env.ts (2)
46-56: Consider adding NaN fallback protection for numeric parsing.If environment variables are set to non-numeric values (e.g.,
NEXT_PUBLIC_MB_SIMPLE_RAG_K="abc"),Number.parseIntandNumber.parseFloatreturnNaNinstead of the intended default. This could cause unexpected behavior downstream.While this follows the existing pattern in the file (e.g.,
experimentalStreamDelayMs,maxAttachments), you may want to add fallback protection for these new RAG parameters since they directly affect retrieval quality.♻️ Optional: Add NaN fallback protection
- simpleRagK: Number.parseInt( - process.env.NEXT_PUBLIC_MB_SIMPLE_RAG_K || '3', - 10, - ), - simpleRagMinCosine: Number.parseFloat( - process.env.NEXT_PUBLIC_MB_SIMPLE_RAG_MIN_COSINE || '0.60', - ), - simpleRagTokenBudget: Number.parseInt( - process.env.NEXT_PUBLIC_MB_SIMPLE_RAG_TOKEN_BUDGET || '600', - 10, - ), + simpleRagK: + Number.parseInt(process.env.NEXT_PUBLIC_MB_SIMPLE_RAG_K || '3', 10) || 3, + simpleRagMinCosine: + Number.parseFloat(process.env.NEXT_PUBLIC_MB_SIMPLE_RAG_MIN_COSINE || '0.60') || 0.6, + simpleRagTokenBudget: + Number.parseInt(process.env.NEXT_PUBLIC_MB_SIMPLE_RAG_TOKEN_BUDGET || '600', 10) || 600,
58-59: Clean up internal tracking comment.The
(Commit 10)reference in the comment appears to be an internal development tracking artifact. Consider removing it for cleaner documentation.🧹 Suggested cleanup
- // Chat observability (Commit 10) + // Chat observabilityapps/pro-web/lib/markdown-utils-commit10testarchive.ts (2)
1-6: Non-standard test file naming.The file name
markdown-utils-commit10testarchive.tsdoesn't follow the*.test.tsconvention. This may prevent test runners from discovering these tests automatically. The "commit10testarchive" suffix also suggests this might be temporary/archive code.Consider renaming to
markdown-utils.test.tsor removing if this is archived code that shouldn't be committed. Based on learnings, test files should follow the*.test.tsor*.spec.tspattern.
15-49: Test logic is sound.The test follows the AAA pattern correctly and the null guard on line 39 is good defensive practice. The assertions comprehensively verify subtree boundaries.
Minor: The type assertion
as (typeof sections)[number]on line 35 is redundant sincebyTitlealready returns the matching element type.Optional: Remove redundant type assertion
- const a = byTitle(sections, 'A') as (typeof sections)[number] + const a = byTitle(sections, 'A')vitest.config.ts (1)
4-13: Consider whether to integratevite-tsconfig-pathswith root tsconfig configuration.The
vite-tsconfig-pathspackage is present in devDependencies but not used. However, leveraging it would require adding path aliases to the roottsconfig.json. Currently, the manual alias approach is straightforward and doesn't require additional tsconfig changes. If adoptingvite-tsconfig-paths, ensure the roottsconfig.jsonincludes the@path alias to keep configuration centralized and avoid duplication.apps/pro-web/lib/queries/use-workspace-structure.ts (1)
347-359: Consider updating theWorkspaceDocumentMetadatatype to includedocNodeId.The
as anycasts on lines 352 and 359 bypass type checking. SincedocNodeIdis now a computed property on documents, adding it to the type definition would improve type safety and editor support.♻️ Suggested type update
In
@/types/thread.types(or whereverWorkspaceDocumentMetadatais defined):export interface WorkspaceDocumentMetadata { id: string name: string project: string // ... other fields + docNodeId?: string | null }Then remove the
as anycasts:finalStructure.documents = Array.from(mergedDocuments.values()).map( (doc) => ({ ...doc, docNodeId: - (doc as any).docNodeId ?? + doc.docNodeId ?? getCanonicalDocNodeId({ docId: doc.id, project: doc.project, documentName: doc.name, }), }), -) as any +)apps/pro-web/components/routes/workspace/document-tab/workspace-document-section-tree.tsx (1)
57-63: Inline function in map callback creates new function per render.The
handleDeleteadapter is defined inside themapcallback, creating a new function instance for each node on every render. While this works correctly, it could cause unnecessary re-renders ofWorkspaceDocumentSectionItemif it usesReact.memo.For a non-blocking improvement, consider extracting the adapter logic or using
useCallbackif performance becomes a concern. The current implementation is functionally correct.apps/pro-web/components/routes/chat/chat-list/message-pairs.tsx (1)
186-208: Consider simplifying the no-op outline branch.Both
shouldRenderOutlinebranches return the sameflatJSX. While the comment explains this is intentional scaffolding, the conditional structure is misleading. Consider simplifying:♻️ Suggested simplification
- // ✅ Branch gate: outline rendering only when explicitly enabled AND we have a tree. - // Non-doc threads never pass outlineTree, so they always use flat rendering. - const shouldRenderOutline = Boolean( - enableOutlineRendering && outlineTree?.length, - ) - - const flat = ( + // TODO: When outline rendering is implemented, use `enableOutlineRendering && outlineTree?.length` + // to branch between flat and outline rendering. For now, always use flat rendering. + return ( <> {previousPairsElements} {showSeparator && ( <Separator className="relative my-6 h-0.5 z-[2] rounded-sm bg-iron dark:bg-mirage" /> )} {currentPairsElements} </> ) - - // For now, outline path intentionally uses flat rendering to avoid regressions. - // Next step: replace this with recursive outline rendering rules (children accordions first, then node messages). - if (shouldRenderOutline) { - return flat - } - - return flat }apps/web/components/shared/shared-accordion.tsx (3)
98-99: Consider removing unused destructured variable.The
searchTermis destructured but marked as unused with_searchTerm. If it's not needed now, consider removing it entirely to reduce noise. Biome may also flag this.♻️ Suggested change
- // currently unused but kept to avoid behavior regressions (future: highlight matches) - const { searchTerm: _searchTerm } = useThreadSearch() + // Note: useThreadSearch().searchTerm available for future highlight matches feature + useThreadSearch()
248-260: Guard against missing threadSlug is good, but return value is confusing.The guard correctly prevents navigation with invalid data. However, returning
thread(the partial prop) when slug is missing is semantically confusing sinceupdateActiveThreadtypically returns the thread on success. Consider returningundefinedornullfor consistency with error cases.♻️ Suggested change
// Guard: avoid url builder errors + accidental navigation to "/" if (!resolvedThreadSlug) { if (appConfig.features.devMode) { console.error('updateActiveThread: missing threadSlug for navigation', { threadId: thread.threadId, fullThreadSlug: ft?.slug, fullThreadThreadSlug: ft?.thread?.slug, threadPropSlug: asThreadLike(thread)?.slug, paramsThreadSlug: params.threadSlug, }) } - return thread + return undefined }
336-346: Duplicate slug resolution and validation pattern.Lines 336-346 and 380-390 contain nearly identical slug resolution and validation logic. Consider extracting to a helper function to reduce duplication.
♻️ Optional: Extract shared validation logic
const validateAndGetSlug = ( thread: SharedAccordionProps['thread'], context: string, ): string | null => { const resolvedSlug = asThreadLike(thread)?.slug ?? asString(params.threadSlug) if (!resolvedSlug && appConfig.features.devMode) { console.error(`Missing required parameters for ${context}: threadSlug`) } return resolvedSlug ?? null }Also applies to: 380-390
apps/pro-web/components/routes/chat/chat-list/message-pair-accordion.tsx (2)
40-44: Type assertion inasThreadLikebypasses type safety.The
asThreadLikehelper usesas ThreadLikewhich doesn't validate the object's shape at runtime. If the object has unexpected types (e.g.,slugis a number), the code will fail silently downstream.Consider adding minimal runtime validation for critical fields if this data comes from untrusted sources, or document that the caller is responsible for ensuring shape conformance.
217-221: Dead code paths due toshouldShowUserMessage = true.With
shouldShowUserMessagehardcoded totrue, the following code is unreachable:
- Lines 246-251: CSS classes for
!shouldShowUserMessagecases- Lines 269-292: The first branch of
AnimatePresence(tooltip for continued threads)Consider removing these dead code paths to improve maintainability, or add a comment explaining if this is intentional scaffolding for future changes.
♻️ Suggested cleanup for dead code
- // Safety path (kept, but should never hit with shouldShowUserMessage=true) - 'bg-transparent dark:bg-transparent w-auto h-auto px-4 py-0 border-none ml-auto': - !shouldShowUserMessage, - 'w-full justify-between': - !shouldShowUserMessage && isPrevious && !isAccordionFocused,And consider simplifying the AnimatePresence section since the first branch never renders.
Also applies to: 246-251, 269-292
apps/pro-web/lib/hooks/__test__/simple-rag-prompt.test.ts (1)
7-96: Consider adding edge case tests.The test suite covers the main functionality well. Consider adding tests for:
- Empty
userQuestionChainandretrievedAnswers- All items below
minCosinethreshold (should return empty retrieval block)- Items with empty/whitespace-only content (should be filtered)
These are optional but would improve robustness.
apps/pro-web/lib/select-scope-conversation-window.ts (2)
232-235: Silent error handling loses diagnostic context.The
catchblock returns an empty result without logging. While fail-open is appropriate for prompt assembly, consider adding a warning log for observability:♻️ Add warning log for error visibility
- } catch { - // Fail-open: never block prompt assembly. - return { userQuestions: [], lastAssistant: null, lastUser: null } + } catch (error) { + // Fail-open: never block prompt assembly, but log for observability. + console.warn('[selectScopeConversationWindow] Query failed, returning empty window:', error) + return { userQuestions: [], lastAssistant: null, lastUser: null } }
192-193: Type casts bypass mb-genql type checking on dynamically constructed where clauses.The
as unknown as neverpattern on lines 192, 205, and 219 suppresses TypeScript errors for runtime-built where objects. While this works and is more type-safe than plainany(as used inhasura.service.ts), it sacrifices the type-safety benefits mb-genql provides. Consider creating typed where clause builders or a generic where type if mb-genql's constraints allow, but the current approach is acceptable given the dynamic nature of scope building.apps/pro-web/lib/markdown-utils.ts (1)
143-146: Theoretical infinite loop risk if ID space is exhausted.While extremely unlikely in practice (36^10 possible IDs), the
do-whileloop has no upper bound. Consider adding a safety counter for defensive coding.💡 Optional: Add safety bound
if (!finalId) { - // Insert a new marker line + // Insert a new marker line (safety bound to avoid infinite loop) + let attempts = 0 do { finalId = generateShortSectionId() - } while (used.has(finalId)) + attempts++ + } while (used.has(finalId) && attempts < 1000) used.add(finalId)apps/pro-web/components/routes/thread/thread-popup.tsx (1)
128-131: Unsafe type assertions bypass type checking.Using
(threadMeta as any)loses type safety. Consider defining a proper interface for thread metadata or using a type guard.💡 Suggested fix: Define metadata interface
+interface ThreadMetadata { + docNodeId?: string + doc_node_id?: string + documentMarkdown?: string + markdown?: string + docMarkdown?: string + content?: string + documents?: Array<{ content?: string; markdown?: string; docMarkdown?: string }> +} + +function isThreadMetadata(meta: unknown): meta is ThreadMetadata { + return meta !== null && typeof meta === 'object' +} + const threadMeta = (currentThread?.metadata as unknown) ?? null -const docNodeId = pickString( - (threadMeta as any)?.docNodeId, - (threadMeta as any)?.doc_node_id, -) +const typedMeta = isThreadMetadata(threadMeta) ? threadMeta : null +const docNodeId = pickString( + typedMeta?.docNodeId, + typedMeta?.doc_node_id, +)apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx (2)
509-525: Props definition should useinterfaceper coding guidelines.As per coding guidelines, "Use interface for objects/classes; type for unions/tuples/aliases". Since this is a props object, it should be an interface.
💡 Suggested fix
-export type WorkspaceTextEditorProps = { +export interface WorkspaceTextEditorProps { activeSection: string | null setActiveSection: (sectionId: string | null) => void viewMode: 'sections' | 'source' sectionTextareaRef: React.RefObject<HTMLTextAreaElement> sourceTextareaRef: React.RefObject<HTMLTextAreaElement> handleCursorPositionChange: (e?: React.SyntheticEvent) => void handleSectionClick: (sectionId: string) => void markUserTyping: () => void handleExpandSection: (sectionTitle: string) => void handleRewriteSection: (sectionTitle: string) => void }
505-507: Consider extractingclampto a shared utility file if planning to reuse across the codebase.Currently, a generic
clamputility only exists in this file. While it's a common utility pattern, extraction toapps/pro-web/lib/utils.tswould be beneficial for future reuse, though not immediately necessary given the single usage.apps/pro-web/app/actions/chat-memory/embed-text-1536.ts (2)
17-29: Consider adding a timeout for the OpenAI API call.The fetch call has no timeout configured. OpenAI API calls can occasionally hang, which could cause the server action to block indefinitely.
♻️ Suggested fix with AbortController timeout
+ const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 30_000) + const res = await fetch('https://api.openai.com/v1/embeddings', { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ model: EMBEDDING_MODEL, input: text, encoding_format: 'float', }), cache: 'no-store', + signal: controller.signal, }) + + clearTimeout(timeoutId)
38-40: Consider validating the embedding dimension.The function name
embedText1536implies a 1536-dimension vector, but there's no validation that the returned embedding matches this expectation. If OpenAI changes the model output or theEMBEDDING_MODELconstant is updated, this could silently return unexpected dimensions.♻️ Optional dimension validation
const json = (await res.json()) as OpenAIEmbeddingsResponse const vec = json.data?.[0]?.embedding ?? [] + if (vec.length > 0 && vec.length !== 1536) { + console.warn(`Unexpected embedding dimension: ${vec.length}, expected 1536`) + } return vecapps/pro-web/services/hasura/simple-rag.ts (1)
7-25: Add explicit return type annotation.Per coding guidelines, function return types should be explicitly annotated. The function implicitly returns
Promise<void>.♻️ Add return type
-export async function ingestAssistantAnswer(input: IngestAssistantAnswerInput) { +export async function ingestAssistantAnswer(input: IngestAssistantAnswerInput): Promise<void> {apps/pro-web/app/actions/chat-memory/embedding.ts (1)
1-43: Consolidate embedding utilities to avoid dual implementations with diverging quality.Two implementations of
embedText1536exist with different features: the utility version inembedding.tslacks input validation and enhanced error handling, while the server action inembed-text-1536.tsincludes these improvements. This divergence creates maintenance burden and the risk of using the weaker implementation.Recommend making
embed-text-1536.tsthe canonical implementation (it's already marked 'use server' and includes validation) and haveembedding.tsre-export from it for utilities, or move the enhanced logic directly intoembedding.tsif both client and server paths need it.apps/pro-web/types/operation-meta.ts (2)
13-31: Consider making derived fields optional if they may not always be present.The comment indicates these are "Derived fields (no classifier)" - if these fields are computed and may not always be available during construction, consider making them optional:
// Derived fields (no classifier) -intent_source: IntentSource -exclude_from_q_chain: boolean -rag_eligible: boolean +intent_source?: IntentSource +exclude_from_q_chain?: boolean +rag_eligible?: booleanIf they're always guaranteed to be present, the current definition is appropriate.
5-5: CentralizeScopeTypeto prevent duplication and drift.
ScopeTypeis duplicated across four files:
apps/pro-web/types/operation-meta.ts(line 5) — already in the correct central locationapps/pro-web/lib/select-scope-conversation-window.ts(line 4)apps/pro-web/services/hasura/hasura.service.ts(line 782)apps/pro-web/components/routes/pro/chat-panel-pro.tsx(line 55)Export
ScopeTypefromoperation-meta.tsand import it in the other files to maintain a single source of truth and align with the guideline of placing shared TypeScript definitions undertypes/.apps/pro-web/services/hasura/hasura.service.ts (1)
782-782: ImportScopeTypefrom centralized types instead of redefining.This duplicates the
ScopeTypedefinition fromapps/pro-web/types/operation-meta.ts. Consider importing it:+import type { ScopeType } from '@/types/operation-meta' -type ScopeType = 'chat_thread' | 'doc_root' | 'heading_thread'apps/hasura/migrations/masterbots/1766629067789_assistant_answers_fn/up.sql (1)
35-55: GraphQL wrapper is appropriate; consider input validation.The text-to-vector cast at Line 50 will fail at runtime if
p_query_vec_textisn't a valid 1536-dimensional vector string. This is acceptable for internal use but could benefit from a guard if exposed to untrusted input.Optional: Add dimension validation
-- If defensive validation is desired: CREATE OR REPLACE FUNCTION public.search_assistant_answers_gql(...) AS $$ SELECT * FROM public.search_assistant_answers( p_thread_id, CASE WHEN array_length(string_to_array(trim(both '[]' from p_query_vec_text), ','), 1) = 1536 THEN p_query_vec_text::vector(1536) ELSE NULL END, ... ); $$;apps/pro-web/components/routes/workspace/document-tab/section-subaccordion-row.tsx (1)
42-89: Component structure is clean; consider extracting onClick handler.The component logic is well-organized with proper memoization and defensive checks. However, the inline arrow function at Line 81 could be extracted per coding guidelines.
As per coding guidelines, prefer stable function references for event handlers:
Extract onClick handler
export function SectionSubaccordionRow({ nodes, maxPreview = 3, renderNode, className, }: SectionSubaccordionRowProps) { const [expanded, setExpanded] = useState(false) + + function handleExpand() { + setExpanded(true) + } // ... rest of component {hasOverflow && !expanded ? ( <Button type="button" variant="ghost" size="sm" className="h-7 px-2 text-xs" - onClick={() => setExpanded(true)} + onClick={handleExpand} > See more… </Button> ) : null}apps/pro-web/components/routes/pro/chat-panel-pro.tsx (3)
275-281: Multipleas anycasts indicate missing type definitions.The chained fallbacks with
as anycasts suggestcurrentDocumentDatalacks proper type definitions fordocNodeIdandidfields. This should be addressed in the query types.Add proper typing to document data
Consider extending the
useDocumentContentreturn type to includedocNodeIdandid:// In the query types file interface DocumentContentData { content: string sections?: MarkdownSection[] docNodeId?: string id?: string }
345-361: IIFE pattern works but could be simplified.The immediately-invoked function expression for
scopecomputation works correctly but adds cognitive overhead. A simpler conditional assignment would be clearer.Simplify scope computation
- const scope = - scopeType !== 'chat_thread' - ? (() => { - const fullMarkdown = currentDocumentData?.content ?? '' - const sections = - currentDocumentData?.sections ?? - (fullMarkdown ? parseMarkdownSections(fullMarkdown) : undefined) - - return { - scopeType, - scopeId, - docNodeId, - fullMarkdown, - sections, - } - })() - : undefined + let scope: typeof undefined | { + scopeType: ScopeType + scopeId: string | null + docNodeId: string | null + fullMarkdown: string + sections?: MarkdownSection[] + } + + if (scopeType !== 'chat_thread') { + const fullMarkdown = currentDocumentData?.content ?? '' + const sections = + currentDocumentData?.sections ?? + (fullMarkdown ? parseMarkdownSections(fullMarkdown) : undefined) + + scope = { scopeType, scopeId, docNodeId, fullMarkdown, sections } + }
54-55: ImportActionTypeandScopeTypefrom shared types to avoid duplication.Lines 54–55 locally define types that already exist in
apps/pro-web/types/operation-meta.ts. Import them instead to maintain consistency across the codebase.Suggested import
+import type { ActionType, ScopeType } from '@/types/operation-meta' // ... -type ActionType = 'ask' | 'rewrite' | 'expand' | 'edit' -type ScopeType = 'chat_thread' | 'doc_root' | 'heading_thread'apps/pro-web/lib/hooks/use-workspace-chat.integration.test.tsx (1)
119-121:useAsyncmock may need adjustment if tests expand.Returning an empty object works for current tests but may cause issues if future tests depend on
useAsyncreturn values likeloading,error, orvalue.Consider a more complete mock shape
vi.mock('react-use', () => ({ - useAsync: () => ({}), + useAsync: () => ({ + loading: false, + error: undefined, + value: undefined, + }), }))apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts (3)
6-9: Duplicate type definition detected.
AssistantAnswerScopeTypeis defined identically inretrieve-assistant-answer-memories.ts(lines 32-35). Consider extracting this type to a shared types file (e.g.,./types.ts) and importing from there to maintain a single source of truth.♻️ Suggested refactor
Create a shared types file:
// apps/pro-web/app/actions/chat-memory/types.ts export type AssistantAnswerScopeType = | 'heading_thread' | 'doc_root' | 'chat_thread'Then import in both files:
-export type AssistantAnswerScopeType = - | 'heading_thread' - | 'doc_root' - | 'chat_thread' +import type { AssistantAnswerScopeType } from './types' +export type { AssistantAnswerScopeType }
104-113: Silent failure on turn index query could mask issues.When the
MaxTurnIndexquery fails, the function returns silently without any logging. This makes debugging difficult if there are persistent issues with the Hasura query. Consider adding a warning log before the early return.♻️ Suggested improvement
} catch { + console.warn('[ingestAssistantAnswer] Failed to fetch max turn index, aborting ingestion') return }
176-213: DuplicatehasuraGqlhelper function.This helper is nearly identical to the one in
retrieve-assistant-answer-memories.ts(lines 514-547). Extract to a shared utility module to avoid code duplication and ensure consistent behavior.♻️ Suggested refactor
Create a shared Hasura utility:
// apps/pro-web/app/actions/chat-memory/hasura-gql.ts 'use server' export async function hasuraGql<TData>( query: string, variables?: Record<string, unknown>, ): Promise<TData> { // ... implementation }Then import in both files:
-async function hasuraGql<TData>( - query: string, - variables?: Record<string, unknown>, -): Promise<TData> { - // ... -} +import { hasuraGql } from './hasura-gql'apps/pro-web/components/routes/chat/chat-list/index.tsx (2)
46-49: Weakened type safety withunknown[]for messages prop.Changing
messagesfrom a typed array tounknown[]reduces type safety. While the comment explains the intent to normalize internally, this pushes runtime errors from compile-time to runtime.Consider using a union type that explicitly supports both message shapes:
+import type { UIMessage } from '@ai-sdk/react' +import type { Message as DBMessage } from 'mb-genql' export interface ChatList { - // Accept both UIMessage[] (from useChat/@ai-sdk/react) and DB Message[] (mb-genql). - // We normalize/cast internally before pairing to preserve existing runtime behavior. - messages?: unknown[] + messages?: UIMessage[] | DBMessage[] | ThreadMessage[]
185-233: ComplexisDocLinkedThreadlogic could benefit from extraction.This 48-line
useMemowith nested helper functions and multiple fallback checks is difficult to test and maintain. Consider extracting to a pure utility function.♻️ Suggested refactor
Extract to a testable utility:
// lib/utils/is-doc-linked-thread.ts export function isDocLinkedThread( metadata: Record<string, unknown> | undefined, messages: unknown[], ): boolean { // Fast signals from thread metadata if (metadata) { const docNodeId = metadata.docNodeId ?? metadata.doc_node_id if (typeof docNodeId === 'string' && docNodeId.trim()) return true const docs = metadata.documents if (Array.isArray(docs) && docs.length > 0) return true } // Message-level anchors return messages.some(hasDocAnchor) } function hasDocAnchor(m: unknown): boolean { // ... extracted logic }Then in the component:
-const isDocLinkedThread = useMemo(() => { - // ... 48 lines -}, [activeThread?.metadata, previousChatMessages, chatMessages]) +const isDocLinkedThread = useMemo( + () => isDocLinkedThreadUtil( + activeThread?.metadata as Record<string, unknown> | undefined, + [...previousChatMessages, ...chatMessages], + ), + [activeThread?.metadata, previousChatMessages, chatMessages], +)apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts (1)
241-320: Retry loop lacks exponential backoff for transient failures.The 6-iteration retry loop handles schema evolution well, but doesn't distinguish between schema mismatches (which should retry immediately with adjusted params) and transient network failures (which should use backoff). Currently, network failures will exhaust all retries instantly.
Consider adding a small delay for non-schema errors:
// After line 318, before throw if (attempt < 5) { await new Promise(r => setTimeout(r, 100 * (attempt + 1))) continue } throw eapps/hasura/migrations/masterbots/1766624501677_assistant_answers/up.sql (1)
59-61: Consider deferring IVFFlatlistsparameter tuning until table size is known.The
ivfflatindex uses the defaultlists = 100, which is reasonable for unknown data volumes. Once the assistant_answers table reaches a stable size, you can optimize thelistsparameter by rebuilding the index with a value based on expected row count (typicallysqrt(row_count)orrow_count / 1000). UseCREATE INDEX CONCURRENTLYduring production rebuilds to avoid blocking writes. This optimization can be performed incrementally as performance data becomes available.apps/pro-web/components/shared/shared-accordion.tsx (1)
278-320: Duplicate guard logic should be consolidated.Lines 278-294 and 296-320 perform the same validation for
resolvedThreadSlug,chatbotName, andcanonicalDomain. The first guard already returns early if any are missing, making the second check unreachable.♻️ Proposed fix: Remove duplicate guard
// Guard: never call navigateTo with missing required params if (!resolvedThreadSlug || !chatbotName || !canonicalDomain) { if (appConfig.features.devMode) { console.error( 'updateActiveThread: missing required params for navigation', { threadId: thread.threadId, chatbotName, canonicalDomain, resolvedThreadSlug, fullThread, thread, paramsThreadSlug: params.threadSlug, }, ) } return thread } - - // Guard: don't call navigateTo with missing required params (url builder logs + returns "/") - const missing: string[] = [] - if (!resolvedThreadSlug) missing.push('threadSlug') - if (!chatbotName) missing.push('chatbot') - if (!canonicalDomain) missing.push('domain') - - if (missing.length) { - if (appConfig.features.devMode) { - console.error( - `updateActiveThread: missing required params for navigation: ${missing.join( - ', ', - )}`, - { - threadId: thread.threadId, - chatbotName, - canonicalDomain, - resolvedThreadSlug, - fullThread, - thread, - paramsThreadSlug: params.threadSlug, - }, - ) - } - return thread - }apps/pro-web/lib/hooks/use-mb-chat.tsx (4)
97-106: Duplicate type definition for MbScopeInput.
MbScopeInputis defined via module augmentation at lines 97-106, then redefined as a local type at lines 331-351. The local definition shadows the augmented interface, which may cause confusion. Consider consolidating into a single exported type.Also applies to: 331-351
134-181: Consider moving types to module level.
OperationMeta,UiMode,ActionType, etc. are defined inside theMBChatProvidercomponent. This works but:
- Types are recreated conceptually on each render (no runtime cost, but unusual)
- Cannot be imported/reused elsewhere
- Makes the component harder to read
Consider extracting these to module level or a shared types file.
466-489: Heavy use ofanyin normalizeToolCall.While the function handles SDK shape variations, the extensive
(toolCall as any)casts reduce type safety. Consider defining a union type for the expected SDK shapes.type SdkToolCall = { toolCallId?: string id?: string toolName?: string name?: string args?: unknown arguments?: unknown function?: { name?: string; arguments?: unknown } }
1488-1493: Silent error swallowing in scoped history fetch.The catch block at lines 1490-1493 silently sets
scopedHistory = nullwithout logging. While fail-open is correct, consider adding a debug log for troubleshooting:} catch { - // fail-open: never block chat if the scoped window query fails - scopedHistory = null + // fail-open: never block chat if the scoped window query fails + if (appConfig.features.devMode) { + console.warn('selectScopeConversationWindow failed, falling back') + } + scopedHistory = null }apps/pro-web/components/routes/workspace/document-tab/document-thread-outline.tsx (1)
94-97: Type is not exported despite being referenced in AI summary.
GroupedPairsResultis declared but not exported. The AI summary indicates it should be exported. If external consumers need this type, add theexportkeyword.-type GroupedPairsResult = { +export type GroupedPairsResult = { pairsByScope: Map<string, MessagePair[]> titleSnapshotByScope: Map<string, string> }
apps/hasura/migrations/masterbots/1766624501677_assistant_answers/down.sql
Outdated
Show resolved
Hide resolved
| WHERE aa.thread_id = p_thread_id | ||
| AND aa.scope_type = p_scope_type | ||
| AND aa.scope_id = p_scope_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/hasura/migrations/masterbots/1766957388317__op_meta_and_scoped_rag/up.sql | head -120 | tail -40Repository: bitcashorg/masterbots
Length of output: 1413
🏁 Script executed:
# Search for the function definition and complete query context
rg -n "scope_id|p_scope_id" apps/hasura/migrations/masterbots/1766957388317__op_meta_and_scoped_rag/up.sql -B5 -A5Repository: bitcashorg/masterbots
Length of output: 3183
🏁 Script executed:
# Check if there are existing patterns for NULL comparisons in other migrations
fd . apps/hasura/migrations/masterbots --type f -name "*.sql" | xargs rg "IS NOT DISTINCT FROM"Repository: bitcashorg/masterbots
Length of output: 297
🏁 Script executed:
# Search for scope_type and doc_root references to understand valid values
rg -n "doc_root|scope_type" apps/hasura/migrations/masterbots/1766957388317__op_meta_and_scoped_rag/up.sql -B2 -A2Repository: bitcashorg/masterbots
Length of output: 2318
🏁 Script executed:
rg "search_assistant_answers_scoped_gql" --type ts --type tsx --type jsRepository: bitcashorg/masterbots
Length of output: 92
🏁 Script executed:
# Also check for any GraphQL queries or mutations that might call this function
rg "searchAssistantAnswersScoped|search_assistant_answers_scoped" apps/pro-web --type ts --type tsx -B3 -A3Repository: bitcashorg/masterbots
Length of output: 92
🏁 Script executed:
# Check the actual query execution in the codebase
rg "scope_id" apps/pro-web/lib --type ts --type tsx -B2 -A2 | head -100Repository: bitcashorg/masterbots
Length of output: 92
🏁 Script executed:
rg "search_assistant_answers_scoped_gql|searchAssistantAnswersScoped" apps/Repository: bitcashorg/masterbots
Length of output: 507
🏁 Script executed:
# Search for scope_id assignments and checks in the codebase
rg "scope_id.*=.*null|scope_id.*=.*NULL" apps/ -i | head -50Repository: bitcashorg/masterbots
Length of output: 738
🏁 Script executed:
# Check the full schema definition for nullable columns
rg "scope_id text" apps/hasura/migrations/ -B2 -A2Repository: bitcashorg/masterbots
Length of output: 3100
Use IS NOT DISTINCT FROM for nullable scope_id comparison.
The condition aa.scope_id = p_scope_id will exclude valid rows when both are NULL. The codebase explicitly allows and uses NULL scope_id for doc_root scope type (as documented in ingest-assistant-answer.ts and retrieve-assistant-answer-memories.ts). This causes silent data loss.
Proposed fix
WHERE aa.thread_id = p_thread_id
AND aa.scope_type = p_scope_type
- AND aa.scope_id = p_scope_id
+ AND aa.scope_id IS NOT DISTINCT FROM p_scope_id
AND (p_exclude_message_id IS NULL OR aa.message_id <> p_exclude_message_id)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| WHERE aa.thread_id = p_thread_id | |
| AND aa.scope_type = p_scope_type | |
| AND aa.scope_id = p_scope_id | |
| WHERE aa.thread_id = p_thread_id | |
| AND aa.scope_type = p_scope_type | |
| AND aa.scope_id IS NOT DISTINCT FROM p_scope_id |
🤖 Prompt for AI Agents
In
@apps/hasura/migrations/masterbots/1766957388317__op_meta_and_scoped_rag/up.sql
around lines 92 - 94, The WHERE clause comparing aa.scope_id = p_scope_id
incorrectly excludes rows when both are NULL; update the condition to use
NULL-safe comparison (aa.scope_id IS NOT DISTINCT FROM p_scope_id) so rows with
both NULL scope_id (used for the doc_root scope_type) are preserved; apply this
change in the query that includes the predicates aa.thread_id = p_thread_id AND
aa.scope_type = p_scope_type AND aa.scope_id = p_scope_id.
| -- assistant_answers: your metadata expects scope_type/scope_id already exist:contentReference[oaicite:0]{index=0} | ||
| CREATE INDEX IF NOT EXISTS assistant_answers_thread_doc_scope_idx | ||
| ON public.assistant_answers (thread_id, doc_node_id, scope_type, scope_id); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS assistant_answers_thread_doc_scope_turn_idx | ||
| ON public.assistant_answers (thread_id, doc_node_id, scope_type, scope_id, turn_index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent handling: assistant_answers indexes assume scope columns exist.
Lines 39-43 create indexes on assistant_answers assuming scope_type and scope_id exist, but the message table at Lines 16-36 defensively checks first. This could cause migration failures if the columns don't exist on assistant_answers.
Also, Line 38 has a garbled comment artifact (contentReference[oaicite:0]{index=0}) that should be cleaned up.
Add defensive check for assistant_answers
--- assistant_answers: your metadata expects scope_type/scope_id already exist:contentReference[oaicite:0]{index=0}
-CREATE INDEX IF NOT EXISTS assistant_answers_thread_doc_scope_idx
-ON public.assistant_answers (thread_id, doc_node_id, scope_type, scope_id);
-
-CREATE INDEX IF NOT EXISTS assistant_answers_thread_doc_scope_turn_idx
-ON public.assistant_answers (thread_id, doc_node_id, scope_type, scope_id, turn_index);
+-- assistant_answers: create indexes only if scope columns exist
+DO $$
+BEGIN
+ IF EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_schema = 'public'
+ AND table_name = 'assistant_answers'
+ AND column_name = 'scope_type'
+ )
+ AND EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_schema = 'public'
+ AND table_name = 'assistant_answers'
+ AND column_name = 'scope_id'
+ )
+ THEN
+ CREATE INDEX IF NOT EXISTS assistant_answers_thread_doc_scope_idx
+ ON public.assistant_answers (thread_id, doc_node_id, scope_type, scope_id);
+
+ CREATE INDEX IF NOT EXISTS assistant_answers_thread_doc_scope_turn_idx
+ ON public.assistant_answers (thread_id, doc_node_id, scope_type, scope_id, turn_index);
+ END IF;
+END $$;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| -- assistant_answers: your metadata expects scope_type/scope_id already exist:contentReference[oaicite:0]{index=0} | |
| CREATE INDEX IF NOT EXISTS assistant_answers_thread_doc_scope_idx | |
| ON public.assistant_answers (thread_id, doc_node_id, scope_type, scope_id); | |
| CREATE INDEX IF NOT EXISTS assistant_answers_thread_doc_scope_turn_idx | |
| ON public.assistant_answers (thread_id, doc_node_id, scope_type, scope_id, turn_index); | |
| -- assistant_answers: create indexes only if scope columns exist | |
| DO $$ | |
| BEGIN | |
| IF EXISTS ( | |
| SELECT 1 FROM information_schema.columns | |
| WHERE table_schema = 'public' | |
| AND table_name = 'assistant_answers' | |
| AND column_name = 'scope_type' | |
| ) | |
| AND EXISTS ( | |
| SELECT 1 FROM information_schema.columns | |
| WHERE table_schema = 'public' | |
| AND table_name = 'assistant_answers' | |
| AND column_name = 'scope_id' | |
| ) | |
| THEN | |
| CREATE INDEX IF NOT EXISTS assistant_answers_thread_doc_scope_idx | |
| ON public.assistant_answers (thread_id, doc_node_id, scope_type, scope_id); | |
| CREATE INDEX IF NOT EXISTS assistant_answers_thread_doc_scope_turn_idx | |
| ON public.assistant_answers (thread_id, doc_node_id, scope_type, scope_id, turn_index); | |
| END IF; | |
| END $$; |
🤖 Prompt for AI Agents
In
@apps/hasura/migrations/masterbots/1767498310895_add_public_message_docid/up.sql
around lines 38 - 43, Remove the garbled comment artifact
"contentReference[oaicite:0]{index=0}" and guard the two index creations
(assistant_answers_thread_doc_scope_idx and
assistant_answers_thread_doc_scope_turn_idx) with a defensive check that the
assistant_answers table actually has both scope_type and scope_id columns;
implement this by wrapping the CREATE INDEX statements in a conditional DO block
or IF EXISTS query that checks information_schema.columns (or pg_catalog) for
both column names on table assistant_answers and only runs the CREATE INDEX ...
statements when both columns are present.
| } catch (error) { | ||
| // Retry once without doc_node_id if that was the cause (schema lag during rollout). | ||
| if (docNodeId && typeof (error as Error)?.message === 'string') { | ||
| const msg = (error as Error).message | ||
| if ( | ||
| msg.includes('doc_node_id') || | ||
| msg.includes('field') || | ||
| msg.includes('assistant_answers_insert_input') | ||
| ) { | ||
| try { | ||
| const { doc_node_id: _ignored, ...withoutDocNode } = baseObject as any | ||
| await hasuraGql(insert, { object: withoutDocNode }) | ||
| } catch { | ||
| return | ||
| } | ||
| } | ||
| } | ||
| return | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retry logic swallows original error on non-schema-related failures.
When the initial insert fails and the error message doesn't match the schema lag conditions, the function returns silently at line 172, losing the original error context. This makes debugging non-schema-related failures difficult.
🔧 Suggested improvement
} catch (error) {
// Retry once without doc_node_id if that was the cause (schema lag during rollout).
if (docNodeId && typeof (error as Error)?.message === 'string') {
const msg = (error as Error).message
if (
msg.includes('doc_node_id') ||
msg.includes('field') ||
msg.includes('assistant_answers_insert_input')
) {
try {
const { doc_node_id: _ignored, ...withoutDocNode } = baseObject as any
await hasuraGql(insert, { object: withoutDocNode })
+ return // Success on retry
} catch {
+ console.warn('[ingestAssistantAnswer] Retry without doc_node_id also failed')
return
}
}
}
+ // Log non-schema errors for debugging (fail-closed, no throw)
+ console.warn('[ingestAssistantAnswer] Insert failed:', (error as Error)?.message)
return
}🤖 Prompt for AI Agents
In @apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts around lines
155 - 173, The catch block currently swallows the original error when it isn't a
schema-lag case; change it to preserve and surface that error: capture the
caught error in the catch (error) block, only attempt the retry branch when
docNodeId && error.message indicates schema issues (as currently done for
doc_node_id/field/assistant_answers_insert_input), but if the message does not
match those conditions rethrow the original error (or at minimum log it) instead
of returning; additionally, if the retry path is taken and the retry fails,
rethrow or attach both the retry error and the original error so callers see
both contexts (refer to docNodeId, baseObject, hasuraGql, and insert to locate
and update the relevant logic).
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
Show resolved
Hide resolved
| <Button | ||
| type="button" | ||
| variant={viewMode === 'sections' ? 'secondary' : 'outline'} | ||
| size="sm" | ||
| onClick={() => setIsPreview(false)} | ||
| disabled={isBusy} | ||
| > | ||
| <FileText className="size-4 mr-2" /> | ||
| Sections | ||
| </Button> | ||
|
|
||
| <Button | ||
| type="button" | ||
| variant={viewMode === 'source' ? 'secondary' : 'outline'} | ||
| size="sm" | ||
| onClick={() => setIsPreview(false)} | ||
| disabled={isBusy} | ||
| > | ||
| <TextCursorInputIcon className="size-4 mr-2" /> | ||
| Source | ||
| </Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n "viewMode" --type=ts --type=tsx apps/pro-web/components/routes/workspace/ -C 3Repository: bitcashorg/masterbots
Length of output: 92
🏁 Script executed:
cat -n apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx | head -350 | tail -100Repository: bitcashorg/masterbots
Length of output: 3448
🏁 Script executed:
cat -n apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx | head -100Repository: bitcashorg/masterbots
Length of output: 3879
🏁 Script executed:
rg -n "setViewMode|viewMode\s*=" --type ts apps/pro-web/components/routes/workspace/document-tab/ -C 2Repository: bitcashorg/masterbots
Length of output: 3189
🏁 Script executed:
cat -n apps/pro-web/components/routes/workspace/document-tab/workspace-document-tab.tsx | head -100Repository: bitcashorg/masterbots
Length of output: 4419
🏁 Script executed:
rg -n "viewMode" apps/pro-web/components/routes/workspace/document-tab/workspace-document-tab.tsx -C 5Repository: bitcashorg/masterbots
Length of output: 691
🏁 Script executed:
rg -n "viewMode" apps/pro-web/lib/hooks/ -C 3Repository: bitcashorg/masterbots
Length of output: 2648
🏁 Script executed:
fd -t f "use-workspace-document-content" apps/pro-web/lib/hooks/Repository: bitcashorg/masterbots
Length of output: 126
🏁 Script executed:
cat -n apps/pro-web/lib/hooks/use-workspace-document-content-hook.ts | grep -A 50 "return {" | head -60Repository: bitcashorg/masterbots
Length of output: 1748
🏁 Script executed:
rg -n "setViewMode" apps/pro-web/lib/hooks/use-workspace-document-content-hook.ts -C 3Repository: bitcashorg/masterbots
Length of output: 653
🏁 Script executed:
rg -n "handleViewSourceToggle" apps/pro-web/lib/hooks/use-workspace-document-content-hook.ts -B 5 -A 10Repository: bitcashorg/masterbots
Length of output: 1563
🏁 Script executed:
cat -n apps/pro-web/lib/hooks/use-workspace-document-content-hook.ts | sed -n '330,360p'Repository: bitcashorg/masterbots
Length of output: 1182
🏁 Script executed:
cat -n apps/pro-web/components/routes/workspace/document-tab/workspace-document-tab.tsx | sed -n '60,80p'Repository: bitcashorg/masterbots
Length of output: 988
🏁 Script executed:
rg -n "handleViewSourceToggle" apps/pro-web/components/routes/workspace/document-tab/ -C 2Repository: bitcashorg/masterbots
Length of output: 606
Pass handleViewSourceToggle to the component and use it to change viewMode.
The Sections and Source buttons display the viewMode state in their variant styling but cannot change it. Both buttons call setIsPreview(false) only, leaving viewMode unchanged. The parent component extracts handleViewSourceToggle from the hook but doesn't pass it to WorkspaceDocumentTextEditor, so the child has no mechanism to update the view mode.
The buttons should call handleViewSourceToggle('sections') and handleViewSourceToggle('source') respectively. Either pass handleViewSourceToggle as a prop or provide an alternative way for the component to trigger viewMode changes.
🤖 Prompt for AI Agents
In
@apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
around lines 304 - 324, The Sections/Source buttons in
WorkspaceDocumentTextEditor read viewMode for styling but never change it; pass
the parent's handleViewSourceToggle prop into WorkspaceDocumentTextEditor and
call handleViewSourceToggle('sections') for the Sections button and
handleViewSourceToggle('source') for the Source button (you can still call
setIsPreview(false) as before). Update the component signature to accept
handleViewSourceToggle and replace the onClick handlers that currently only call
setIsPreview with calls that invoke handleViewSourceToggle with the correct
mode.
| const handleClick = async (e: React.MouseEvent) => { | ||
| e.stopPropagation() | ||
| const category = thread?.chatbot.department?.name || 'general' | ||
| const category = thread?.chatbot?.categories[0]?.category?.name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential runtime error: accessing categories[0] without null check.
The expression thread?.chatbot?.categories[0]?.category?.name will throw if categories is an empty array since categories[0] returns undefined, but the preceding ?. does not guard array access.
🐛 Proposed fix
- const category = thread?.chatbot?.categories[0]?.category?.name
+ const category = thread?.chatbot?.categories?.[0]?.category?.name📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const category = thread?.chatbot?.categories[0]?.category?.name | |
| const category = thread?.chatbot?.categories?.[0]?.category?.name |
🤖 Prompt for AI Agents
In @apps/pro-web/components/shared/shared-accordion.tsx at line 374, The current
assignment to the local variable "category" can throw when chatbot.categories is
an empty array; change the access to safely handle empty arrays by using
optional chaining on the index (e.g.,
thread?.chatbot?.categories?.[0]?.category?.name) or guard with a length check
(e.g., const categories = thread?.chatbot?.categories; const category =
categories && categories.length ? categories[0].category?.name : undefined) so
that accessing categories[0] never throws; update the assignment where
"category" is declared.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/pro-web/lib/hooks/use-onboarding.ts (1)
97-97: Remove default export per coding guidelines.The coding guidelines specify "Avoid default exports; prefer named exports." Since
useOnboardingis already exported as a named export at line 15, this default export should be removed.♻️ Proposed fix
return { showOnboarding, markSeen } } - -export default useOnboardingAs per coding guidelines, named exports are preferred for consistency across the codebase.
apps/pro-web/components/routes/chat/prompt-form/index.tsx (2)
152-152: Remove debug console.log statement.This
console.log('normalizedInput', normalizedInput)appears to be debug output that should be removed before merging.🧹 Remove debug statement
const normalizedInput = input.toLowerCase() - console.log('normalizedInput', normalizedInput) if (
336-337: Remove debug console.log statements in handleAIAction.Multiple
console.logstatements are present throughout thehandleAIActioncallback (lines 336-337, 363, 368, 374, 399). Consider removing these or replacing with a proper logging utility that can be disabled in production.apps/pro-web/components/routes/browse/browse-list-item.tsx (1)
42-48: Unused state and variable.
messagesstate is declared butsetMessagesis never called, somessagesis always an empty array passed toBrowseChatMessageList.tabis destructured fromuseBrowse()but never used.If this is intentional placeholder code, consider adding a TODO comment. Otherwise, remove the unused declarations or implement the message-loading logic.
🧹 Suggested cleanup
- const [messages, setMessages] = React.useState<Message[]>([]) const { searchTerm } = useThreadSearch() const [isAccordionOpen, setIsAccordionOpen] = React.useState(false) const [isVisible, setIsVisible] = React.useState(true) const params = useParams() - const { tab } = useBrowse() + useBrowse() // Ensure hook is called if needed for side effectsAnd update the
BrowseChatMessageListusage:<BrowseChatMessageList chatbot={thread?.chatbot} user={thread?.user || undefined} - messages={messages} + messages={thread.messages || []} />
🤖 Fix all issues with AI agents
In `@apps/pro-web/components/routes/browse/browse-list-item.tsx`:
- Around line 146-148: The comment is wrong: returning null unmounts the
component but the comment claims it stays mounted; either update the comment to
state that "returning null unmounts the component" or change the behavior to
keep the component mounted by replacing the early return of null (the isVisible
check) with rendering the element with a hidden style/class (e.g., use CSS
visibility:hidden or a "hidden" class) so state/scroll are preserved; locate the
isVisible check in the BrowseListItem component (the if (!isVisible) return null
line) and apply one of these two fixes.
- Around line 59-76: There are two duplicate useEffect blocks both writing to
initialUrl; remove the second useEffect (the one without the guard) so only the
guarded useEffect runs and preserves the original URL, ensuring initialUrl is
assigned only once; if instead you intended to update on tab changes, convert to
a single useEffect that includes tab in its dependency array and remove the
early-return guard inside that effect so initialUrl is updated when tab changes.
- Around line 78-123: The parent component’s loadMore callback is unstable and
causes the IntersectionObserver effect in browse-list-item.tsx to re-run; wrap
the loadMore function in the parent (browse-specific-thread-list.tsx) with
useCallback and include only stable dependencies so its reference remains stable
across renders, ensuring the prop named loadMore passed into the BrowseListItem
(or thread list) is memoized and does not trigger unnecessary effect
re-subscriptions.
In
`@apps/pro-web/components/routes/workspace/document-tab/workspace-document-section-item.tsx`:
- Around line 113-126: The Button for deleting a section currently lacks an
explicit type and may default to "submit" inside a form; update the Button
component (the instance rendering XIcon with onClick that calls
onDeleteSection(section.title)) to include type="button" so it cannot trigger
form submission, keeping the existing e.preventDefault()/e.stopPropagation and
accessibility title/aria-label intact.
In `@apps/pro-web/lib/queries/use-document-content.ts`:
- Around line 123-125: The hook useDocumentContent throws when queryDoc is
missing (the Error('Document not found in workspace structure') path), so update
all callers that currently destructure only { data, isLoading, isPending } to
also destructure { isError, error } from useDocumentContent and handle the error
case explicitly (render an error UI or message when isError is true) instead of
silently falling back to documentData?.sections || []; search for usages of
useDocumentContent and adjust their render logic to check isError and use error
for diagnostics.
In `@apps/pro-web/lib/url.ts`:
- Around line 486-503: The fallback URL construction inside the if
(!threadQuestionSlug) branch can produce empty path segments; modify the block
(the fallback branch that currently returns arrays joined with '/') to either
guard early using the existing missingKeys check for required fields or build
the parts array and remove empty segments before joining while preserving the
leading slash (i.e., keep the initial empty string element). Target the fallback
logic in the function in apps/pro-web/lib/url.ts where the chatbot/chat branch
and the longer 'u'/'t' branch are returned and ensure you filter out falsy/empty
string segments (but still preserve the initial leading slash) or return a safe
fallback when required keys are missing.
🧹 Nitpick comments (24)
apps/pro-web/lib/hooks/use-onboarding.ts (3)
16-18: Unusedsessionvariable.The
sessionvariable is destructured fromuseSession()but is never used in this hook. Consider removing this line to avoid confusion and unnecessary hook invocation.🧹 Proposed cleanup
export function useOnboarding() { - const { data: session } = useSession() const { selectedCategories, isLoaded: isCategoryStorageLoaded } = useCategorySelections()Also remove the unused import:
-import { useSession } from 'next-auth/react'
34-61: Hardcoded delay and redundant type check.Two observations:
- The
1750mstimeout is a magic number. Consider extracting it as a named constant with a comment explaining why this specific delay is needed (waiting for sidebar mount).- The
el instanceof Nodecheck at line 54 is redundant sinceel instanceof HTMLElementwas already verified at line 36, andHTMLElementextendsNode.♻️ Proposed improvements
+const SIDEBAR_MOUNT_DELAY_MS = 1750 // Wait for sidebar element to be rendered + export function useOnboarding() { // ... - timeoutRef.current = setTimeout(() => { + timeoutRef.current = setTimeout(() => { const el = document.querySelector('aside[id$=_sidebar]') if (!(el instanceof HTMLElement)) return // Set initial state setSidebarState(el.dataset.state || null) const observer = new MutationObserver((mutations) => { // ... }) - // Start observing (extra runtime guard prevents "parameter 1 is not of type 'Node'" crashes) - if (el instanceof Node) { - observer.observe(el, { - attributes: true, - attributeFilter: ['data-state'], - }) - observerRef.current = observer - } - }, 1750) + observer.observe(el, { + attributes: true, + attributeFilter: ['data-state'], + }) + observerRef.current = observer + }, SIDEBAR_MOUNT_DELAY_MS)
81-84: Remove commented-out code.The commented line at line 83 should be removed. Commented-out code adds noise and can be retrieved from version control if needed later.
🧹 Proposed cleanup
setShowOnboarding( !hasSeen && selectedCategories.length === 0 && isSidebarOpen, - // !hasSeen && selectedCategories.length === 0, )apps/pro-web/lib/hooks/use-intersection-observer.tsx (2)
20-26: Callback stability depends on caller memoizingonIntersect.The callback is stable only if
onIntersectis memoized by the caller. If callers pass an inline arrow function, this will still cause the callback (and thus the effect) to re-run on every render. Consider documenting this expectation or wrappingonIntersectwithuseRefto capture the latest value without triggering re-subscriptions.💡 Alternative: Use ref pattern to avoid re-subscription
+import { type RefObject, useCallback, useEffect, useRef } from 'react' ... export function useIntersectionObserver({ target, root = null, rootMargin = '0px', threshold = 0, onIntersect, enabled = true, }: UseIntersectionObserverProps) { + const onIntersectRef = useRef(onIntersect) + onIntersectRef.current = onIntersect + const callback = useCallback<IntersectionObserverCallback>( ([entry]) => { - onIntersect(Boolean(entry?.isIntersecting)) + onIntersectRef.current(Boolean(entry?.isIntersecting)) }, - [onIntersect], + [], )
47-50: Silent catch may hide debugging information.The empty catch block silently swallows errors. While this prevents crashes, it could hide issues during development. Consider logging in non-production environments.
💡 Optional: Add development-only warning
try { observer.observe(targetEl) } catch (error) { + if (process.env.NODE_ENV !== 'production') { + console.warn('[useIntersectionObserver] Failed to observe element:', error) + } observer.disconnect() return }apps/pro-web/components/routes/chat/prompt-form/ai-actions-dropdown.tsx (2)
35-35: Remove unused import.
useCallbackis imported but never used in the component. This creates unnecessary dead code.Suggested fix
-import { Fragment, useCallback, useMemo, useState } from 'react' +import { Fragment, useMemo, useState } from 'react'
123-152: Error messages are constructed but may not be surfaced to users.The
actionPayloads.errorobject contains helpful error messages (lines 127-135), but when the action is cancelled (actionToTrigger !== 'cancel-action'fails), the error message isn't visibly displayed to the user. The action silently fails to trigger.Consider showing a toast notification or other feedback when
actionToTrigger === 'cancel-action'to inform users they need to select a document section first.apps/pro-web/lib/workspace-debug.ts (1)
20-27: Flags are static — require page refresh after toggling.
DEBUG_FLAGSis evaluated once at module initialization. Settingwindow.__DEBUG_WORKSPACE__ = truein devtools after page load won't take effect until refresh. This is fine for performance, but consider clarifying in the documentation (lines 4-8) that a refresh is required after enabling flags.Alternatively, if runtime toggling is desired, use getters:
♻️ Optional: Use getters for runtime toggle support
export const DEBUG_FLAGS = { - workspace: Boolean(w?.__DEBUG_WORKSPACE__), - content: Boolean(w?.__DEBUG_WORKSPACE_CONTENT__), - chat: Boolean(w?.__DEBUG_WORKSPACE_CHAT__), - markdown: Boolean(w?.__DEBUG_MARKDOWN__), + get workspace() { + return Boolean(w?.__DEBUG_WORKSPACE__) + }, + get content() { + return Boolean(w?.__DEBUG_WORKSPACE_CONTENT__) + }, + get chat() { + return Boolean(w?.__DEBUG_WORKSPACE_CHAT__) + }, + get markdown() { + return Boolean(w?.__DEBUG_MARKDOWN__) + }, }apps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsx (2)
10-14: Unused imports: Tooltip components are never used.The
Tooltip,TooltipContent, andTooltipTriggerimports are not used in this component.🧹 Remove unused imports
-import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from '@/components/ui/tooltip'
58-79: Consider edge case: suffix removal relies on exact string match.The
replace(suffix, '')at line 67 only removes the first occurrence. If the user manually edits the input to include the suffix text elsewhere, the toggle state may become inconsistent with the actual input content. This is a minor edge case but worth noting.apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx (1)
1-142: Significant code duplication with pro-web variant.This file is nearly identical to
apps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsx. Consider extracting thePromptEnhancercomponent to a shared package (e.g.,packages/uior a shared components package) to maintain a single source of truth and reduce maintenance burden.Additionally, the
Tooltip,TooltipContent, andTooltipTriggerimports at lines 10-14 are unused and should be removed.apps/web/components/routes/chat/prompt-form/index.tsx (1)
69-69: Unused import:idfrom date-fns/locale.The
idimport fromdate-fns/localedoes not appear to be used in this file.🧹 Remove unused import
-import { id } from 'date-fns/locale'apps/pro-web/components/routes/workspace/document-tab/workspace-document-section-item.tsx (2)
71-74: Provide a meaningful lint suppression reason.The biome-ignore comment uses a placeholder
<explanation>instead of explaining why semantic elements aren't suitable here. Consider documenting the actual reason (e.g., "Using div with role=button to support complex nested interactive content and custom keyboard handling").Suggested fix
<div - // biome-ignore lint/a11y/useSemanticElements: <explanation> + // biome-ignore lint/a11y/useSemanticElements: Using div with role=button to support accordion nesting and custom hover state management role="button" tabIndex={0}
117-121: Consider extracting the delete handler to a stable function reference.Per coding guidelines, prefer regular function references over inline arrows in JSX to avoid extra re-renders. This handler could be extracted alongside
handleSectionClick.Suggested refactor
const handleSectionClick = () => { onSectionClick(section.id) } +const handleDeleteClick = (e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + onDeleteSection(section.title) +} + // ... later in JSX: <Button ... - onClick={(e) => { - e.preventDefault() - e.stopPropagation() - onDeleteSection(section.title) - }} + onClick={handleDeleteClick} ... >apps/pro-web/components/routes/browse/browse-list-item.tsx (3)
23-37: Prefer named export over default export.As per coding guidelines, avoid default exports and prefer named exports for better refactoring support and explicit imports.
♻️ Suggested fix
-export default function BrowseListItem({ +export function BrowseListItem({Then update imports in consuming files:
import { BrowseListItem } from '@/components/routes/browse/browse-list-item'
19-21: Consider removing or refactoring module-level mutable state.
initialUrlis a module-level variable that persists across all component instances and renders. The comment indicates it's "currently unused." Module-level mutable state in React components can cause subtle bugs with SSR or when multiple instances exist.If this is truly unused, remove it along with the associated effects. If needed for future use, consider using a ref within the component instead.
46-46: Remove unusedparamsvariable.
useParams()is called butparamsis never used in the component.- const params = useParams()apps/pro-web/lib/queries/use-workspace-structure.ts (1)
347-359: Type assertion(doc as any).docNodeIdbypasses type safety.The
WorkspaceDocumentMetadatatype should be extended to includedocNodeIdrather than usingas any. This also applies to the final cast on line 359.♻️ Suggested approach
Update the
WorkspaceDocumentMetadatatype to include an optionaldocNodeIdfield, then remove theas anycasts:// In types/thread.types.ts or wherever WorkspaceDocumentMetadata is defined export interface WorkspaceDocumentMetadata { // existing fields... + docNodeId?: string | null }Then update the mapping:
finalStructure.documents = Array.from(mergedDocuments.values()).map( (doc) => ({ ...doc, - docNodeId: - (doc as any).docNodeId ?? - getCanonicalDocNodeId({ - docId: doc.id, - project: doc.project, - documentName: doc.name, - }), + docNodeId: + doc.docNodeId ?? + getCanonicalDocNodeId({ + docId: doc.id, + project: doc.project, + documentName: doc.name, + }), }), -) as any +)apps/pro-web/lib/hooks/use-workspace-chat.tsx (1)
787-848: Scope computation and window fetching is well-structured.The try/catch ensures the workspace chat continues to function even if scope window fetch fails. The
finalUserPromptfallback on line 847 is correct.One consideration: the type cast on line 840 (
scopedHistory as unknown as any) bypasses type checking. Consider aligning theScopeConversationWindowreturn type with whatfollowingQuestionsPromptexpects.💡 Optional: Improve type alignment
The
followingQuestionsPromptexpectsArray<MessageDB & { id?: string }>. Consider mappingscopedHistoryto match this type explicitly rather than usingas unknown as any.if (scopedHistory.length) { finalUserPrompt = followingQuestionsPrompt( userPrompt, - scopedHistory as unknown as any, + scopedHistory.map(msg => ({ + ...msg, + id: msg.messageId, + })), '', ) }apps/pro-web/lib/hooks/use-mb-chat.tsx (2)
117-158: Consider extracting inline types to a dedicated types file.
UiMode,ActionType,IntentSource,DocContextPolicy, andOperationMetaare defined inline within the component. Per the coding guidelines, these should be in a.type.tsfile for better organization.The AI summary references
apps/pro-web/types/operation-meta.tswhich appears to already define some of these types. Consider importing from there instead:+import type { + UiMode, + ActionType, + IntentSource, + DocContextPolicy, + OperationMeta, +} from '@/types/operation-meta' -type UiMode = 'chat' | 'workspace' -type ActionType = 'ask' | 'rewrite' | 'expand' | 'edit' -// ... rest of inline type definitions
1064-1161: Large duplicated structure in enrichedChatOptions construction.The
mb_op_metaobject is constructed identically in both branches of the ternary. Consider extracting to a variable.♻️ Reduce duplication
+const mbOpMeta = { + scope_type: scopeTypeForMeta, + scope_id: scopeIdForMeta, + doc_node_id: docNodeIdForMeta, + scope_title_snapshot: scopeTitleSnapshotForMeta, + doc_context_included: docContextIncluded, + doc_context_policy: docContextIncluded ? DOC_CONTEXT_POLICY : null, + heading_content_version: headingContentVersion ?? null, +} const enrichedChatOptions: ChatRequestOptions | undefined = chatRequestOptions ? ({ ...chatRequestOptions, body: { ...((chatRequestOptions as any).body || {}), - mb_op_meta: { - scope_type: scopeTypeForMeta, - scope_id: scopeIdForMeta, - // ... rest of fields - }, + mb_op_meta: mbOpMeta, }, } as ChatRequestOptions) : ({ body: { - mb_op_meta: { - scope_type: scopeTypeForMeta, - // ... rest of fields - }, + mb_op_meta: mbOpMeta, }, } as ChatRequestOptions)apps/pro-web/components/routes/chat/chat-list/message-pair-accordion.tsx (2)
200-236: Focus/scroll effect has correct cleanup but could short-circuit earlier.The effect properly cleans up the timeout. However, when
shouldFocusis true and there's noactiveThreadQuestionSlug, the effect continues to check for a DOM element that won't exist.Minor optimization to short-circuit when no URL slug exists
useEffect(() => { let shouldFocus = false if (!activeThreadQuestionSlug) { // If no threadQuestionSlug in URL, focus on the last message of the pair shouldFocus = index === arrayLength - 1 && !isPrevious + if (shouldFocus) setIsAccordionFocused(true) + return } else { // If threadQuestionSlug exists, focus on the message with that slug shouldFocus = userQuestionSlug === activeThreadQuestionSlug } if (shouldFocus) setIsAccordionFocused(true) - - if (!activeThreadQuestionSlug) return
362-366: Dead code:shouldShowUserMessageis alwaystrue.With
shouldShowUserMessagehardcoded totrue, the conditional checks at lines 414-417 and 391-395 will never evaluate their alternative branches. If this is intentional for future flexibility, consider adding a brief comment. Otherwise, the dead branches could be removed for clarity.apps/pro-web/lib/url.ts (1)
693-730:parsePathmirrorsparseRouteFromPathnamein message-pair-accordion.tsx.Both functions parse the same URL patterns but return different shapes. Consider consolidating into a single utility to avoid drift, or document the relationship between them.
| // Preserve original behavior: set initialUrl once, then update when tab changes. | ||
| // These are guarded for client-only usage (this file is a client component). | ||
| useEffect(() => { | ||
| if (initialUrl) return | ||
| initialUrl = location.href | ||
| }, [initialUrl]) | ||
| try { | ||
| initialUrl = window.location.href | ||
| } catch { | ||
| // no-op | ||
| } | ||
| }, []) | ||
|
|
||
| // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation> | ||
| useEffect(() => { | ||
| initialUrl = location.href | ||
| }, [tab]) | ||
| try { | ||
| initialUrl = window.location.href | ||
| } catch { | ||
| // no-op | ||
| } | ||
| }, []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate useEffects with conflicting behavior.
Two nearly identical effects both set initialUrl. The first guards against re-assignment, but the second (Lines 70-76) has no guard and runs on every mount, defeating the first effect's purpose. Additionally, the comment mentions "update when tab changes" but tab is not in the dependency array.
If the intent is to preserve the initial URL, remove the second effect. If it should update when tab changes, add tab to the dependency array of a single effect.
🛠️ Suggested fix (preserve initial URL only)
// Preserve original behavior: set initialUrl once, then update when tab changes.
// These are guarded for client-only usage (this file is a client component).
useEffect(() => {
if (initialUrl) return
try {
initialUrl = window.location.href
} catch {
// no-op
}
}, [])
-
- useEffect(() => {
- try {
- initialUrl = window.location.href
- } catch {
- // no-op
- }
- }, [])📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Preserve original behavior: set initialUrl once, then update when tab changes. | |
| // These are guarded for client-only usage (this file is a client component). | |
| useEffect(() => { | |
| if (initialUrl) return | |
| initialUrl = location.href | |
| }, [initialUrl]) | |
| try { | |
| initialUrl = window.location.href | |
| } catch { | |
| // no-op | |
| } | |
| }, []) | |
| // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation> | |
| useEffect(() => { | |
| initialUrl = location.href | |
| }, [tab]) | |
| try { | |
| initialUrl = window.location.href | |
| } catch { | |
| // no-op | |
| } | |
| }, []) | |
| // Preserve original behavior: set initialUrl once, then update when tab changes. | |
| // These are guarded for client-only usage (this file is a client component). | |
| useEffect(() => { | |
| if (initialUrl) return | |
| try { | |
| initialUrl = window.location.href | |
| } catch { | |
| // no-op | |
| } | |
| }, []) |
🤖 Prompt for AI Agents
In `@apps/pro-web/components/routes/browse/browse-list-item.tsx` around lines 59 -
76, There are two duplicate useEffect blocks both writing to initialUrl; remove
the second useEffect (the one without the guard) so only the guarded useEffect
runs and preserves the original URL, ensuring initialUrl is assigned only once;
if instead you intended to update on tab changes, convert to a single useEffect
that includes tab in its dependency array and remove the early-return guard
inside that effect so initialUrl is updated when tab changes.
| // Infinite load observer (crash-hardened) | ||
| useEffect(() => { | ||
| if (!threadRef.current) return | ||
| const observer = new IntersectionObserver(([entry]) => { | ||
| if (hasMore && isLast && entry.isIntersecting && !loading) { | ||
| const timeout = setTimeout(() => { | ||
| loadMore() | ||
| clearTimeout(timeout) | ||
| }, 150) | ||
| const el = threadRef.current | ||
| if (!(el instanceof Element)) return | ||
|
|
||
| // If there’s nothing more to load or this isn’t the last item, don’t observe. | ||
| if (!hasMore || !isLast) return | ||
|
|
||
| const observer = new IntersectionObserver((entries) => { | ||
| const entry = entries[0] | ||
| if (!entry?.isIntersecting) return | ||
| if (loading) return | ||
| if (!hasMore || !isLast) return | ||
|
|
||
| // Prevent duplicate timers / duplicate loadMore calls | ||
| if (loadMoreTimerRef.current) return | ||
|
|
||
| loadMoreTimerRef.current = setTimeout(() => { | ||
| loadMoreTimerRef.current = null | ||
| loadMore() | ||
| }, 150) | ||
|
|
||
| // Stop observing once we've triggered loadMore for this last item | ||
| try { | ||
| observer.unobserve(entry.target) | ||
| } catch { | ||
| // no-op (target may already be detached) | ||
| } | ||
| }) | ||
| observer.observe(threadRef.current) | ||
|
|
||
| try { | ||
| observer.observe(el) | ||
| } catch { | ||
| // If observe throws (rare teardown race / invalid element), ensure no crash. | ||
| observer.disconnect() | ||
| return | ||
| } | ||
|
|
||
| return () => { | ||
| if (loadMoreTimerRef.current) { | ||
| clearTimeout(loadMoreTimerRef.current) | ||
| loadMoreTimerRef.current = null | ||
| } | ||
| observer.disconnect() | ||
| } | ||
| }, [isLast, hasMore, loading]) | ||
| }, [hasMore, isLast, loading, loadMore]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, locate and examine the browse-list-item.tsx file
fd "browse-list-item.tsx" -t fRepository: bitcashorg/masterbots
Length of output: 178
🏁 Script executed:
#!/bin/bash
# Read the browse-list-item.tsx file from pro-web
wc -l "apps/pro-web/components/routes/browse/browse-list-item.tsx"Repository: bitcashorg/masterbots
Length of output: 127
🏁 Script executed:
#!/bin/bash
# Read the full browse-list-item.tsx file
cat -n "apps/pro-web/components/routes/browse/browse-list-item.tsx"Repository: bitcashorg/masterbots
Length of output: 8969
🏁 Script executed:
#!/bin/bash
# Find files that import BrowseListItem from pro-web
rg "import.*BrowseListItem|from.*browse-list-item" apps/pro-web --type=tsx -lRepository: bitcashorg/masterbots
Length of output: 92
🏁 Script executed:
#!/bin/bash
# Find files that import BrowseListItem from pro-web
rg "import.*BrowseListItem|from.*browse-list-item" apps/pro-web -lRepository: bitcashorg/masterbots
Length of output: 134
🏁 Script executed:
#!/bin/bash
# Examine the file that uses BrowseListItem
wc -l "apps/pro-web/components/routes/browse/browse-specific-thread-list.tsx"Repository: bitcashorg/masterbots
Length of output: 138
🏁 Script executed:
#!/bin/bash
# Read the browse-specific-thread-list.tsx file
cat -n "apps/pro-web/components/routes/browse/browse-specific-thread-list.tsx"Repository: bitcashorg/masterbots
Length of output: 4257
🏁 Script executed:
#!/bin/bash
# Double-check if loadMore uses useCallback anywhere in the parent component
rg -A 10 "const loadMore" "apps/pro-web/components/routes/browse/browse-specific-thread-list.tsx"Repository: bitcashorg/masterbots
Length of output: 351
LGTM - Robust IntersectionObserver implementation.
The crash-hardened observer with timer-based debouncing effectively prevents duplicate loadMore calls and handles edge cases gracefully. The cleanup function correctly clears both the timer and disconnects the observer.
However, ensure loadMore in the parent component (browse-specific-thread-list.tsx) is wrapped in useCallback with stable dependencies to avoid unnecessary observer re-subscriptions. Currently, loadMore is redefined on every parent render, causing the effect to re-run unnecessarily.
🤖 Prompt for AI Agents
In `@apps/pro-web/components/routes/browse/browse-list-item.tsx` around lines 78 -
123, The parent component’s loadMore callback is unstable and causes the
IntersectionObserver effect in browse-list-item.tsx to re-run; wrap the loadMore
function in the parent (browse-specific-thread-list.tsx) with useCallback and
include only stable dependencies so its reference remains stable across renders,
ensuring the prop named loadMore passed into the BrowseListItem (or thread list)
is memoized and does not trigger unnecessary effect re-subscriptions.
| // Preserve original visibility behavior (component remains mounted, just hidden) | ||
| if (!isVisible) return null | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Misleading comment - returning null unmounts the component.
The comment states "component remains mounted, just hidden" but returning null actually unmounts the component from the DOM. If the intent is to keep it mounted but hidden (e.g., to preserve scroll position or state), use CSS visibility instead:
🛠️ Option A: Fix comment to match behavior
- // Preserve original visibility behavior (component remains mounted, just hidden)
+ // Hide component when search doesn't match (component unmounts)
if (!isVisible) return null🛠️ Option B: Keep mounted but visually hidden
- // Preserve original visibility behavior (component remains mounted, just hidden)
- if (!isVisible) return null
-
return (
- <div ref={threadRef}>
+ <div ref={threadRef} className={cn({ hidden: !isVisible })}>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Preserve original visibility behavior (component remains mounted, just hidden) | |
| if (!isVisible) return null | |
| // Hide component when search doesn't match (component unmounts) | |
| if (!isVisible) return null | |
🤖 Prompt for AI Agents
In `@apps/pro-web/components/routes/browse/browse-list-item.tsx` around lines 146
- 148, The comment is wrong: returning null unmounts the component but the
comment claims it stays mounted; either update the comment to state that
"returning null unmounts the component" or change the behavior to keep the
component mounted by replacing the early return of null (the isVisible check)
with rendering the element with a hidden style/class (e.g., use CSS
visibility:hidden or a "hidden" class) so state/scroll are preserved; locate the
isVisible check in the BrowseListItem component (the if (!isVisible) return null
line) and apply one of these two fixes.
| <Button | ||
| type="button" | ||
| variant="ghost" | ||
| size="sm" | ||
| className="size-6 p-0 text-foreground/70 hover:text-destructive-foreground hover:bg-destructive" | ||
| onClick={(e) => { | ||
| e.preventDefault() | ||
| e.stopPropagation() | ||
| onDeleteSection(section.title) | ||
| }} | ||
| title={`Delete ${section.title} section`} | ||
| // disabled={isLoading || workspaceProcessingState !== 'idle'} | ||
| aria-label={`Delete ${section.title} section`} | ||
| > | ||
| <XIcon className="size-3.5" /> | ||
| </Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add type="button" to prevent unintended form submissions.
The type="button" attribute was removed from this Button. Without it, the button defaults to type="submit" when nested inside a form element, which could trigger unintended form submissions despite the e.preventDefault() call.
Proposed fix
<Button
variant="ghost"
size="sm"
+ type="button"
className="size-6 p-0 text-foreground/70 hover:text-destructive-foreground hover:bg-destructive"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onDeleteSection(section.title)
}}
title={`Delete ${section.title} section`}
aria-label={`Delete ${section.title} section`}
>🤖 Prompt for AI Agents
In
`@apps/pro-web/components/routes/workspace/document-tab/workspace-document-section-item.tsx`
around lines 113 - 126, The Button for deleting a section currently lacks an
explicit type and may default to "submit" inside a form; update the Button
component (the instance rendering XIcon with onClick that calls
onDeleteSection(section.title)) to include type="button" so it cannot trigger
form submission, keeping the existing e.preventDefault()/e.stopPropagation and
accessibility title/aria-label intact.
| if (!queryDoc) { | ||
| throw new Error('Document not found in workspace structure') | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Check how useDocumentContent is used and whether callers handle errors
rg -n --type=tsx --type=ts -A 5 'useDocumentContent\(' apps/pro-web/Repository: bitcashorg/masterbots
Length of output: 92
🏁 Script executed:
# Also check the full implementation of use-document-content.ts to understand the context
cat -n apps/pro-web/lib/queries/use-document-content.tsRepository: bitcashorg/masterbots
Length of output: 26292
🏁 Script executed:
# Search for error handling patterns in the codebase (try/catch around useDocumentContent or error boundary usage)
rg -n --type=tsx --type=ts 'try\s*\{' apps/pro-web/ | head -20Repository: bitcashorg/masterbots
Length of output: 92
🏁 Script executed:
# Search for useDocumentContent usage across TypeScript files
rg -n 'useDocumentContent\(' apps/pro-web/ -A 5Repository: bitcashorg/masterbots
Length of output: 5089
🏁 Script executed:
# Also search for error handling patterns in components that might use this hook
rg -n 'useDocumentContent\|\.isError\|\.error' apps/pro-web/ | head -30Repository: bitcashorg/masterbots
Length of output: 47
🏁 Script executed:
# Check if there are error boundaries in the app
rg -n 'ErrorBoundary|error\.tsx|catch' apps/pro-web/app -A 3 | head -40Repository: bitcashorg/masterbots
Length of output: 3235
🏁 Script executed:
# Look at how callers actually handle the query result - check for error handling
rg -n 'useDocumentContent\(' apps/pro-web/ -A 20 | grep -E 'isError|\.error|loading|isPending|isLoading'Repository: bitcashorg/masterbots
Length of output: 380
🏁 Script executed:
# Examine one of the main calling components to see how they use the hook
head -100 apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsxRepository: bitcashorg/masterbots
Length of output: 3179
🏁 Script executed:
# Check how data is being used in one of the callers - look for null checks or error handling
sed -n '50,100p' apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsxRepository: bitcashorg/masterbots
Length of output: 1582
🏁 Script executed:
# Search for any null coalescing or fallback handling for document data
rg -n 'currentDocumentData|documentData|documentContent' apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx -A 3 -B 1Repository: bitcashorg/masterbots
Length of output: 416
🏁 Script executed:
# Check if any of the calling components check for .isError or .error
rg -n '\.isError|\.error' apps/pro-web/components/routes/workspace/document-tab/ -B 2 -A 2Repository: bitcashorg/masterbots
Length of output: 600
🏁 Script executed:
# Check the wrapper hook use-workspace-document-content-hook.ts to see how it handles errors
cat -n apps/pro-web/lib/hooks/use-workspace-document-content-hook.tsRepository: bitcashorg/masterbots
Length of output: 21631
🏁 Script executed:
# Look at use-workspace-chat.tsx to see how it uses the result
sed -n '140,180p' apps/pro-web/lib/hooks/use-workspace-chat.tsxRepository: bitcashorg/masterbots
Length of output: 1328
Ensure error state is handled in calling components.
The error thrown when queryDoc is not found is appropriate for the TanStack Query pattern. However, callers destructure { data, isLoading, isPending } without checking the .isError or .error properties. Components use fallbacks like documentData?.sections || [], which won't help when the query is in an error state—they should explicitly check for .isError and display appropriate error UI instead of silently falling back to empty values.
🤖 Prompt for AI Agents
In `@apps/pro-web/lib/queries/use-document-content.ts` around lines 123 - 125, The
hook useDocumentContent throws when queryDoc is missing (the Error('Document not
found in workspace structure') path), so update all callers that currently
destructure only { data, isLoading, isPending } to also destructure { isError,
error } from useDocumentContent and handle the error case explicitly (render an
error UI or message when isError is true) instead of silently falling back to
documentData?.sections || []; search for usages of useDocumentContent and adjust
their render logic to check isError and use error for diagnostics.
| // If missing, fall back to the parent profile thread URL (no error). | ||
| if (!threadQuestionSlug) { | ||
| if (type === 'chatbot') { | ||
| return ['', 'b', chatbot?.toLowerCase() ?? '', threadSlug ?? ''].join( | ||
| '/', | ||
| ) | ||
| return '/' | ||
| } | ||
| return [ | ||
| '', | ||
| 'u', | ||
| usernameSlug ?? '', | ||
| 't', | ||
| normalizeCategorySlug(category ?? ''), | ||
| normalizeDomainSlug(domain ?? '', raw), | ||
| chatbot?.toLowerCase() ?? '', | ||
| threadSlug ?? '', | ||
| ].join('/') | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fallback URL may produce malformed paths with empty segments.
When threadQuestionSlug is missing and fallback kicks in, if other params (like usernameSlug, category, etc.) are also empty, the URL could contain empty segments (e.g., /u//t///). While this is an edge case, consider adding a guard or using the existing missingKeys check before constructing the fallback.
Suggested defensive check
// If missing, fall back to the parent profile thread URL (no error).
if (!threadQuestionSlug) {
if (type === 'chatbot') {
+ if (!chatbot || !threadSlug) return '/'
return ['', 'b', chatbot?.toLowerCase() ?? '', threadSlug ?? ''].join(
'/',
)
}
+ if (!usernameSlug || !category || !domain || !chatbot || !threadSlug) return '/'
return [
'',
'u',🤖 Prompt for AI Agents
In `@apps/pro-web/lib/url.ts` around lines 486 - 503, The fallback URL
construction inside the if (!threadQuestionSlug) branch can produce empty path
segments; modify the block (the fallback branch that currently returns arrays
joined with '/') to either guard early using the existing missingKeys check for
required fields or build the parts array and remove empty segments before
joining while preserving the leading slash (i.e., keep the initial empty string
element). Target the fallback logic in the function in apps/pro-web/lib/url.ts
where the chatbot/chat branch and the longer 'u'/'t' branch are returned and
ensure you filter out falsy/empty string segments (but still preserve the
initial leading slash) or return a safe fallback when required keys are missing.
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@apps/pro-web/app/actions/document-operations/log-document-operation.ts`:
- Around line 42-53: In getHasuraConfig, remove the NEXT_PUBLIC fallback for the
admin secret: stop using process.env.NEXT_PUBLIC_HASURA_ADMIN_SECRET and only
read server-side variables (process.env.HASURA_GRAPHQL_ADMIN_SECRET and
process.env.HASURA_ADMIN_SECRET) so the adminSecret cannot be exposed to the
client; update the adminSecret assignment in getHasuraConfig accordingly and
ensure the url fallback to NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT is left only if
intentionally needed for runtime, otherwise use the server-only
HASURA_GRAPHQL_ENDPOINT.
In `@apps/pro-web/lib/hooks/use-workspace-chat.tsx`:
- Around line 246-254: Remove the unnecessary "as any" casts on newDocument: use
the typed properties directly (newDocument?.currentVersion and
newDocument?.versions). Compute versionAfter by checking typeof
newDocument?.currentVersion === 'number' and assign newDocument.currentVersion
or null. For checksumAfter, call newDocument?.versions?.find with the callback
parameter typed as WorkspaceDocumentVersion (e.g., (v: WorkspaceDocumentVersion)
=> v?.version === versionAfter) and read ?.checksum ?? null. This change affects
the newDocument, versionAfter and checksumAfter expressions created after the
useUpdateDocumentContent result.
- Around line 959-963: The double-cast bypass in the call to
followingQuestionsPrompt should be replaced by constructing scopedHistory with
the correct MessageDB shape (Array<MessageDB & { id?: string }>) instead of
using "scopedHistory as unknown as any"; locate where scopedHistory is built in
use-workspace-chat.tsx and map each item to include the MessageDB fields (role,
content, messageId -> id if needed, createdAt) or write a small transform
function (e.g., mapScopedToMessageDB) that converts the existing objects to
MessageDB objects, then pass the transformed array into followingQuestionsPrompt
so TypeScript types line up without unsafe casts (mirror the correct typing
pattern from use-mb-chat.tsx but implement a proper mapping rather than double
casting).
🧹 Nitpick comments (3)
apps/pro-web/lib/hooks/use-workspace-chat.tsx (1)
242-267: Good best-effort logging pattern, but consider consolidating.The completion logging logic is correctly wrapped in try/catch to ensure it never blocks UX. However, this pattern is repeated in multiple places (lines 242-267, 758-774, 1007-1022).
Consider extracting to a helper function to reduce duplication:
♻️ Optional: Extract logging helper
async function finalizeDOcOpLogging( opIdPromise: Promise<string | null> | null, mode: 'complete' | 'fail', data?: { versionAfter?: number | null; checksumAfter?: string | null; error?: string | null } ) { try { const opId = await opIdPromise if (opId) { await logDocumentOperation({ mode, id: opId, ...data, }) } } catch { // ignore - best effort } }apps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/up.sql (1)
74-88: Consider partial index for nullable columns.The indexes on
(doc_node_id, section_id)and(doc_node_id, scope_type, scope_id, ...)include nullable columns. If queries frequently filter for non-NULL values, partial indexes could be more efficient:CREATE INDEX ... WHERE section_id IS NOT NULL;This is a minor optimization and can be deferred if query patterns aren't yet established.
apps/pro-web/app/actions/document-operations/log-document-operation.ts (1)
55-82: Consider usinggetHasuraClient()from mb-lib orhasura.service.tspattern.The codebase provides established utilities for Hasura GraphQL operations. The
getHasuraClient()function exported from mb-lib includes comprehensive environment validation and error handling for multiple secret variants (HASURA_GRAPHQL_LOCAL_ADMIN_SECRET vs HASURA_GRAPHQL_ADMIN_SECRET). For consistency with the server-side pattern established inapps/pro-web/services/hasura/hasura.service.ts, refactor this custom fetch wrapper to use the shared client instead.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/hasura/metadata/databases/masterbots/tables/public_document_operations.yamlapps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/down.sqlapps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/up.sqlapps/pro-web/app/actions/document-operations/log-document-operation.tsapps/pro-web/lib/hooks/use-workspace-chat.tsx
🧰 Additional context used
📓 Path-based instructions (9)
apps/hasura/migrations/masterbots/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Store and manage Hasura migrations under apps/hasura/migrations/masterbots/
Store Hasura-managed migrations in timestamped folders under apps/hasura/migrations/masterbots/
Keep Hasura-managed database migrations in timestamped folders under apps/hasura/migrations/masterbots/
Files:
apps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/down.sqlapps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/up.sql
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: Use lowercase with dash-separated directory and file names; use specific extensions: .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Use Bun for package management and scripts instead of npm/yarn
Use Biome for formatting and linting via bun format-and-lint:fix; avoid Prettier/ESLint configs
Files:
apps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/down.sqlapps/hasura/metadata/databases/masterbots/tables/public_document_operations.yamlapps/pro-web/lib/hooks/use-workspace-chat.tsxapps/pro-web/app/actions/document-operations/log-document-operation.tsapps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/up.sql
apps/{web,pro-web}/lib/hooks/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/lib/hooks/**: Place custom React hooks under lib/hooks/
Encapsulate complex state in custom hooks and separate presentation from business logicStore custom React hooks under lib/hooks/
Files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
apps/{web,pro-web}/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/**/*.tsx: Declare React components with the function keyword
Name React components using PascalCase
Order component files: imports → constants → component → styled components → types/interfaces
Use functional components and hooks; type props with a TypeScript interface
Use regular function references (not inline arrows) for React event handlers to avoid extra re-renders
apps/{web,pro-web}/**/*.tsx: Use regular function references for event handlers instead of inline arrow functions in JSX
Declare React components with the function keyword (function ComponentName() {})
Use PascalCase for React component names
Order React component files: imports, constants, component, styled components, types/interfaces
Style components with Tailwind CSS utility classes
Files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use meaningful variable names; prefix booleans with is/has/does/should
Use interface for objects/classes; type for unions/tuples/aliases; enum for fixed sets; const for literals
Avoid any; prefer specific types
Leverage type inference where clear; annotate function params and return types
Prefer type assertions with 'as' over angle-bracket syntax
Use type guards to narrow types in conditionals
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use modern JavaScript features with TypeScript across the codebase
Use meaningful names; prefix booleans with is/has/does/should (e.g., isDisabled)
Receive an object and return an object (RORO) for functions interfacing with external services
Type definitions: use interface for objects/classes; type for unions/tuples/aliases; const for literals; enum for fixed sets
Avoid any; prefer specific types
Leverage type inference; omit explicit annotations when obvious
Add explicit type annotations for function parameters and return values
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types within conditionals
Favor composition over inheritance in code structure
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions
Use interface for object shapes/classes; type for unions/tuples/aliases; const for literals; enum for fixed enumerations
Avoid using any; prefer precise types
Leverage type inference when clear; omit redundant annotations
Explicitly annotate function parameters and return types
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types safely
Use meaningful variable names; prefix booleans with is/has/does/should
Favor composition over inheritance
Use lowercase dash-separated names for files and folders; use extensions like .config.ts, .test.ts, .context.tsx, .typ...
Files:
apps/pro-web/lib/hooks/use-workspace-chat.tsxapps/pro-web/app/actions/document-operations/log-document-operation.ts
apps/{web,pro-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/**/*.{ts,tsx}: Use lowercase kebab-case for directories and filenames (components can omit extension)
Verify DOM operations and consider timing (RAF/delays) when manipulating the DOM in React
Use Tailwind CSS utility classes for styling React components
apps/{web,pro-web}/**/*.{ts,tsx}: Use TypeScript to define React props types (interfaces for props)
Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Separate presentation from business logic and compose small components
Use hasura.service.ts for all GraphQL operations from UI layers
apps/{web,pro-web}/**/*.{ts,tsx}: Prefer Server Components; use the use client directive only when client-side features are required
Implement Nested Layouts using the App Router
Use Streaming and Suspense in Next.js 14/15 where appropriate
Follow Next.js file conventions as per documentation
Use IndexedDB for local caching to enable immediate UI updates
Files:
apps/pro-web/lib/hooks/use-workspace-chat.tsxapps/pro-web/app/actions/document-operations/log-document-operation.ts
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
**/*.tsx: Avoid inline arrow functions in JSX props; use stable function references for event handlers
Declare React components with the function keyword (functional components)
Use PascalCase for React component names
Use functional components and hooks instead of class components
Type component props with TypeScript interfaces
Files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
apps/{web,pro-web}/lib/hooks/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Place custom React hooks in lib/hooks/
Files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
apps/{web,pro-web}/app/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/app/**: Prefer Next.js Server Components; use 'use client' only when necessary
Leverage Streaming and Suspense for responsiveness in Next.js 15
Follow Next.js file conventions in the App Router
apps/{web,pro-web}/app/**: Leverage Server Components by default; use the use client directive only when client-side features are needed
Implement Nested Layouts using the App Router
Use Streaming and Suspense features in Next.js 14/15 where appropriate
Follow Next.js file conventions for routing and layout in the App Router
Optimize data fetching: fetch on the server, fetch where needed, and use preload patterns to avoid waterfalls
Files:
apps/pro-web/app/actions/document-operations/log-document-operation.ts
🧠 Learnings (25)
📓 Common learnings
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-21T19:47:50.195Z
Learning: For repo bitcashorg/masterbots (apps/pro-web), AndlerRL prefers integrating TanStack Query into React Context Hooks wherever they fit to manage shared server state and side effects, with a longer-term plan to align broader state management with TanStack Query and potentially replace React Context hooks. Captured from PR `#584` discussion on Nov 21, 2025.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 542
File: apps/web/components/layout/profile/layout.tsx:67-68
Timestamp: 2025-08-29T20:34:24.433Z
Learning: AndlerRL prefers skeleton loading states over conditional rendering for better UX when components are waiting for data to load. Components should show loading indicators rather than blank spaces.
📚 Learning: 2024-12-05T01:08:50.085Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The backend uses Hasura with proper table relationships, permissions, and metadata structure defined in YAML configurations.
Applied to files:
apps/hasura/metadata/databases/masterbots/tables/public_document_operations.yaml
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-10-24T20:40:56.510Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 573
File: apps/pro-web/components/routes/pro/pro.tsx:49-49
Timestamp: 2025-10-24T20:40:56.510Z
Learning: In apps/pro-web/lib/hooks/use-mb-chat.tsx, the useMBChat() hook returns a tuple [MBChatHookState, MBChatHookActions], not a direct object. Use array destructuring to access state (index 0) and actions (index 1), e.g., const [state, actions] = useMBChat() or const [{ isLoading }] = useMBChat().
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-02-17T16:51:19.102Z
Learnt from: Bran18
Repo: bitcashorg/masterbots PR: 376
File: apps/masterbots.ai/components/routes/chat/chat.tsx:92-102
Timestamp: 2025-02-17T16:51:19.102Z
Learning: For the Chat component in apps/masterbots.ai/components/routes/chat/chat.tsx, the developer prefers to verify potential optimizations through local testing before accepting suggestions, particularly for performance-related changes like useCallback usage in debounce implementations.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2024-11-14T21:12:19.789Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 302
File: apps/masterbots.ai/lib/hooks/use-mb-chat.ts:0-0
Timestamp: 2024-11-14T21:12:19.789Z
Learning: In `apps/masterbots.ai/lib/hooks/use-mb-chat.ts`, for handling message ordering issues, prefer simple solutions like using `setTimeout` over adding sequence numbers to the message schema, to keep the codebase simple.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-09-04T21:31:18.830Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: The pro-web workspace system has identified performance bottlenecks in markdown parsing, AI streaming response handling, selection range management, Base64 document processing, and multiple debounced state operations that require optimization and better coordination.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-09-04T21:31:18.830Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: The pro-web workspace features have complex performance considerations including markdown parsing optimization, AI streaming response handling, state persistence coordination, and selection range management that require careful monitoring and optimization.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Pro workspace editors: controlled/uncontrolled patterns, Markdown utilities, section state sync, and timed DOM operations
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-06-13T08:55:45.217Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 508
File: apps/masterbots.ai/lib/helpers/ai-helpers.ts:280-283
Timestamp: 2025-06-13T08:55:45.217Z
Learning: In `apps/masterbots.ai/lib/helpers/ai-helpers.ts` the `verifyDuplicateMessage` function should:
• Use `message.content` as the sole de-duplication key so provisional (no-slug) and persisted (slugged) messages merge properly.
• Return the full `message` object (not just a string key) for the continuation prompts (`CONTINUE_GENERATION_PROMPT`, `CONTINUE_GENERATION_PROMPT_2`) because their content repeats and they must remain distinct.
• No explicit `!message.content` guard is needed; `return message.content` already yields `undefined` when content is missing.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-07-02T19:46:45.701Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 519
File: apps/masterbots.ai/lib/hooks/use-chat-attachments.ts:276-297
Timestamp: 2025-07-02T19:46:45.701Z
Learning: In `apps/masterbots.ai/lib/hooks/use-chat-attachments.ts`, the `validateTextContent` function intentionally uses `slugify()` with character removal (`remove: /[^\w\s]/g`) followed by splitting on hyphens to count words. This approach is preferred over simple whitespace splitting because it removes special characters (like those found in code text) and counts only "true words", making it more robust for validating pasted content that may contain mixed text and code.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2024-10-23T00:00:06.253Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 287
File: apps/masterbots.ai/components/routes/chat/share-button.tsx:10-29
Timestamp: 2024-10-23T00:00:06.253Z
Learning: When implementing the `handleShare` function in `apps/masterbots.ai/components/routes/chat/share-button.tsx`, prefer using modern APIs like the Clipboard API over deprecated methods like `document.execCommand('copy')`, as the project no longer needs to support very old web browsers.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-09-04T21:31:18.830Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: Successfully created comprehensive GitHub issue `#555` consolidating performance feedback and edge-case management for pro-web workspace features, including prioritized action items, testing requirements, and specific file references for implementation.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-09-04T21:31:18.830Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: Edge cases for pro-web workspace include concurrent document editing, AI streaming interruptions, large document handling, selection range boundary conditions, and storage failure scenarios that need comprehensive testing and error handling.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2024-10-14T18:40:46.645Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 283
File: apps/masterbots.ai/components/routes/chat/admin-mode-approve.tsx:11-15
Timestamp: 2024-10-14T18:40:46.645Z
Learning: In the project, the `adminApproveThread` function within the `useThread` hook manages error handling and loading state, so components using it don't need to implement additional error handling unless they introduce side effects.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-11-21T18:00:08.455Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 590
File: apps/web/lib/hooks/use-profile.tsx:60-78
Timestamp: 2025-11-21T18:00:08.455Z
Learning: In apps/web/lib/hooks/use-profile.tsx: The sessionUser state should be populated directly from session.user (JWT) and not by fetching from Hasura on the client side. The useEffect should only synchronize sessionUser with session.user. Client-side Hasura fetches for user data should be avoided; user data comes from the JWT/session. Only when users update their profile should we refresh both the database and JWT token (via NextAuth session update trigger).
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2024-10-23T07:33:36.041Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 287
File: apps/masterbots.ai/lib/hooks/use-thread-visibility.tsx:3-3
Timestamp: 2024-10-23T07:33:36.041Z
Learning: Always use explicit imports for React hooks to maintain consistency across the monorepo, and avoid using both named and default imports simultaneously.
Applied to files:
apps/pro-web/lib/hooks/use-workspace-chat.tsx
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single interaction point for GraphQL operations with Hasura
Applied to files:
apps/pro-web/app/actions/document-operations/log-document-operation.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use hasura.service.ts for all GraphQL operations from UI layers
Applied to files:
apps/pro-web/app/actions/document-operations/log-document-operation.ts
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single point for all GraphQL interactions
Applied to files:
apps/pro-web/app/actions/document-operations/log-document-operation.ts
📚 Learning: 2025-05-06T09:54:25.327Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 468
File: apps/masterbots.ai/app/api/cron/delete-requested-accounts/route.ts:70-81
Timestamp: 2025-05-06T09:54:25.327Z
Learning: For Hasura GraphQL operations in server-side code (including API routes and cron jobs), use the `getHasuraClient()` function from mb-lib rather than implementing custom fetch functions. This provides proper error handling, environment validation, and follows the established pattern in the codebase.
Applied to files:
apps/pro-web/app/actions/document-operations/log-document-operation.ts
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Keep Hasura-managed database migrations in timestamped folders under apps/hasura/migrations/masterbots/
Applied to files:
apps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/up.sql
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store Hasura-managed migrations in timestamped folders under apps/hasura/migrations/masterbots/
Applied to files:
apps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/up.sql
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store and manage Hasura migrations under apps/hasura/migrations/masterbots/
Applied to files:
apps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/up.sql
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Perform database schema changes via Hasura console (task console), then migrations (task migrate)
Applied to files:
apps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/up.sql
🔇 Additional comments (11)
apps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/down.sql (1)
1-24: LGTM! Safe and idempotent rollback migration.The down migration correctly:
- Drops indexes before the table (avoiding dependency errors)
- Uses
IF EXISTSguards for idempotent execution- Safely checks for FK constraint existence before dropping
- Follows the established Hasura migration patterns in timestamped folders
apps/hasura/metadata/databases/masterbots/tables/public_document_operations.yaml (1)
35-38: Verify access to rows with NULL thread_id.The filter
thread.user_id._eq: X-Hasura-User-Idtraverses the object relationship, butthread_idis nullable per the migration (some operations may start before a thread_id is known). Rows withthread_id = NULLwill fail this filter since there's no related thread row, making them inaccessible to users.If orphan rows should be accessible, consider adjusting the filter:
filter: _or: - thread: user_id: _eq: X-Hasura-User-Id - thread_id: _is_null: trueOtherwise, if this is intentional (orphan ops should not be queryable), this is fine as-is.
apps/pro-web/app/actions/document-operations/log-document-operation.ts (2)
84-129: LGTM on the start operation flow.The insert mutation is well-structured:
- Properly maps camelCase args to snake_case DB columns
- Sets sensible defaults with
?? nullfallbacks- Returns the operation ID for subsequent updates
- Best-effort pattern with caller-side error handling is appropriate
131-157: LGTM on the complete/fail operation flow.The update mutation correctly:
- Determines status from mode
- Uses pk_columns for targeted update
- Provides default error message for fail mode
- Returns updated ID for confirmation
apps/pro-web/lib/hooks/use-workspace-chat.tsx (5)
51-60: LGTM on UUID validation helper.The
asUuidOrNullfunction is a good defensive measure to prevent noisy Hasura mutation errors when thread IDs may not be valid UUIDs (e.g., during initial state).
62-81: LGTM on operation type inference.The
inferOperationTypefunction provides reasonable heuristics for classifying operations. The priority order (selection → keyword matching) is sensible.
869-874: VerifygetCanonicalDocNodeIdhandles all edge cases.The fallback chain
(documentData as any)?.docNodeId ?? (documentData as any)?.idis reasonable, but the function call could returnnull. The subsequent fallback on line 877 (docRootId ?? documentKey) ensures a value, which is good.
927-969: Good fail-closed pattern for scope conversation window.The try/catch ensures workspace chat continues working even if the scope window fetch fails, falling back to the original
userPrompt. This is the correct resilience pattern.
992-999: LGTM on scope passthrough to append.The scope metadata is correctly passed through to enable downstream processing. The structure matches the pattern mentioned in the AI summary for
use-mb-chat.tsx.apps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/up.sql (2)
1-44: LGTM! Well-structured table definition.The table design is solid:
- UUID primary key with
gen_random_uuid()default- Appropriate column types and nullability
- Sensible default for
statuscolumn- Good separation of concerns (context, anchoring, operation, prompts, versioning)
IF NOT EXISTSmakes the migration idempotent
46-72: Good defensive FK constraint pattern.The conditional FK creation is well-implemented:
- Checks if
threadtable exists before adding constraint- Checks if constraint already exists to prevent errors on re-run
ON DELETE SET NULLis appropriate for optional relationships
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| function getHasuraConfig() { | ||
| // Keep flexible: supports both server-only and next-public env patterns. | ||
| const url = | ||
| process.env.HASURA_GRAPHQL_ENDPOINT ?? | ||
| process.env.NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT | ||
| const adminSecret = | ||
| process.env.HASURA_GRAPHQL_ADMIN_SECRET ?? | ||
| process.env.HASURA_ADMIN_SECRET ?? | ||
| process.env.NEXT_PUBLIC_HASURA_ADMIN_SECRET | ||
|
|
||
| return { url, adminSecret } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove NEXT_PUBLIC_* fallbacks for admin secret.
The getHasuraConfig function falls back to NEXT_PUBLIC_HASURA_ADMIN_SECRET (line 50). Environment variables prefixed with NEXT_PUBLIC_ are exposed to the browser bundle. While this file uses 'use server', the fallback pattern is risky if copy-pasted elsewhere and contradicts security best practices.
Since this is a server action, only server-side env vars should be used.
🔒 Suggested fix
function getHasuraConfig() {
- // Keep flexible: supports both server-only and next-public env patterns.
const url =
- process.env.HASURA_GRAPHQL_ENDPOINT ??
- process.env.NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT
+ process.env.HASURA_GRAPHQL_ENDPOINT
const adminSecret =
- process.env.HASURA_GRAPHQL_ADMIN_SECRET ??
- process.env.HASURA_ADMIN_SECRET ??
- process.env.NEXT_PUBLIC_HASURA_ADMIN_SECRET
+ process.env.HASURA_GRAPHQL_ADMIN_SECRET ??
+ process.env.HASURA_ADMIN_SECRET
return { url, adminSecret }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function getHasuraConfig() { | |
| // Keep flexible: supports both server-only and next-public env patterns. | |
| const url = | |
| process.env.HASURA_GRAPHQL_ENDPOINT ?? | |
| process.env.NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT | |
| const adminSecret = | |
| process.env.HASURA_GRAPHQL_ADMIN_SECRET ?? | |
| process.env.HASURA_ADMIN_SECRET ?? | |
| process.env.NEXT_PUBLIC_HASURA_ADMIN_SECRET | |
| return { url, adminSecret } | |
| } | |
| function getHasuraConfig() { | |
| const url = | |
| process.env.HASURA_GRAPHQL_ENDPOINT | |
| const adminSecret = | |
| process.env.HASURA_GRAPHQL_ADMIN_SECRET ?? | |
| process.env.HASURA_ADMIN_SECRET | |
| return { url, adminSecret } | |
| } |
🤖 Prompt for AI Agents
In `@apps/pro-web/app/actions/document-operations/log-document-operation.ts`
around lines 42 - 53, In getHasuraConfig, remove the NEXT_PUBLIC fallback for
the admin secret: stop using process.env.NEXT_PUBLIC_HASURA_ADMIN_SECRET and
only read server-side variables (process.env.HASURA_GRAPHQL_ADMIN_SECRET and
process.env.HASURA_ADMIN_SECRET) so the adminSecret cannot be exposed to the
client; update the adminSecret assignment in getHasuraConfig accordingly and
ensure the url fallback to NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT is left only if
intentionally needed for runtime, otherwise use the server-only
HASURA_GRAPHQL_ENDPOINT.
| const versionAfter = | ||
| typeof (newDocument as any)?.currentVersion === 'number' | ||
| ? (newDocument as any).currentVersion | ||
| : null | ||
|
|
||
| const checksumAfter = | ||
| (newDocument as any)?.versions?.find?.( | ||
| (v: any) => v?.version === versionAfter, | ||
| )?.checksum ?? null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, check if the file exists and get basic info
fd "use-workspace-chat.tsx" apps/pro-web/lib/hooks/Repository: bitcashorg/masterbots
Length of output: 110
🏁 Script executed:
#!/bin/bash
# Read the file and show lines around 246-254 with context
cat -n apps/pro-web/lib/hooks/use-workspace-chat.tsx | sed -n '230,270p'Repository: bitcashorg/masterbots
Length of output: 1396
🏁 Script executed:
#!/bin/bash
# Search for type definitions related to documentData and newDocument
rg -n "documentData|newDocument" apps/pro-web/lib/hooks/use-workspace-chat.tsx | head -30Repository: bitcashorg/masterbots
Length of output: 1066
🏁 Script executed:
#!/bin/bash
# Get more context around line 186 to see where documentData comes from
cat -n apps/pro-web/lib/hooks/use-workspace-chat.tsx | sed -n '180,195p'Repository: bitcashorg/masterbots
Length of output: 604
🏁 Script executed:
#!/bin/bash
# Get more context around line 218 to see where newDocument comes from
cat -n apps/pro-web/lib/hooks/use-workspace-chat.tsx | sed -n '210,230p'Repository: bitcashorg/masterbots
Length of output: 813
🏁 Script executed:
#!/bin/bash
# Search for the mutation/function that returns newDocument
rg -n "newDocument" apps/pro-web/lib/hooks/use-workspace-chat.tsx -B3 | head -50Repository: bitcashorg/masterbots
Length of output: 1082
🏁 Script executed:
#!/bin/bash
# Check imports and type definitions at the beginning of the file
head -100 apps/pro-web/lib/hooks/use-workspace-chat.tsxRepository: bitcashorg/masterbots
Length of output: 3105
🏁 Script executed:
#!/bin/bash
# Find the lib/queries files
fd -t f -e ts -e tsx lib/queriesRepository: bitcashorg/masterbots
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Search for useDocumentContent and useUpdateDocumentContent definitions
rg -n "useDocumentContent|useUpdateDocumentContent" lib/queries -A10 | head -80Repository: bitcashorg/masterbots
Length of output: 116
🏁 Script executed:
#!/bin/bash
# Look for type definitions related to document structure
rg -n "currentVersion|versions" lib/queries -B2 -A2 | head -100Repository: bitcashorg/masterbots
Length of output: 116
🏁 Script executed:
#!/bin/bash
# Find where lib/queries actually is
fd -t d queries apps/Repository: bitcashorg/masterbots
Length of output: 90
🏁 Script executed:
#!/bin/bash
# Search more broadly for useDocumentContent definition
rg -n "export.*useDocumentContent|function useDocumentContent" --type ts --type tsxRepository: bitcashorg/masterbots
Length of output: 92
🏁 Script executed:
#!/bin/bash
# Check the structure of apps directory
ls -la apps/Repository: bitcashorg/masterbots
Length of output: 336
🏁 Script executed:
#!/bin/bash
# Search for useDocumentContent definition in the correct location
rg -n "export.*useDocumentContent|function useDocumentContent" apps/pro-web/lib/queries/ -A10Repository: bitcashorg/masterbots
Length of output: 1031
🏁 Script executed:
#!/bin/bash
# List files in apps/pro-web/lib/queries/
ls -la apps/pro-web/lib/queries/Repository: bitcashorg/masterbots
Length of output: 754
🏁 Script executed:
#!/bin/bash
# Search for useUpdateDocumentContent
rg -n "useUpdateDocumentContent" apps/pro-web/lib/queries/ -A10Repository: bitcashorg/masterbots
Length of output: 10048
🏁 Script executed:
#!/bin/bash
# Search for type definitions with currentVersion
rg -n "currentVersion" apps/pro-web/lib/queries/ -B5 -A5Repository: bitcashorg/masterbots
Length of output: 14775
🏁 Script executed:
#!/bin/bash
# Find the WorkspaceDocumentMetadata type definition
rg -n "type WorkspaceDocumentMetadata|interface WorkspaceDocumentMetadata" -A30Repository: bitcashorg/masterbots
Length of output: 1953
🏁 Script executed:
#!/bin/bash
# Check what useUpdateDocumentContent returns by looking at the return statement
cat -n apps/pro-web/lib/queries/use-document-content.ts | sed -n '325,335p'Repository: bitcashorg/masterbots
Length of output: 553
🏁 Script executed:
#!/bin/bash
# Search for any type definitions or imports related to WorkspaceDocumentMetadata
rg -n "WorkspaceDocumentMetadata" apps/pro-web/lib/queries/ -B2 -A5 | head -100Repository: bitcashorg/masterbots
Length of output: 7432
Remove unnecessary as any casts—the type is already properly defined.
The newDocument object returned from useUpdateDocumentContent is already typed as WorkspaceDocumentMetadata, which explicitly defines both currentVersion and versions properties. The as any casts at lines 247–248 and 252 bypass TypeScript's type safety unnecessarily:
// Remove the `as any` casts:
const versionAfter =
typeof newDocument?.currentVersion === 'number'
? newDocument.currentVersion
: null
const checksumAfter =
newDocument?.versions?.find?.(
(v: WorkspaceDocumentVersion) => v?.version === versionAfter,
)?.checksum ?? nullUpdate the type hint for the find callback parameter to use the proper WorkspaceDocumentVersion type instead of any.
🤖 Prompt for AI Agents
In `@apps/pro-web/lib/hooks/use-workspace-chat.tsx` around lines 246 - 254, Remove
the unnecessary "as any" casts on newDocument: use the typed properties directly
(newDocument?.currentVersion and newDocument?.versions). Compute versionAfter by
checking typeof newDocument?.currentVersion === 'number' and assign
newDocument.currentVersion or null. For checksumAfter, call
newDocument?.versions?.find with the callback parameter typed as
WorkspaceDocumentVersion (e.g., (v: WorkspaceDocumentVersion) => v?.version ===
versionAfter) and read ?.checksum ?? null. This change affects the newDocument,
versionAfter and checksumAfter expressions created after the
useUpdateDocumentContent result.
| finalUserPrompt = followingQuestionsPrompt( | ||
| userPrompt, | ||
| scopedHistory as unknown as any, // matches existing pattern in use-mb-chat.tsx | ||
| '', | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's see the context around the flagged lines in use-workspace-chat.tsx
cat -n apps/pro-web/lib/hooks/use-workspace-chat.tsx | sed -n '950,970p'Repository: bitcashorg/masterbots
Length of output: 793
🏁 Script executed:
# Find the definition/declaration of followingQuestionsPrompt
rg -n 'followingQuestionsPrompt' --type=ts --type=tsx -B2 -A8Repository: bitcashorg/masterbots
Length of output: 92
🏁 Script executed:
# Check use-mb-chat.tsx for the mentioned pattern
rg -n 'followingQuestionsPrompt' apps/pro-web/lib/hooks/use-mb-chat.tsx -B2 -A5Repository: bitcashorg/masterbots
Length of output: 570
🏁 Script executed:
# Find the definition of followingQuestionsPrompt in the prompts file
rg -n 'export.*followingQuestionsPrompt' -A 15Repository: bitcashorg/masterbots
Length of output: 2844
🏁 Script executed:
# Get more context from use-mb-chat.tsx around the followingQuestionsPrompt call
cat -n apps/pro-web/lib/hooks/use-mb-chat.tsx | sed -n '1450,1465p'Repository: bitcashorg/masterbots
Length of output: 539
🏁 Script executed:
# Now let's look at how scopedHistory is constructed in use-workspace-chat.tsx
cat -n apps/pro-web/lib/hooks/use-workspace-chat.tsx | sed -n '930,965p'Repository: bitcashorg/masterbots
Length of output: 1289
🏁 Script executed:
# Let's check MessageDB type to understand what's expected
rg -n 'type MessageDB|interface MessageDB' -A 10Repository: bitcashorg/masterbots
Length of output: 47
🏁 Script executed:
# Search more broadly for MessageDB definition
rg 'MessageDB' -B2 -A6 | head -50Repository: bitcashorg/masterbots
Length of output: 3705
🏁 Script executed:
# Check what types are imported/used in use-workspace-chat.tsx
head -50 apps/pro-web/lib/hooks/use-workspace-chat.tsx | grep -E 'import|from'Repository: bitcashorg/masterbots
Length of output: 1380
Replace the double cast with proper typing of scopedHistory to match the expected MessageDB structure.
The cast scopedHistory as unknown as any bypasses type safety and masks a real type mismatch. The array is constructed with role, content, messageId, and createdAt properties, but followingQuestionsPrompt expects Array<MessageDB & { id?: string }>. While this pattern appears elsewhere in use-mb-chat.tsx, it indicates a systemic issue rather than justifying the workaround. Construct scopedHistory to conform to the expected type, or create a proper transformation function that maps the shape correctly.
🤖 Prompt for AI Agents
In `@apps/pro-web/lib/hooks/use-workspace-chat.tsx` around lines 959 - 963, The
double-cast bypass in the call to followingQuestionsPrompt should be replaced by
constructing scopedHistory with the correct MessageDB shape (Array<MessageDB & {
id?: string }>) instead of using "scopedHistory as unknown as any"; locate where
scopedHistory is built in use-workspace-chat.tsx and map each item to include
the MessageDB fields (role, content, messageId -> id if needed, createdAt) or
write a small transform function (e.g., mapScopedToMessageDB) that converts the
existing objects to MessageDB objects, then pass the transformed array into
followingQuestionsPrompt so TypeScript types line up without unsafe casts
(mirror the correct typing pattern from use-mb-chat.tsx but implement a proper
mapping rather than double casting).
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx`:
- Around line 324-327: The Templates Button renders but has no behavior; either
wire it to the template flow or mark it as a placeholder—add a clear disabled
state and tooltip/title instead of leaving it clickable. Update the Button (the
one rendering LayoutTemplate and "Templates") to include disabled and a title
like "Coming soon" (or add an onClick that opens your template picker/modal if
you want real behavior), and ensure the visual state still uses
variant="outline" and size="sm" so the UI remains consistent; reference the
Button component rendering LayoutTemplate to locate where to apply the change.
- Around line 330-368: The raw vs structured state desync is caused because
editing the raw textarea (input) never updates structuredData; update the
mode-switch logic so that when viewMode changes from 'raw' to 'structured' you
attempt to parse the current input and setStructuredData accordingly (e.g.,
parse JSON/YAML or a simple delimiter format), falling back to a safe default
(empty fields) or surfacing a validation error if parsing fails; ensure you
update structuredData via setStructuredData and preserve input via setInput, and
keep the existing handleStructuredChange logic for structured edits so changes
remain bidirectionally consistent between input and structuredData.
♻️ Duplicate comments (1)
apps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsx (1)
68-85: Same issues apply as noted in the web app version.The suffix replacement fragility, state desync between modes, non-functional Templates button, and inline arrow functions noted in
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxapply equally here. Consolidating to a shared component would address all instances at once.
🧹 Nitpick comments (5)
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx (4)
68-85: Potential bug: suffix removal may fail if suffix contains regex metacharacters.
String.prototype.replacewith a string pattern only removes the first occurrence and doesn't escape regex metacharacters. If a suffix ever contains characters like.or?, this could behave unexpectedly. Currently the suffixes are safe, but this is fragile.♻️ Safer replacement approach
if (isActive) { newOptions = activeOptions.filter((id) => id !== optionId) - newInput = newInput.replace(suffix, '') + // Split and rejoin to avoid regex edge cases + const suffixIndex = newInput.indexOf(suffix) + if (suffixIndex !== -1) { + newInput = newInput.slice(0, suffixIndex) + newInput.slice(suffixIndex + suffix.length) + } } else {
87-190: Consider memoizinggetStructureFieldsfor render stability.
getStructureFields()is called on every render, creating new array/object references. While the performance impact is minimal, memoizingfieldswould prevent unnecessary re-renders of child components that depend on referential equality.♻️ Memoization suggestion
+const fields = React.useMemo(() => { // Model Detection const isO1 = selectedModel.match(/(o1|gpt-5)/i) const isClaude = selectedModel.match(/(claude|anthropic)/i) const isGemini = selectedModel.match(/(gemini)/i) - - // Determine Structure Fields - const getStructureFields = () => { if (isO1) { return [ // ... existing fields ] } // ... rest of logic - } - - const fields = getStructureFields() +}, [selectedModel])
301-322: Use stable function references for view mode toggles.As per coding guidelines, prefer regular function references over inline arrow functions in JSX to avoid extra re-renders.
♻️ Extract handlers
+const handleSetRawMode = React.useCallback(() => setViewMode('raw'), []) +const handleSetStructuredMode = React.useCallback(() => setViewMode('structured'), []) // In JSX: <button - onClick={() => setViewMode('raw')} + onClick={handleSetRawMode} // ... > Raw </button> <button - onClick={() => setViewMode('structured')} + onClick={handleSetStructuredMode} // ... > Structured </button>
301-311: Add explicittype="button"to prevent form submission edge cases.Plain
<button>elements default totype="submit". While these are in a Popover (not a form), adding explicittype="button"improves robustness if the DOM structure changes.<button + type="button" onClick={() => setViewMode('raw')} // ... >apps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsx (1)
1-375: ExtractPromptEnhancerto a shared package to eliminate duplication.This component is identically duplicated between
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxandapps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsx. Any bug fix or feature change requires updates in both locations, creating maintenance burden.Move
PromptEnhancerto a shared package (e.g.,packages/uiorpackages/shared-components) and import it from both apps. This aligns with DRY principles and ensures consistent behavior across environments.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/pro-web/components/routes/chat/prompt-form/index.tsxapps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/web/components/routes/chat/prompt-form/index.tsxapps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/components/routes/chat/prompt-form/index.tsx
🧰 Additional context used
📓 Path-based instructions (7)
apps/{web,pro-web}/components/routes/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Place page-specific components under components/routes/[feature]/
Organize page-specific components under components/routes/
Files:
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/index.tsx
apps/{web,pro-web}/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/**/*.tsx: Declare React components with the function keyword
Name React components using PascalCase
Order component files: imports → constants → component → styled components → types/interfaces
Use functional components and hooks; type props with a TypeScript interface
Use regular function references (not inline arrows) for React event handlers to avoid extra re-renders
apps/{web,pro-web}/**/*.tsx: Use regular function references for event handlers instead of inline arrow functions in JSX
Declare React components with the function keyword (function ComponentName() {})
Use PascalCase for React component names
Order React component files: imports, constants, component, styled components, types/interfaces
Style components with Tailwind CSS utility classes
Files:
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/index.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use meaningful variable names; prefix booleans with is/has/does/should
Use interface for objects/classes; type for unions/tuples/aliases; enum for fixed sets; const for literals
Avoid any; prefer specific types
Leverage type inference where clear; annotate function params and return types
Prefer type assertions with 'as' over angle-bracket syntax
Use type guards to narrow types in conditionals
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use modern JavaScript features with TypeScript across the codebase
Use meaningful names; prefix booleans with is/has/does/should (e.g., isDisabled)
Receive an object and return an object (RORO) for functions interfacing with external services
Type definitions: use interface for objects/classes; type for unions/tuples/aliases; const for literals; enum for fixed sets
Avoid any; prefer specific types
Leverage type inference; omit explicit annotations when obvious
Add explicit type annotations for function parameters and return values
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types within conditionals
Favor composition over inheritance in code structure
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions
Use interface for object shapes/classes; type for unions/tuples/aliases; const for literals; enum for fixed enumerations
Avoid using any; prefer precise types
Leverage type inference when clear; omit redundant annotations
Explicitly annotate function parameters and return types
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types safely
Use meaningful variable names; prefix booleans with is/has/does/should
Favor composition over inheritance
Use lowercase dash-separated names for files and folders; use extensions like .config.ts, .test.ts, .context.tsx, .typ...
Files:
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/index.tsx
apps/{web,pro-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/**/*.{ts,tsx}: Use lowercase kebab-case for directories and filenames (components can omit extension)
Verify DOM operations and consider timing (RAF/delays) when manipulating the DOM in React
Use Tailwind CSS utility classes for styling React components
apps/{web,pro-web}/**/*.{ts,tsx}: Use TypeScript to define React props types (interfaces for props)
Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Separate presentation from business logic and compose small components
Use hasura.service.ts for all GraphQL operations from UI layers
apps/{web,pro-web}/**/*.{ts,tsx}: Prefer Server Components; use the use client directive only when client-side features are required
Implement Nested Layouts using the App Router
Use Streaming and Suspense in Next.js 14/15 where appropriate
Follow Next.js file conventions as per documentation
Use IndexedDB for local caching to enable immediate UI updates
Files:
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/index.tsx
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: Use lowercase with dash-separated directory and file names; use specific extensions: .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Use Bun for package management and scripts instead of npm/yarn
Use Biome for formatting and linting via bun format-and-lint:fix; avoid Prettier/ESLint configs
Files:
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/index.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
**/*.tsx: Avoid inline arrow functions in JSX props; use stable function references for event handlers
Declare React components with the function keyword (functional components)
Use PascalCase for React component names
Use functional components and hooks instead of class components
Type component props with TypeScript interfaces
Files:
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/index.tsx
apps/{web,pro-web}/components/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Order component files: imports → constants → component → styled components → types/interfaces
Files:
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/index.tsx
🧠 Learnings (8)
📓 Common learnings
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-21T19:47:50.195Z
Learning: For repo bitcashorg/masterbots (apps/pro-web), AndlerRL prefers integrating TanStack Query into React Context Hooks wherever they fit to manage shared server state and side effects, with a longer-term plan to align broader state management with TanStack Query and potentially replace React Context hooks. Captured from PR `#584` discussion on Nov 21, 2025.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 542
File: apps/web/components/layout/profile/layout.tsx:67-68
Timestamp: 2025-08-29T20:34:24.433Z
Learning: AndlerRL prefers skeleton loading states over conditional rendering for better UX when components are waiting for data to load. Components should show loading indicators rather than blank spaces.
📚 Learning: 2025-02-17T16:51:19.102Z
Learnt from: Bran18
Repo: bitcashorg/masterbots PR: 376
File: apps/masterbots.ai/components/routes/chat/chat.tsx:92-102
Timestamp: 2025-02-17T16:51:19.102Z
Learning: For the Chat component in apps/masterbots.ai/components/routes/chat/chat.tsx, the developer prefers to verify potential optimizations through local testing before accepting suggestions, particularly for performance-related changes like useCallback usage in debounce implementations.
Applied to files:
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/index.tsx
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/**/*.tsx : Use functional components and hooks; type props with a TypeScript interface
Applied to files:
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsxapps/pro-web/components/routes/chat/prompt-form/index.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use TypeScript to define React props types (interfaces for props)
Applied to files:
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Applied to files:
apps/pro-web/components/routes/chat/prompt-form/index.tsx
📚 Learning: 2024-11-07T14:40:39.595Z
Learnt from: Bran18
Repo: bitcashorg/masterbots PR: 301
File: apps/masterbots.ai/components/routes/chat/chat-combobox.tsx:33-33
Timestamp: 2024-11-07T14:40:39.595Z
Learning: In `apps/masterbots.ai/components/routes/chat/chat-combobox.tsx`, the 'MB' logo is temporarily represented by the string `'MB'` until the custom MB icon is created.
Applied to files:
apps/pro-web/components/routes/chat/prompt-form/index.tsx
📚 Learning: 2025-10-24T20:40:56.510Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 573
File: apps/pro-web/components/routes/pro/pro.tsx:49-49
Timestamp: 2025-10-24T20:40:56.510Z
Learning: In apps/pro-web/lib/hooks/use-mb-chat.tsx, the useMBChat() hook returns a tuple [MBChatHookState, MBChatHookActions], not a direct object. Use array destructuring to access state (index 0) and actions (index 1), e.g., const [state, actions] = useMBChat() or const [{ isLoading }] = useMBChat().
Applied to files:
apps/pro-web/components/routes/chat/prompt-form/index.tsx
📚 Learning: 2024-12-05T01:08:50.085Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The frontend uses shadcn/ui components and follows a strict component organization with proper separation of client/server components in Next.js.
Applied to files:
apps/pro-web/components/routes/chat/prompt-form/index.tsx
🧬 Code graph analysis (2)
apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx (1)
apps/pro-web/lib/utils.ts (1)
cn(9-11)
apps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsx (1)
apps/pro-web/lib/utils.ts (1)
cn(9-11)
🔇 Additional comments (4)
apps/pro-web/components/routes/chat/prompt-form/index.tsx (4)
247-251: Good SSR/CSR-stable ID generation.Using
useId()instead ofnanoid()ensures deterministic IDs across server and client renders, preventing hydration mismatches. The colon normalization is a sensible safety measure.
481-487: PromptEnhancer integration looks correct.The component receives the necessary props (
input,setInput,selectedModel) and is positioned alongside the attachment controls. TheselectedModelfromuseModel()hook enables model-aware structured prompts.
488-598: Attachment Popover structure is well-organized.The Command-based UI with accordion for saved attachments and grouped actions for new attachments follows existing patterns. The conditional rendering based on
userHasRelatedAttachmentprovides good UX by hiding empty sections.
97-106: This concern is unfounded—the fixed ID is handled correctly by conditional logic.The
'thread-context-default'ID is used only when adding an attachment named exactly'Thread Context.txt'. If a file with that name already exists, the next one is created with name'Thread Context (1).txt'and assigned a uniquenanoid(16)ID instead. TheuniqBy([...attachments, attachment], 'id')deduplication inaddAttachmentObject(line 148, use-chat-attachments.ts) prevents duplicate IDs within the attachment list. Additionally, only onePromptForminstance exists in the codebase, eliminating cross-instance collision risk.Likely an incorrect or invalid review comment.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| <Button variant="outline" size="sm" className="gap-2"> | ||
| <LayoutTemplate className="size-4" /> | ||
| Templates | ||
| </Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Templates button has no functionality.
The button renders but has no onClick handler. If this is intentional placeholder, consider adding a disabled prop or tooltip indicating "Coming soon".
Do you want me to help implement a templates system or add a placeholder UI indicating this feature is coming?
🤖 Prompt for AI Agents
In `@apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx` around lines
324 - 327, The Templates Button renders but has no behavior; either wire it to
the template flow or mark it as a placeholder—add a clear disabled state and
tooltip/title instead of leaving it clickable. Update the Button (the one
rendering LayoutTemplate and "Templates") to include disabled and a title like
"Coming soon" (or add an onClick that opens your template picker/modal if you
want real behavior), and ensure the visual state still uses variant="outline"
and size="sm" so the UI remains consistent; reference the Button component
rendering LayoutTemplate to locate where to apply the change.
| {/* Main Input Area */} | ||
| <div className="min-h-[200px] max-h-[60vh] overflow-y-auto pr-1"> | ||
| {viewMode === 'raw' ? ( | ||
| <Textarea | ||
| value={input} | ||
| onChange={(e) => setInput(e.target.value)} | ||
| className="w-full h-[300px] resize-none font-mono text-sm" | ||
| placeholder="Enter your prompt here..." | ||
| /> | ||
| ) : ( | ||
| <div className="space-y-4"> | ||
| {fields.map((field) => ( | ||
| <div key={field.key} className="space-y-1.5"> | ||
| <Label className="text-xs font-semibold text-muted-foreground uppercase tracking-wide"> | ||
| {field.label} | ||
| </Label> | ||
| {field.type === 'textarea' ? ( | ||
| <Textarea | ||
| placeholder={field.placeholder} | ||
| value={structuredData[field.key] || ''} | ||
| onChange={(e) => | ||
| handleStructuredChange(field.key, e.target.value) | ||
| } | ||
| className="resize-y min-h-[80px]" | ||
| /> | ||
| ) : ( | ||
| <Input | ||
| placeholder={field.placeholder} | ||
| value={structuredData[field.key] || ''} | ||
| onChange={(e) => | ||
| handleStructuredChange(field.key, e.target.value) | ||
| } | ||
| /> | ||
| )} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
State desync between Raw and Structured modes.
When users edit in Raw mode, structuredData is not updated. Switching to Structured mode shows stale values from the last structured edit, not the current raw input. This could confuse users who expect bidirectional sync.
Consider either:
- Parsing the raw input back into structured fields when switching modes
- Clearing structured fields when raw input is manually edited
- Disabling mode switching after manual raw edits (with a warning)
🤖 Prompt for AI Agents
In `@apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx` around lines
330 - 368, The raw vs structured state desync is caused because editing the raw
textarea (input) never updates structuredData; update the mode-switch logic so
that when viewMode changes from 'raw' to 'structured' you attempt to parse the
current input and setStructuredData accordingly (e.g., parse JSON/YAML or a
simple delimiter format), falling back to a safe default (empty fields) or
surfacing a validation error if parsing fails; ensure you update structuredData
via setStructuredData and preserve input via setInput, and keep the existing
handleStructuredChange logic for structured edits so changes remain
bidirectionally consistent between input and structuredData.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/pro-web/services/hasura/hasura.service.ts (1)
680-742: Retry logic silently swallows errors and still throws original error.The fail-closed retry catches
retryErrbut only logs it, then falls through to throw the original error. If the retry succeeds, this works fine. But if the retry fails, the original error message (aboutscopeTitleSnapshot) is thrown instead of the actual retry failure, which can be misleading for debugging.Also, the
canonicalDocNodeIdis computed but never included in the retry'srestobject—if the original insert intended to includedocNodeId, the retry will omit it.🐛 Suggested improvement
if ( msg.includes('scopeTitleSnapshot') || msg.includes('scope_title_snapshot') ) { try { const obj = object as Record<string, unknown> const { scopeTitleSnapshot: _a, scope_title_snapshot: _b, ...rest } = obj + // Preserve docNodeId if it was intended for the original insert + const retryObject = canonicalDocNodeId == null + ? rest + : { ...rest, docNodeId: canonicalDocNodeId } const { insertMessageOne: retryMessage } = await client.mutation({ insertMessageOne: { - __args: { object: rest }, + __args: { object: retryObject }, __scalar: true, }, }) return retryMessage as Message } catch (retryErr) { console.error('Error saving new message (retry failed):', retryErr) + throw new Error('Failed to save new message (retry also failed).') } } console.error('Error saving new message:', error) throw new Error('Failed to save new message.')
🤖 Fix all issues with AI agents
In `@apps/pro-web/lib/url.ts`:
- Around line 86-89: The filter building wordMatches uses user-controlled
domainSlug directly in new RegExp which can cause ReDoS; escape regex
metacharacters in domainSlug before constructing regex (or avoid RegExp entirely
by using a safe string comparison like slug === domainSlug or
slug.split/startsWith depending on intent). Update the code around
domainSlugs.filter and the regex creation to call a safe escape helper (e.g.,
escapeRegex(domainSlug)) or replace the regex branch with a non-regex comparison
to safely match word boundaries.
In `@apps/pro-web/lib/workspace-debug.ts`:
- Around line 22-27: DEBUG_FLAGS is computed once at module load so toggling
window.__DEBUG_* at runtime has no effect; change it to evaluate dynamically by
replacing the static boolean values with runtime getters or functions (e.g.,
convert DEBUG_FLAGS into an object with getters for
workspace/content/chat/markdown or export a getDebugFlags() function) so each
access reads w?.__DEBUG_WORKSPACE__, w?.__DEBUG_WORKSPACE_CONTENT__,
w?.__DEBUG_WORKSPACE_CHAT__, and w?.__DEBUG_MARKDOWN__ anew; update any call
sites that expect the previous static object if necessary.
In `@apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx`:
- Around line 1-377: This duplicates the pro-web PromptEnhancer — extract
PromptEnhancer (and constants like ENHANCEMENT_OPTIONS) into a shared package
(e.g., packages/shared or packages/ui) and import it here to avoid maintenance
drift; while migrating, fix the technical feedback: change model detection to
boolean checks using RegExp.test() for isO1/isClaude/isGemini instead of
.match(), memoize computed fields by wrapping getStructureFields()/fields in
React.useMemo depending on selectedModel and those boolean flags, and replace
inline arrow callbacks in JSX with stable useCallback handlers (e.g., replace
inline onCheckedChange, onClick for view toggles, onChange for Textarea/Input,
Popover onOpenChange and expand toggle) using named functions like
handleOptionToggle, handleStructuredChange, buildPromptFromStructure to keep
stable references and avoid re-renders.
In `@packages/mb-drizzle/src/drizzle/schema.ts`:
- Around line 566-582: Indexes document_operations_doc_node_scope_created_idx
and document_operations_document_key_created_idx use the wrong operator class
for text columns; update the op calls for table.docNodeId, table.scopeType,
table.scopeId, and table.documentKey from 'timestamptz_ops' to 'text_ops' (leave
the section index as-is since it already uses text_ops); modify the index
definitions referencing those symbols so the text columns use op('text_ops')
consistently.
- Around line 492-524: Indexes in packages/mb-drizzle/src/drizzle/schema.ts use
incorrect operator classes causing Postgres to ignore them; update the .op(...)
calls to match each column type: for
index('assistant_answers_thread_doc_scope_idx') change
table.threadId.op('text_ops') -> 'uuid_ops'; for
index('assistant_answers_thread_doc_scope_turn_idx') change
table.threadId.op('int4_ops') -> 'uuid_ops', table.scopeId.op('int4_ops') ->
'text_ops', table.turnIndex.op('enum_ops') -> 'int4_ops'; for
index('assistant_answers_thread_scope_idx') change table.threadId.op('enum_ops')
-> 'uuid_ops'; for index('assistant_answers_thread_scope_turn_idx') change
table.scopeId.op('enum_ops') -> 'text_ops' and table.turnIndex.op('uuid_ops') ->
'int4_ops'; ensure every .op(...) matches the actual column type (uuid ->
uuid_ops, text -> text_ops, integer -> int4_ops, enum -> enum_ops).
- Around line 906-946: The new message table index definitions (index names
message_doc_node_id_idx, message_thread_doc_created_idx,
message_thread_doc_scope_created_idx, message_thread_scope_created_at_idx,
message_thread_scope_qchain_idx, message_thread_scope_recent_idx) use incorrect
operator classes for several columns; update the .op(...) calls on the
referenced columns (table.docNodeId, table.createdAt, table.threadId,
table.scopeType, table.scopeId) to match their actual column types: use text_ops
for text columns (docNodeId, scopeId), uuid_ops for uuid columns (threadId),
timestamptz_ops for timestamp/timestamptz columns (createdAt), and enum_ops for
enum columns (scopeType); ensure each index's .op(...) entries are corrected
accordingly in the index(...) .using(...) definitions where those columns
appear.
♻️ Duplicate comments (12)
apps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsx (2)
326-329: Templates button has no functionality.The button renders but lacks an
onClickhandler. Adddisabledwith a tooltip or wire up the template system.
332-370: State desync between Raw and Structured modes.Editing in Raw mode doesn't update
structuredData. Switching to Structured shows stale values. Consider clearing structured fields when raw input is edited, or parse raw input when switching modes.apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx (2)
326-329: Templates button has no functionality.Same issue as flagged in past review - button needs
onClickhandler or disabled state.
332-370: State desync between Raw and Structured modes.Same issue as flagged in past review - bidirectional sync needed or mode-switch handling.
apps/pro-web/lib/hooks/use-workspace-chat.tsx (1)
976-982: ThescopedHistory as anycast bypasses type safety.The array is constructed with
role,content,messageId, andcreatedAtproperties, butfollowingQuestionsPromptexpectsArray<MessageDB & { id?: string }>. This was flagged in a previous review. Consider creating a proper mapping function or adjusting the type definitions to align.apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts (1)
155-173: Non-schema errors are swallowed without logging.When the initial insert fails and the error message doesn't match the schema lag conditions, the function returns silently at line 172, losing the original error context. This makes debugging non-schema-related failures difficult. Consider adding a
console.warnfor non-schema errors while maintaining the fail-closed behavior.apps/pro-web/lib/url.ts (1)
486-503: Fallback URL may produce malformed paths with empty segments.When
threadQuestionSlugis missing and fallback kicks in, if other params (likeusernameSlug,category, etc.) are also empty, the URL could contain empty segments (e.g.,/u//t///). Consider routing throughprofilesThreadUrlfor the user case similar to howthreadQuestionUrlnow usesthreadUrl.🐛 Suggested defensive approach
// If missing, fall back to the parent profile thread URL (no error). if (!threadQuestionSlug) { if (type === 'chatbot') { + if (!chatbot || !threadSlug) return '/' return ['', 'b', chatbot?.toLowerCase() ?? '', threadSlug ?? ''].join( '/', ) } - return [ - '', - 'u', - usernameSlug ?? '', - 't', - normalizeCategorySlug(category ?? ''), - normalizeDomainSlug(domain ?? '', raw), - chatbot?.toLowerCase() ?? '', - threadSlug ?? '', - ].join('/') + // Route through profilesThreadUrl for consistent validation + return urlBuilders.profilesThreadUrl({ + type: 'user', + usernameSlug: usernameSlug ?? '', + category: category ?? '', + domain: domain ?? '', + chatbot: chatbot ?? '', + threadSlug: threadSlug ?? '', + raw, + }) }apps/pro-web/components/shared/shared-accordion.tsx (1)
379-379: Potential runtime error: accessingcategories[0]without null check.The expression
thread?.chatbot?.categories[0]?.category?.namewill throw ifcategoriesis an empty array sincecategories[0]returnsundefined, but the preceding?.does not guard array access.🐛 Proposed fix
- const category = thread?.chatbot?.categories[0]?.category?.name + const category = thread?.chatbot?.categories?.[0]?.category?.nameapps/pro-web/components/routes/chat/chat-list/index.tsx (1)
109-114: Double type assertion bypasses type safety.The
as unknown as ThreadMessage[]double-cast pattern masks potential type mismatches. Consider creating a normalization function with runtime validation.🔧 Suggested improvement
function normalizeMessages(raw: unknown[]): ThreadMessage[] { return (raw ?? []).map(m => { const msg = m as Record<string, unknown> return { messageId: (msg.messageId ?? msg.id ?? '') as string, content: (msg.content ?? '') as string, role: (msg.role ?? 'user') as string, createdAt: msg.createdAt as string | undefined, // ... other required fields } as ThreadMessage }) } // Usage: const chatMessages = normalizeMessages(messages || activeThread?.messages || [])apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts (3)
81-84: Inconsistent token estimation with codebase.This
estimateTokensfunction returns0for empty strings, whileapps/pro-web/lib/simple-rag/prompt.tsusesMath.max(1, Math.ceil(text.length / 4))which clamps to minimum 1. This inconsistency could cause division-by-zero or unexpected behavior in token budget calculations.🔧 Suggested fix
function estimateTokens(text: string): number { // deterministic + cheap (matches the guide's intent) - return Math.ceil((text ?? '').length / 4) + return Math.max(1, Math.ceil((text ?? '').length / 4)) }Or import from the shared location to maintain consistency.
129-140: Sanitize regex input to prevent potential ReDoS.The
tryExtractExpectedTypefunction constructs a regex fromvarNamewhich could contain special characters. While the risk is low sincevarNamecomes from internal code, sanitizing the input eliminates the concern flagged by static analysis.🔧 Suggested fix
function tryExtractExpectedType( errMsg: string, varName: string, ): string | null { + // Sanitize varName to prevent ReDoS + const safeVarName = varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') const re = new RegExp( - `\\$${varName}" of type "[^"]+" used in position expecting type "([^"]+)"`, + `\\$${safeVarName}" of type "[^"]+" used in position expecting type "([^"]+)"`, ) const m = errMsg.match(re) return m?.[1] ?? null }
371-378: Remove unsafe double type assertion.The
as unknown as AssistantAnswerMemory[]cast bypasses type safety. The mapped object already has the correct shape, so the cast is unnecessary.🔧 Suggested fix
const normalize = (rows: SearchRow[]): AssistantAnswerMemory[] => (rows ?? []).map((r) => ({ messageId: r.message_id, turnIndex: r.turn_index, content: r.content, cosine: r.cosine, createdAt: r.created_at, - })) as unknown as AssistantAnswerMemory[] + }))
🧹 Nitpick comments (13)
apps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsx (3)
87-90: Model detection flags should be boolean, notRegExpMatchArray | null.Using
.match()returns a match array or null. While this works for truthiness checks, it leads to imprecise types. Convert to explicit booleans for clarity and type safety.♻️ Suggested fix
- const isO1 = selectedModel.match(/(o1|gpt-5)/i) - const isClaude = selectedModel.match(/(claude|anthropic)/i) - const isGemini = selectedModel.match(/(gemini)/i) + const isO1 = /(o1|gpt-5)/i.test(selectedModel) + const isClaude = /(claude|anthropic)/i.test(selectedModel) + const isGemini = /gemini/i.test(selectedModel)
93-190: MemoizegetStructureFieldsto avoid recreation on every render.The function and
fieldsassignment are recreated on each render. Since they only depend on the model detection flags, wrap inuseMemo.♻️ Suggested refactor
- // Determine Structure Fields - const getStructureFields = () => { - if (isO1) { - return [ - // ... fields - ] - } - // ... rest of conditions - } - - const fields = getStructureFields() + // Determine Structure Fields + const fields = React.useMemo(() => { + if (isO1) { + return [ + { key: 'goal', label: 'Goal', type: 'input', placeholder: 'I want...' }, + { key: 'returnFormat', label: 'Return Format', type: 'input', placeholder: 'Structure as...' }, + { key: 'constraints', label: 'Warnings & Constraints', type: 'textarea', placeholder: '- Do NOT...' }, + { key: 'context', label: 'Context Dump', type: 'textarea', placeholder: 'Background info...' }, + ] + } + if (isClaude) { + return [ + { key: 'taskRole', label: 'Task Context / Role Refinement', type: 'input', placeholder: 'Refine global persona (optional)...' }, + { key: 'context', label: 'Context', type: 'textarea', placeholder: 'Background context...' }, + { key: 'instruction', label: 'Instruction', type: 'textarea', placeholder: 'Analyze and perform...' }, + { key: 'outputFormat', label: 'Output Format', type: 'textarea', placeholder: 'Wrap in XML tags...' }, + { key: 'userQuery', label: 'User Query', type: 'textarea', placeholder: 'The actual request...' }, + ] + } + if (isGemini) { + return [ + { key: 'mediaData', label: 'Media & Data Description', type: 'textarea', placeholder: 'Attached file content...' }, + { key: 'systemInstructions', label: 'System Instructions', type: 'textarea', placeholder: 'Role and task...' }, + { key: 'examples', label: 'Few-Shot Examples', type: 'textarea', placeholder: 'Input: ... Output: ...' }, + { key: 'query', label: 'The Query', type: 'textarea', placeholder: 'Based on data above...' }, + ] + } + return [ + { key: 'prompt', label: 'Prompt', type: 'textarea', placeholder: 'Enter your prompt...' }, + ] + }, [isO1, isClaude, isGemini])
268-270: Use stable function references instead of inline arrows in JSX.Per coding guidelines, avoid inline arrow functions in JSX props to prevent unnecessary re-renders. Extract handlers using
useCallbackor define stable function references.♻️ Example for checkbox handler
+ const handleCheckboxChange = React.useCallback( + (optionId: string, suffix: string) => () => { + handleOptionToggle(optionId, suffix) + }, + [handleOptionToggle] + ) + // In JSX: - onCheckedChange={() => - handleOptionToggle(option.id, option.suffix) - } + onCheckedChange={handleCheckboxChange(option.id, option.suffix)}Similar refactoring applies to other inline handlers at lines 285, 303, 315, 337, 352-353, and 361-362.
apps/pro-web/lib/queries/use-save-document.ts (1)
288-288: Unreachable fallback toserverDoc.content.Since
contentis validated as non-empty at line 127 andensureEmbeddedSectionIdsreturns the processed string,canonicalContentwill always be truthy. The|| serverDoc.contentfallback is unreachable.Consider simplifying for clarity:
✨ Suggested simplification
- const rawContent = canonicalContent || serverDoc.content + const rawContent = canonicalContentapps/pro-web/components/routes/pro/chat-panel-pro.tsx (3)
54-55: Consider importingActionTypefrom existing module.
ActionTypeis already defined inapps/pro-web/types/operation-meta.tswith the same union. Reusing the existing type avoids duplication and ensures consistency if the canonical type evolves.♻️ Suggested refactor
+'use client' + +import type { ActionType } from '@/types/operation-meta' // ... other imports -type ActionType = 'ask' | 'rewrite' | 'expand' | 'edit' type ScopeType = 'chat_thread' | 'doc_root' | 'heading_thread'
298-306: Unused metadata object_opMeta.This object is computed but never used. The comment indicates it's prepared for future plumbing, but until wired up it adds cognitive overhead. Consider removing until needed or adding a TODO comment with a tracking reference.
347-362: IIFE pattern for conditional scope computation.The immediately-invoked function expression works but is less common in React codebases. This could be simplified to a variable assignment with a conditional for improved readability.
♻️ Optional simplification
- const scope = - scopeType !== 'chat_thread' - ? (() => { - const fullMarkdown = currentDocumentData?.content ?? '' - const sections = - currentDocumentData?.sections ?? - (fullMarkdown ? parseMarkdownSections(fullMarkdown) : undefined) - - return { - scopeType, - scopeId, - docNodeId, - fullMarkdown, - sections, - } - })() - : undefined + let scope: { scopeType: ScopeType; scopeId: string | null; docNodeId: string | null; fullMarkdown: string; sections: ReturnType<typeof parseMarkdownSections> | undefined } | undefined + + if (scopeType !== 'chat_thread') { + const fullMarkdown = currentDocumentData?.content ?? '' + const sections = + currentDocumentData?.sections ?? + (fullMarkdown ? parseMarkdownSections(fullMarkdown) : undefined) + + scope = { + scopeType, + scopeId, + docNodeId, + fullMarkdown, + sections, + } + }packages/mb-drizzle/src/drizzle/schema.ts (1)
32-49: Consider consolidating duplicate enum values, but verify database intent first.
assistantAnswerScopeType(line 32) andscopeTypeEnum(line 45) define identical values:'chat_thread','doc_root','heading_thread'. However, they map to separate PostgreSQL enum types (assistant_answer_scope_typevsscope_type_enum) and are used in different tables—the former inassistantAnswerstable (line 477), the latter inmessagetable (line 888). If these represent the same domain concept and can evolve together, consolidating to a single enum would reduce maintenance overhead. If they are intentionally separate to maintain semantic clarity between "assistant answer scope" and "message scope," the current separation is justified.apps/pro-web/services/hasura/hasura.service.ts (1)
683-690: Avoidas anytype assertions; use proper typing.The function casts
paramstoanywhich loses type safety. Consider using a properly typed destructuring or a type guard instead.♻️ Suggested typing improvement
export async function saveNewMessage( params: Partial<SaveNewMessageParams> & { doc_node_id?: string | null }, ) { - const { jwt, docNodeId, doc_node_id, ...object } = params as any + const { jwt, docNodeId, doc_node_id, ...object } = params const client = getHasuraClient({ jwt }) // During migration, callers may pass either camelCase or snake_case. - const canonicalDocNodeId: string | null = (docNodeId ?? - doc_node_id ?? - null) as any + const canonicalDocNodeId: string | null | undefined = docNodeId ?? doc_node_id ?? nullapps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts (1)
522-555: Consider adding request timeout.The
hasuraGqlfunction makes external HTTP requests without a timeout. In production, this could lead to hanging requests if Hasura becomes unresponsive.🔧 Suggested improvement
+const HASURA_TIMEOUT_MS = 30000 + async function hasuraGql<TData>( query: string, variables?: Record<string, unknown>, ): Promise<TData> { const endpoint = process.env.HASURA_GRAPHQL_ENDPOINT || process.env.NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT if (!endpoint) throw new Error('Missing HASURA_GRAPHQL_ENDPOINT') const adminSecret = process.env.HASURA_GRAPHQL_ADMIN_SECRET || process.env.HASURA_ADMIN_SECRET if (!adminSecret) throw new Error('Missing HASURA_GRAPHQL_ADMIN_SECRET') + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), HASURA_TIMEOUT_MS) + - const res = await fetch(endpoint, { + const res = await fetch(endpoint, { method: 'POST', headers: { 'content-type': 'application/json', 'x-hasura-admin-secret': adminSecret, }, body: JSON.stringify({ query, variables }), cache: 'no-store', + signal: controller.signal, - }) + }).finally(() => clearTimeout(timeoutId))apps/pro-web/lib/hooks/use-mb-chat.tsx (2)
433-446: Remove debug console.log in production code.The
normalizeToolCallfunction contains aconsole.logstatement that should be removed or gated behind a dev mode flag for production cleanliness.🔧 Suggested fix
function normalizeToolCall(toolCall: ToolCall<string, unknown>): AiToolCall { - console.log('normalizeToolCall object —> ', { toolCall }) + if (appConfig.features.devMode) { + console.log('normalizeToolCall object —> ', { toolCall }) + } const id = toolCall.toolCallId ?? ''
1110-1144: Duplicated mb_op_meta construction.The
enrichedChatOptionsconstruction duplicates themb_op_metaobject structure in both branches. Consider extracting this to a shared object.🔧 Suggested refactor
+ const mbOpMeta = { + scope_type: scopeTypeForMeta, + scope_id: scopeIdForMeta, + doc_node_id: docNodeIdForMeta, + scope_title_snapshot: scopeTitleSnapshotForMeta, + doc_context_included: docContextIncluded, + doc_context_policy: docContextIncluded ? DOC_CONTEXT_POLICY : null, + heading_content_version: headingContentVersion ?? null, + } + const enrichedChatOptions: ChatRequestOptions | undefined = chatRequestOptions ? ({ ...chatRequestOptions, body: { // biome-ignore lint/suspicious/noExplicitAny: passthrough metadata ...((chatRequestOptions as any).body || {}), - mb_op_meta: { - scope_type: scopeTypeForMeta, - scope_id: scopeIdForMeta, - doc_node_id: docNodeIdForMeta, - scope_title_snapshot: scopeTitleSnapshotForMeta, - doc_context_included: docContextIncluded, - doc_context_policy: docContextIncluded - ? DOC_CONTEXT_POLICY - : null, - heading_content_version: headingContentVersion ?? null, - }, + mb_op_meta: mbOpMeta, }, } as ChatRequestOptions) : ({ body: { - mb_op_meta: { - scope_type: scopeTypeForMeta, - scope_id: scopeIdForMeta, - doc_node_id: docNodeIdForMeta, - scope_title_snapshot: scopeTitleSnapshotForMeta, - doc_context_included: docContextIncluded, - doc_context_policy: docContextIncluded - ? DOC_CONTEXT_POLICY - : null, - heading_content_version: headingContentVersion ?? null, - }, + mb_op_meta: mbOpMeta, }, } as ChatRequestOptions)apps/pro-web/components/routes/thread/thread-popup.tsx (1)
344-348: Unused parameters should be removed or documented.The
ThreadPopUpCardHeaderfunction has parameters marked as intentionally unused with underscore prefix. If these are kept for API compatibility, consider adding a@deprecatedJSDoc comment or removing them if no external callers exist./** * `@deprecated` messages, isBrowseView, isProView params are no longer used * but kept for API compatibility. Remove in next major version. */
| const wordMatches = domainSlugs.filter((slug) => { | ||
| const regex = new RegExp(`\\b${domainSlug}\\b`) | ||
| return regex.test(slug) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential ReDoS vulnerability with user-controlled regex input.
The domainSlug variable is derived from user input and used directly in a RegExp constructor. If domainSlug contains special regex characters or malicious patterns, this could lead to Regular Expression Denial of Service (ReDoS).
🔒 Suggested fix: escape regex special characters
+// Escape special regex characters to prevent ReDoS
+const escapeRegex = (str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const wordMatches = domainSlugs.filter((slug) => {
- const regex = new RegExp(`\\b${domainSlug}\\b`)
+ const regex = new RegExp(`\\b${escapeRegex(domainSlug)}\\b`)
return regex.test(slug)
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const wordMatches = domainSlugs.filter((slug) => { | |
| const regex = new RegExp(`\\b${domainSlug}\\b`) | |
| return regex.test(slug) | |
| }) | |
| // Escape special regex characters to prevent ReDoS | |
| const escapeRegex = (str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') | |
| const wordMatches = domainSlugs.filter((slug) => { | |
| const regex = new RegExp(`\\b${escapeRegex(domainSlug)}\\b`) | |
| return regex.test(slug) | |
| }) |
🧰 Tools
🪛 ast-grep (0.40.5)
[warning] 86-86: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\b${domainSlug}\\b)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🤖 Prompt for AI Agents
In `@apps/pro-web/lib/url.ts` around lines 86 - 89, The filter building
wordMatches uses user-controlled domainSlug directly in new RegExp which can
cause ReDoS; escape regex metacharacters in domainSlug before constructing regex
(or avoid RegExp entirely by using a safe string comparison like slug ===
domainSlug or slug.split/startsWith depending on intent). Update the code around
domainSlugs.filter and the regex creation to call a safe escape helper (e.g.,
escapeRegex(domainSlug)) or replace the regex branch with a non-regex comparison
to safely match word boundaries.
| export const DEBUG_FLAGS = { | ||
| workspace: | ||
| (typeof window !== 'undefined' && | ||
| Boolean( | ||
| (window as typeof window & { __DEBUG_WORKSPACE__?: boolean }) | ||
| .__DEBUG_WORKSPACE__, | ||
| )) || | ||
| true, | ||
| content: | ||
| (typeof window !== 'undefined' && | ||
| Boolean( | ||
| (window as typeof window & { __DEBUG_WORKSPACE_CONTENT__?: boolean }) | ||
| .__DEBUG_WORKSPACE_CONTENT__, | ||
| )) || | ||
| true, | ||
| chat: | ||
| (typeof window !== 'undefined' && | ||
| Boolean( | ||
| (window as typeof window & { __DEBUG_WORKSPACE_CHAT__?: boolean }) | ||
| .__DEBUG_WORKSPACE_CHAT__, | ||
| )) || | ||
| true, | ||
| markdown: | ||
| (typeof window !== 'undefined' && | ||
| Boolean( | ||
| (window as typeof window & { __DEBUG_MARKDOWN__?: boolean }) | ||
| .__DEBUG_MARKDOWN__, | ||
| )) || | ||
| true, | ||
| workspace: Boolean(w?.__DEBUG_WORKSPACE__), | ||
| content: Boolean(w?.__DEBUG_WORKSPACE_CONTENT__), | ||
| chat: Boolean(w?.__DEBUG_WORKSPACE_CHAT__), | ||
| markdown: Boolean(w?.__DEBUG_MARKDOWN__), | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug flags are evaluated once at module load, not dynamically.
The documented workflow suggests setting flags in devtools at runtime, but DEBUG_FLAGS values are computed when the module loads. Setting window.__DEBUG_WORKSPACE__ = true after page load won't enable debugging—users must reload.
Consider using getters for dynamic evaluation:
🔧 Proposed fix using getters for runtime evaluation
export const DEBUG_FLAGS = {
- workspace: Boolean(w?.__DEBUG_WORKSPACE__),
- content: Boolean(w?.__DEBUG_WORKSPACE_CONTENT__),
- chat: Boolean(w?.__DEBUG_WORKSPACE_CHAT__),
- markdown: Boolean(w?.__DEBUG_MARKDOWN__),
+ get workspace() {
+ return typeof window !== 'undefined' && Boolean((window as DebugWindow).__DEBUG_WORKSPACE__)
+ },
+ get content() {
+ return typeof window !== 'undefined' && Boolean((window as DebugWindow).__DEBUG_WORKSPACE_CONTENT__)
+ },
+ get chat() {
+ return typeof window !== 'undefined' && Boolean((window as DebugWindow).__DEBUG_WORKSPACE_CHAT__)
+ },
+ get markdown() {
+ return typeof window !== 'undefined' && Boolean((window as DebugWindow).__DEBUG_MARKDOWN__)
+ },
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const DEBUG_FLAGS = { | |
| workspace: | |
| (typeof window !== 'undefined' && | |
| Boolean( | |
| (window as typeof window & { __DEBUG_WORKSPACE__?: boolean }) | |
| .__DEBUG_WORKSPACE__, | |
| )) || | |
| true, | |
| content: | |
| (typeof window !== 'undefined' && | |
| Boolean( | |
| (window as typeof window & { __DEBUG_WORKSPACE_CONTENT__?: boolean }) | |
| .__DEBUG_WORKSPACE_CONTENT__, | |
| )) || | |
| true, | |
| chat: | |
| (typeof window !== 'undefined' && | |
| Boolean( | |
| (window as typeof window & { __DEBUG_WORKSPACE_CHAT__?: boolean }) | |
| .__DEBUG_WORKSPACE_CHAT__, | |
| )) || | |
| true, | |
| markdown: | |
| (typeof window !== 'undefined' && | |
| Boolean( | |
| (window as typeof window & { __DEBUG_MARKDOWN__?: boolean }) | |
| .__DEBUG_MARKDOWN__, | |
| )) || | |
| true, | |
| workspace: Boolean(w?.__DEBUG_WORKSPACE__), | |
| content: Boolean(w?.__DEBUG_WORKSPACE_CONTENT__), | |
| chat: Boolean(w?.__DEBUG_WORKSPACE_CHAT__), | |
| markdown: Boolean(w?.__DEBUG_MARKDOWN__), | |
| } | |
| export const DEBUG_FLAGS = { | |
| get workspace() { | |
| return typeof window !== 'undefined' && Boolean((window as DebugWindow).__DEBUG_WORKSPACE__) | |
| }, | |
| get content() { | |
| return typeof window !== 'undefined' && Boolean((window as DebugWindow).__DEBUG_WORKSPACE_CONTENT__) | |
| }, | |
| get chat() { | |
| return typeof window !== 'undefined' && Boolean((window as DebugWindow).__DEBUG_WORKSPACE_CHAT__) | |
| }, | |
| get markdown() { | |
| return typeof window !== 'undefined' && Boolean((window as DebugWindow).__DEBUG_MARKDOWN__) | |
| }, | |
| } |
🤖 Prompt for AI Agents
In `@apps/pro-web/lib/workspace-debug.ts` around lines 22 - 27, DEBUG_FLAGS is
computed once at module load so toggling window.__DEBUG_* at runtime has no
effect; change it to evaluate dynamically by replacing the static boolean values
with runtime getters or functions (e.g., convert DEBUG_FLAGS into an object with
getters for workspace/content/chat/markdown or export a getDebugFlags()
function) so each access reads w?.__DEBUG_WORKSPACE__,
w?.__DEBUG_WORKSPACE_CONTENT__, w?.__DEBUG_WORKSPACE_CHAT__, and
w?.__DEBUG_MARKDOWN__ anew; update any call sites that expect the previous
static object if necessary.
| 'use client' | ||
|
|
||
| import { Button } from '@/components/ui/button' | ||
| import { Checkbox } from '@/components/ui/checkbox' | ||
| import { Input } from '@/components/ui/input' | ||
| import { Label } from '@/components/ui/label' | ||
| import { | ||
| Popover, | ||
| PopoverContent, | ||
| PopoverTrigger, | ||
| } from '@/components/ui/popover' | ||
| import { Textarea } from '@/components/ui/textarea' | ||
| import { cn } from '@/lib/utils' | ||
| import { ChevronDown, ChevronUp, LayoutTemplate } from 'lucide-react' | ||
| import * as React from 'react' | ||
|
|
||
| interface PromptEnhancerProps { | ||
| setInput: (value: string) => void | ||
| input: string | ||
| className?: string | ||
| selectedModel?: string | ||
| } | ||
|
|
||
| const ENHANCEMENT_OPTIONS = [ | ||
| { | ||
| id: 'complete-specific', | ||
| label: 'Clarify intent', | ||
| suffix: ' Please provide a detailed and specific answer.', | ||
| }, | ||
| { | ||
| id: 'review-refine', | ||
| label: 'Verify and improve', | ||
| suffix: ' Please review your answer for accuracy and refine it.', | ||
| }, | ||
| { | ||
| id: 'creative-angle', | ||
| label: 'Unique insight', | ||
| suffix: ' Please answer with a creative perspective.', | ||
| }, | ||
| ] | ||
|
|
||
| type ViewMode = 'raw' | 'structured' | ||
|
|
||
| export function PromptEnhancer({ | ||
| setInput, | ||
| input, | ||
| className, | ||
| selectedModel = '', | ||
| }: PromptEnhancerProps) { | ||
| const [open, setOpen] = React.useState(false) | ||
| const [activeOptions, setActiveOptions] = React.useState<string[]>([]) | ||
| const [isExpanded, setIsExpanded] = React.useState(true) | ||
| const [viewMode, setViewMode] = React.useState<ViewMode>('raw') | ||
|
|
||
| // Structured Data State | ||
| const [structuredData, setStructuredData] = React.useState< | ||
| Record<string, string> | ||
| >({}) | ||
|
|
||
| // Reset active options when input is cleared | ||
| React.useEffect(() => { | ||
| if (!input) { | ||
| setActiveOptions([]) | ||
| setStructuredData({}) | ||
| } | ||
| }, [input]) | ||
|
|
||
| const handleOptionToggle = (optionId: string, suffix: string) => { | ||
| const isActive = activeOptions.includes(optionId) | ||
| let newOptions: string[] | ||
| let newInput = input | ||
|
|
||
| if (isActive) { | ||
| newOptions = activeOptions.filter((id) => id !== optionId) | ||
| newInput = newInput.replace(suffix, '') | ||
| } else { | ||
| newOptions = [...activeOptions, optionId] | ||
| if (!newInput.includes(suffix)) { | ||
| newInput = `${newInput}${suffix}` | ||
| } | ||
| } | ||
|
|
||
| setActiveOptions(newOptions) | ||
| setInput(newInput) | ||
| } | ||
|
|
||
| // Model Detection | ||
| const isO1 = selectedModel.match(/(o1|gpt-5)/i) | ||
| const isClaude = selectedModel.match(/(claude|anthropic)/i) | ||
| const isGemini = selectedModel.match(/(gemini)/i) | ||
|
|
||
| // Determine Structure Fields | ||
| const getStructureFields = () => { | ||
| if (isO1) { | ||
| return [ | ||
| { key: 'goal', label: 'Goal', type: 'input', placeholder: 'I want...' }, | ||
| { | ||
| key: 'returnFormat', | ||
| label: 'Return Format', | ||
| type: 'input', | ||
| placeholder: 'Structure as...', | ||
| }, | ||
| { | ||
| key: 'constraints', | ||
| label: 'Warnings & Constraints', | ||
| type: 'textarea', | ||
| placeholder: '- Do NOT...', | ||
| }, | ||
| { | ||
| key: 'context', | ||
| label: 'Context Dump', | ||
| type: 'textarea', | ||
| placeholder: 'Background info...', | ||
| }, | ||
| ] | ||
| } | ||
| if (isClaude) { | ||
| return [ | ||
| { | ||
| key: 'taskRole', | ||
| label: 'Task Context / Role Refinement', | ||
| type: 'input', | ||
| placeholder: 'Refine global persona (optional)...', | ||
| }, | ||
| { | ||
| key: 'context', | ||
| label: 'Context', | ||
| type: 'textarea', | ||
| placeholder: 'Background context...', | ||
| }, | ||
| { | ||
| key: 'instruction', | ||
| label: 'Instruction', | ||
| type: 'textarea', | ||
| placeholder: 'Analyze and perform...', | ||
| }, | ||
| { | ||
| key: 'outputFormat', | ||
| label: 'Output Format', | ||
| type: 'textarea', | ||
| placeholder: 'Wrap in XML tags...', | ||
| }, | ||
| { | ||
| key: 'userQuery', | ||
| label: 'User Query', | ||
| type: 'textarea', | ||
| placeholder: 'The actual request...', | ||
| }, | ||
| ] | ||
| } | ||
| if (isGemini) { | ||
| return [ | ||
| { | ||
| key: 'mediaData', | ||
| label: 'Media & Data Description', | ||
| type: 'textarea', | ||
| placeholder: 'Attached file content...', | ||
| }, | ||
| { | ||
| key: 'systemInstructions', | ||
| label: 'System Instructions', | ||
| type: 'textarea', | ||
| placeholder: 'Role and task...', | ||
| }, | ||
| { | ||
| key: 'examples', | ||
| label: 'Few-Shot Examples', | ||
| type: 'textarea', | ||
| placeholder: 'Input: ... Output: ...', | ||
| }, | ||
| { | ||
| key: 'query', | ||
| label: 'The Query', | ||
| type: 'textarea', | ||
| placeholder: 'Based on data above...', | ||
| }, | ||
| ] | ||
| } | ||
| // Default structure | ||
| return [ | ||
| { | ||
| key: 'prompt', | ||
| label: 'Prompt', | ||
| type: 'textarea', | ||
| placeholder: 'Enter your prompt...', | ||
| }, | ||
| ] | ||
| } | ||
|
|
||
| const fields = getStructureFields() | ||
|
|
||
| const handleStructuredChange = (key: string, value: string) => { | ||
| const newData = { ...structuredData, [key]: value } | ||
| setStructuredData(newData) | ||
| buildPromptFromStructure(newData) | ||
| } | ||
|
|
||
| const buildPromptFromStructure = (data: Record<string, string>) => { | ||
| let assembledProfile = '' | ||
|
|
||
| if (isO1) { | ||
| if (data.goal) | ||
| assembledProfile += `<!-- SECTION 1: GOAL -->\n${data.goal}\n` | ||
| if (data.returnFormat) | ||
| assembledProfile += `<!-- SECTION 2: RETURN FORMAT -->\n${data.returnFormat}\n` | ||
| if (data.constraints) | ||
| assembledProfile += `<!-- SECTION 3: WARNINGS & CONSTRAINTS -->\n${data.constraints}\n` | ||
| if (data.context) | ||
| assembledProfile += `<!-- SECTION 4: CONTEXT DUMP -->\n${data.context}\n` | ||
| } else if (isClaude) { | ||
| if (data.taskRole) | ||
| assembledProfile += `<system_role>${data.taskRole}</system_role>\n` | ||
| if (data.context) | ||
| assembledProfile += `<context>\n${data.context}\n</context>\n` | ||
| if (data.instruction) | ||
| assembledProfile += `<instruction>\n${data.instruction}\n</instruction>\n` | ||
| if (data.outputFormat) | ||
| assembledProfile += `<output_format>\n${data.outputFormat}\n</output_format>\n` | ||
| if (data.userQuery) | ||
| assembledProfile += `<user_query>\n${data.userQuery}\n</user_query>\n` | ||
| } else if (isGemini) { | ||
| if (data.mediaData) | ||
| assembledProfile += `<!-- PART 1: MEDIA & DATA -->\n${data.mediaData}\n` | ||
| if (data.systemInstructions) | ||
| assembledProfile += `<!-- PART 2: SYSTEM INSTRUCTIONS -->\n${data.systemInstructions}\n` | ||
| if (data.examples) | ||
| assembledProfile += `<!-- PART 3: FEW-SHOT EXAMPLES -->\n${data.examples}\n` | ||
| if (data.query) | ||
| assembledProfile += `<!-- PART 4: THE QUERY -->\n${data.query}\n` | ||
| } else { | ||
| assembledProfile = data.prompt || '' | ||
| } | ||
|
|
||
| // Re-append active modifiers | ||
| const modifiers = activeOptions | ||
| .map((optId) => ENHANCEMENT_OPTIONS.find((o) => o.id === optId)?.suffix) | ||
| .filter(Boolean) | ||
| .join('') | ||
|
|
||
| setInput((assembledProfile.trim() + modifiers).trim()) | ||
| } | ||
|
|
||
| return ( | ||
| <Popover open={open} onOpenChange={setOpen}> | ||
| <PopoverTrigger asChild> | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| className={cn('text-xs text-muted-foreground', className)} | ||
| > | ||
| Prompt Enhancer | ||
| </Button> | ||
| </PopoverTrigger> | ||
| <PopoverContent | ||
| align="end" | ||
| className={cn('p-0', 'w-[80vw] max-w-[800px]')} | ||
| > | ||
| <div className="flex flex-col"> | ||
| {/* Header Section with Checkboxes */} | ||
| <div className="flex items-center justify-between p-3 border-b bg-muted/30"> | ||
| <div className="flex items-center gap-3"> | ||
| <span className="text-sm font-semibold">Enhance:</span> | ||
| {ENHANCEMENT_OPTIONS.map((option) => ( | ||
| <div key={option.id} className="flex items-center space-x-2"> | ||
| <Checkbox | ||
| id={`header-${option.id}`} | ||
| checked={activeOptions.includes(option.id)} | ||
| onCheckedChange={() => | ||
| handleOptionToggle(option.id, option.suffix) | ||
| } | ||
| /> | ||
| <label | ||
| htmlFor={`header-${option.id}`} | ||
| className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer" | ||
| > | ||
| {option.label} | ||
| </label> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| className="h-8 w-8 p-0" | ||
| onClick={() => setIsExpanded(!isExpanded)} | ||
| > | ||
| {isExpanded ? ( | ||
| <ChevronDown className="size-4" /> | ||
| ) : ( | ||
| <ChevronUp className="size-4" /> | ||
| )} | ||
| </Button> | ||
| </div> | ||
|
|
||
| {/* Collapsible Content */} | ||
| {isExpanded && ( | ||
| <div className="p-4 space-y-4 bg-background animate-in slide-in-from-top-2 duration-200"> | ||
| {/* Controls Row */} | ||
| <div className="flex items-center justify-between"> | ||
| <div className="flex items-center bg-muted rounded-md p-1"> | ||
| <button | ||
| type="button" | ||
| onClick={() => setViewMode('raw')} | ||
| className={cn( | ||
| 'px-3 py-1 text-sm rounded-sm transition-all', | ||
| viewMode === 'raw' | ||
| ? 'bg-background shadow-sm text-foreground' | ||
| : 'text-muted-foreground hover:text-foreground', | ||
| )} | ||
| > | ||
| Raw | ||
| </button> | ||
| <button | ||
| type="button" | ||
| onClick={() => setViewMode('structured')} | ||
| className={cn( | ||
| 'px-3 py-1 text-sm rounded-sm transition-all', | ||
| viewMode === 'structured' | ||
| ? 'bg-background shadow-sm text-foreground' | ||
| : 'text-muted-foreground hover:text-foreground', | ||
| )} | ||
| > | ||
| Structured | ||
| </button> | ||
| </div> | ||
| <Button variant="outline" size="sm" className="gap-2"> | ||
| <LayoutTemplate className="size-4" /> | ||
| Templates | ||
| </Button> | ||
| </div> | ||
|
|
||
| {/* Main Input Area */} | ||
| <div className="min-h-[200px] max-h-[60vh] overflow-y-auto pr-1"> | ||
| {viewMode === 'raw' ? ( | ||
| <Textarea | ||
| value={input} | ||
| onChange={(e) => setInput(e.target.value)} | ||
| className="w-full h-[300px] resize-none font-mono text-sm" | ||
| placeholder="Enter your prompt here..." | ||
| /> | ||
| ) : ( | ||
| <div className="space-y-4"> | ||
| {fields.map((field) => ( | ||
| <div key={field.key} className="space-y-1.5"> | ||
| <Label className="text-xs font-semibold text-muted-foreground uppercase tracking-wide"> | ||
| {field.label} | ||
| </Label> | ||
| {field.type === 'textarea' ? ( | ||
| <Textarea | ||
| placeholder={field.placeholder} | ||
| value={structuredData[field.key] || ''} | ||
| onChange={(e) => | ||
| handleStructuredChange(field.key, e.target.value) | ||
| } | ||
| className="resize-y min-h-[80px]" | ||
| /> | ||
| ) : ( | ||
| <Input | ||
| placeholder={field.placeholder} | ||
| value={structuredData[field.key] || ''} | ||
| onChange={(e) => | ||
| handleStructuredChange(field.key, e.target.value) | ||
| } | ||
| /> | ||
| )} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </PopoverContent> | ||
| </Popover> | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Significant code duplication with pro-web variant.
This component is nearly identical to apps/pro-web/components/routes/chat/prompt-form/prompt-enhancer.tsx. Consider extracting to a shared package (e.g., packages/shared or packages/ui) to maintain consistency and reduce maintenance burden.
All technical issues noted in the pro-web review apply here as well:
- Model detection should use
.test()for boolean types (lines 88-90) - Memoize
fieldswithuseMemo(lines 93-190) - Use stable function references instead of inline arrows (lines 268-270, 285, 303, 315, 337, 352-353, 361-362)
🤖 Prompt for AI Agents
In `@apps/web/components/routes/chat/prompt-form/prompt-enhancer.tsx` around lines
1 - 377, This duplicates the pro-web PromptEnhancer — extract PromptEnhancer
(and constants like ENHANCEMENT_OPTIONS) into a shared package (e.g.,
packages/shared or packages/ui) and import it here to avoid maintenance drift;
while migrating, fix the technical feedback: change model detection to boolean
checks using RegExp.test() for isO1/isClaude/isGemini instead of .match(),
memoize computed fields by wrapping getStructureFields()/fields in React.useMemo
depending on selectedModel and those boolean flags, and replace inline arrow
callbacks in JSX with stable useCallback handlers (e.g., replace inline
onCheckedChange, onClick for view toggles, onChange for Textarea/Input, Popover
onOpenChange and expand toggle) using named functions like handleOptionToggle,
handleStructuredChange, buildPromptFromStructure to keep stable references and
avoid re-renders.
| index('assistant_answers_thread_doc_scope_idx').using( | ||
| 'btree', | ||
| table.threadId.asc().nullsLast().op('text_ops'), | ||
| table.docNodeId.asc().nullsLast().op('text_ops'), | ||
| table.scopeType.asc().nullsLast().op('enum_ops'), | ||
| table.scopeId.asc().nullsLast().op('text_ops'), | ||
| ), | ||
| index('assistant_answers_thread_doc_scope_turn_idx').using( | ||
| 'btree', | ||
| table.threadId.asc().nullsLast().op('int4_ops'), | ||
| table.docNodeId.asc().nullsLast().op('text_ops'), | ||
| table.scopeType.asc().nullsLast().op('text_ops'), | ||
| table.scopeId.asc().nullsLast().op('int4_ops'), | ||
| table.turnIndex.asc().nullsLast().op('enum_ops'), | ||
| ), | ||
| index('assistant_answers_thread_scope_idx').using( | ||
| 'btree', | ||
| table.threadId.asc().nullsLast().op('uuid_ops'), | ||
| table.scopeType.asc().nullsLast().op('enum_ops'), | ||
| table.scopeId.asc().nullsLast().op('text_ops'), | ||
| ), | ||
| index('assistant_answers_thread_scope_turn_idx').using( | ||
| 'btree', | ||
| table.threadId.asc().nullsLast().op('enum_ops'), | ||
| table.scopeType.asc().nullsLast().op('text_ops'), | ||
| table.scopeId.asc().nullsLast().op('enum_ops'), | ||
| table.turnIndex.desc().nullsFirst().op('uuid_ops'), | ||
| ), | ||
| index('assistant_answers_thread_turn_idx').using( | ||
| 'btree', | ||
| table.threadId.asc().nullsLast().op('uuid_ops'), | ||
| table.turnIndex.desc().nullsFirst().op('int4_ops'), | ||
| ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect operator classes in index definitions may prevent index usage.
Several indexes have mismatched operator classes for their column types:
| Line | Column | Type | Current Op | Expected Op |
|---|---|---|---|---|
| 494 | threadId | uuid | text_ops |
uuid_ops |
| 501 | threadId | uuid | int4_ops |
uuid_ops |
| 504 | scopeId | text | int4_ops |
text_ops |
| 505 | turnIndex | integer | enum_ops |
int4_ops |
| 515 | threadId | uuid | enum_ops |
uuid_ops |
| 517 | scopeId | text | enum_ops |
text_ops |
| 518 | turnIndex | integer | uuid_ops |
int4_ops |
PostgreSQL may ignore these indexes during query planning if the operator class doesn't match the column type.
🔧 Proposed fix for operator classes
index('assistant_answers_thread_doc_scope_idx').using(
'btree',
- table.threadId.asc().nullsLast().op('text_ops'),
+ table.threadId.asc().nullsLast().op('uuid_ops'),
table.docNodeId.asc().nullsLast().op('text_ops'),
table.scopeType.asc().nullsLast().op('enum_ops'),
table.scopeId.asc().nullsLast().op('text_ops'),
),
index('assistant_answers_thread_doc_scope_turn_idx').using(
'btree',
- table.threadId.asc().nullsLast().op('int4_ops'),
+ table.threadId.asc().nullsLast().op('uuid_ops'),
table.docNodeId.asc().nullsLast().op('text_ops'),
- table.scopeType.asc().nullsLast().op('text_ops'),
- table.scopeId.asc().nullsLast().op('int4_ops'),
- table.turnIndex.asc().nullsLast().op('enum_ops'),
+ table.scopeType.asc().nullsLast().op('enum_ops'),
+ table.scopeId.asc().nullsLast().op('text_ops'),
+ table.turnIndex.asc().nullsLast().op('int4_ops'),
),
index('assistant_answers_thread_scope_idx').using(
'btree',
table.threadId.asc().nullsLast().op('uuid_ops'),
table.scopeType.asc().nullsLast().op('enum_ops'),
table.scopeId.asc().nullsLast().op('text_ops'),
),
index('assistant_answers_thread_scope_turn_idx').using(
'btree',
- table.threadId.asc().nullsLast().op('enum_ops'),
- table.scopeType.asc().nullsLast().op('text_ops'),
- table.scopeId.asc().nullsLast().op('enum_ops'),
- table.turnIndex.desc().nullsFirst().op('uuid_ops'),
+ table.threadId.asc().nullsLast().op('uuid_ops'),
+ table.scopeType.asc().nullsLast().op('enum_ops'),
+ table.scopeId.asc().nullsLast().op('text_ops'),
+ table.turnIndex.desc().nullsFirst().op('int4_ops'),
),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| index('assistant_answers_thread_doc_scope_idx').using( | |
| 'btree', | |
| table.threadId.asc().nullsLast().op('text_ops'), | |
| table.docNodeId.asc().nullsLast().op('text_ops'), | |
| table.scopeType.asc().nullsLast().op('enum_ops'), | |
| table.scopeId.asc().nullsLast().op('text_ops'), | |
| ), | |
| index('assistant_answers_thread_doc_scope_turn_idx').using( | |
| 'btree', | |
| table.threadId.asc().nullsLast().op('int4_ops'), | |
| table.docNodeId.asc().nullsLast().op('text_ops'), | |
| table.scopeType.asc().nullsLast().op('text_ops'), | |
| table.scopeId.asc().nullsLast().op('int4_ops'), | |
| table.turnIndex.asc().nullsLast().op('enum_ops'), | |
| ), | |
| index('assistant_answers_thread_scope_idx').using( | |
| 'btree', | |
| table.threadId.asc().nullsLast().op('uuid_ops'), | |
| table.scopeType.asc().nullsLast().op('enum_ops'), | |
| table.scopeId.asc().nullsLast().op('text_ops'), | |
| ), | |
| index('assistant_answers_thread_scope_turn_idx').using( | |
| 'btree', | |
| table.threadId.asc().nullsLast().op('enum_ops'), | |
| table.scopeType.asc().nullsLast().op('text_ops'), | |
| table.scopeId.asc().nullsLast().op('enum_ops'), | |
| table.turnIndex.desc().nullsFirst().op('uuid_ops'), | |
| ), | |
| index('assistant_answers_thread_turn_idx').using( | |
| 'btree', | |
| table.threadId.asc().nullsLast().op('uuid_ops'), | |
| table.turnIndex.desc().nullsFirst().op('int4_ops'), | |
| ), | |
| index('assistant_answers_thread_doc_scope_idx').using( | |
| 'btree', | |
| table.threadId.asc().nullsLast().op('uuid_ops'), | |
| table.docNodeId.asc().nullsLast().op('text_ops'), | |
| table.scopeType.asc().nullsLast().op('enum_ops'), | |
| table.scopeId.asc().nullsLast().op('text_ops'), | |
| ), | |
| index('assistant_answers_thread_doc_scope_turn_idx').using( | |
| 'btree', | |
| table.threadId.asc().nullsLast().op('uuid_ops'), | |
| table.docNodeId.asc().nullsLast().op('text_ops'), | |
| table.scopeType.asc().nullsLast().op('enum_ops'), | |
| table.scopeId.asc().nullsLast().op('text_ops'), | |
| table.turnIndex.asc().nullsLast().op('int4_ops'), | |
| ), | |
| index('assistant_answers_thread_scope_idx').using( | |
| 'btree', | |
| table.threadId.asc().nullsLast().op('uuid_ops'), | |
| table.scopeType.asc().nullsLast().op('enum_ops'), | |
| table.scopeId.asc().nullsLast().op('text_ops'), | |
| ), | |
| index('assistant_answers_thread_scope_turn_idx').using( | |
| 'btree', | |
| table.threadId.asc().nullsLast().op('uuid_ops'), | |
| table.scopeType.asc().nullsLast().op('enum_ops'), | |
| table.scopeId.asc().nullsLast().op('text_ops'), | |
| table.turnIndex.desc().nullsFirst().op('int4_ops'), | |
| ), | |
| index('assistant_answers_thread_turn_idx').using( | |
| 'btree', | |
| table.threadId.asc().nullsLast().op('uuid_ops'), | |
| table.turnIndex.desc().nullsFirst().op('int4_ops'), | |
| ), |
🤖 Prompt for AI Agents
In `@packages/mb-drizzle/src/drizzle/schema.ts` around lines 492 - 524, Indexes in
packages/mb-drizzle/src/drizzle/schema.ts use incorrect operator classes causing
Postgres to ignore them; update the .op(...) calls to match each column type:
for index('assistant_answers_thread_doc_scope_idx') change
table.threadId.op('text_ops') -> 'uuid_ops'; for
index('assistant_answers_thread_doc_scope_turn_idx') change
table.threadId.op('int4_ops') -> 'uuid_ops', table.scopeId.op('int4_ops') ->
'text_ops', table.turnIndex.op('enum_ops') -> 'int4_ops'; for
index('assistant_answers_thread_scope_idx') change table.threadId.op('enum_ops')
-> 'uuid_ops'; for index('assistant_answers_thread_scope_turn_idx') change
table.scopeId.op('enum_ops') -> 'text_ops' and table.turnIndex.op('uuid_ops') ->
'int4_ops'; ensure every .op(...) matches the actual column type (uuid ->
uuid_ops, text -> text_ops, integer -> int4_ops, enum -> enum_ops).
| index('document_operations_doc_node_scope_created_idx').using( | ||
| 'btree', | ||
| table.docNodeId.asc().nullsLast().op('timestamptz_ops'), | ||
| table.scopeType.asc().nullsLast().op('timestamptz_ops'), | ||
| table.scopeId.asc().nullsLast().op('timestamptz_ops'), | ||
| table.createdAt.desc().nullsFirst().op('timestamptz_ops'), | ||
| ), | ||
| index('document_operations_doc_node_section_idx').using( | ||
| 'btree', | ||
| table.docNodeId.asc().nullsLast().op('text_ops'), | ||
| table.sectionId.asc().nullsLast().op('text_ops'), | ||
| ), | ||
| index('document_operations_document_key_created_idx').using( | ||
| 'btree', | ||
| table.documentKey.asc().nullsLast().op('timestamptz_ops'), | ||
| table.createdAt.desc().nullsFirst().op('timestamptz_ops'), | ||
| ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mismatched operator classes in documentOperations indexes.
Text columns are using timestamptz_ops instead of text_ops:
| Line | Column | Type | Current Op | Expected Op |
|---|---|---|---|---|
| 568 | docNodeId | text | timestamptz_ops |
text_ops |
| 569 | scopeType | text | timestamptz_ops |
text_ops |
| 570 | scopeId | text | timestamptz_ops |
text_ops |
| 580 | documentKey | text | timestamptz_ops |
text_ops |
🔧 Proposed fix
index('document_operations_doc_node_scope_created_idx').using(
'btree',
- table.docNodeId.asc().nullsLast().op('timestamptz_ops'),
- table.scopeType.asc().nullsLast().op('timestamptz_ops'),
- table.scopeId.asc().nullsLast().op('timestamptz_ops'),
+ table.docNodeId.asc().nullsLast().op('text_ops'),
+ table.scopeType.asc().nullsLast().op('text_ops'),
+ table.scopeId.asc().nullsLast().op('text_ops'),
table.createdAt.desc().nullsFirst().op('timestamptz_ops'),
),
...
index('document_operations_document_key_created_idx').using(
'btree',
- table.documentKey.asc().nullsLast().op('timestamptz_ops'),
+ table.documentKey.asc().nullsLast().op('text_ops'),
table.createdAt.desc().nullsFirst().op('timestamptz_ops'),
),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| index('document_operations_doc_node_scope_created_idx').using( | |
| 'btree', | |
| table.docNodeId.asc().nullsLast().op('timestamptz_ops'), | |
| table.scopeType.asc().nullsLast().op('timestamptz_ops'), | |
| table.scopeId.asc().nullsLast().op('timestamptz_ops'), | |
| table.createdAt.desc().nullsFirst().op('timestamptz_ops'), | |
| ), | |
| index('document_operations_doc_node_section_idx').using( | |
| 'btree', | |
| table.docNodeId.asc().nullsLast().op('text_ops'), | |
| table.sectionId.asc().nullsLast().op('text_ops'), | |
| ), | |
| index('document_operations_document_key_created_idx').using( | |
| 'btree', | |
| table.documentKey.asc().nullsLast().op('timestamptz_ops'), | |
| table.createdAt.desc().nullsFirst().op('timestamptz_ops'), | |
| ), | |
| index('document_operations_doc_node_scope_created_idx').using( | |
| 'btree', | |
| table.docNodeId.asc().nullsLast().op('text_ops'), | |
| table.scopeType.asc().nullsLast().op('text_ops'), | |
| table.scopeId.asc().nullsLast().op('text_ops'), | |
| table.createdAt.desc().nullsFirst().op('timestamptz_ops'), | |
| ), | |
| index('document_operations_doc_node_section_idx').using( | |
| 'btree', | |
| table.docNodeId.asc().nullsLast().op('text_ops'), | |
| table.sectionId.asc().nullsLast().op('text_ops'), | |
| ), | |
| index('document_operations_document_key_created_idx').using( | |
| 'btree', | |
| table.documentKey.asc().nullsLast().op('text_ops'), | |
| table.createdAt.desc().nullsFirst().op('timestamptz_ops'), | |
| ), |
🤖 Prompt for AI Agents
In `@packages/mb-drizzle/src/drizzle/schema.ts` around lines 566 - 582, Indexes
document_operations_doc_node_scope_created_idx and
document_operations_document_key_created_idx use the wrong operator class for
text columns; update the op calls for table.docNodeId, table.scopeType,
table.scopeId, and table.documentKey from 'timestamptz_ops' to 'text_ops' (leave
the section index as-is since it already uses text_ops); modify the index
definitions referencing those symbols so the text columns use op('text_ops')
consistently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai This isn't database schema issue? This is an auto-generated file that follows a given schema. Right now I am discovering why we need document_operations if we have already thread.metadata.documents which already tracks version change (and active current version) and sections on each document, so as the relationship that it has with an organization project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
@AndlerRL, you raise two important points—let me address both:
1. Auto-generated vs Schema Issue
While Drizzle schemas can be generated from databases, this appears to be a source schema that drives migrations. Regardless of how the file is created, the operator class mismatch is a real PostgreSQL issue:
- Text columns (
docNodeId,scopeType,scopeId,documentKey) should usetext_ops - Using
timestamptz_opsfor text columns can cause index build failures or silent performance degradation - PostgreSQL won't necessarily error immediately, but the index won't work optimally
2. Why document_operations When thread.metadata.documents Exists?
Let me search the codebase to understand the distinction:
🏁 Script executed:
#!/bin/bash
# Search for thread.metadata.documents usage to understand its structure
echo "=== thread.metadata.documents usage ==="
rg -A5 -B2 "metadata\.documents" --type ts --type tsx
echo -e "\n=== document_operations usage in code ==="
rg "document_operations|documentOperations" --type ts --type tsx -C3
echo -e "\n=== Check migration files for document_operations purpose ==="
fd -e sql . apps/hasura/migrations | xargs grep -l "document_operations" | head -5 | xargs cat
Length of output: 3883
Based on the migration file, I can now explain the architectural distinction:
document_operations vs thread.metadata.documents
They serve complementary but distinct purposes:
thread.metadata.documents (Current State)
- What it stores: Current document state
- Purpose: "What IS currently true"
- Contents: Active version, current sections, organization/project relationship
- Storage: JSONB (flexible, document-oriented)
document_operations (Audit Trail)
- What it stores: Operation history
- Purpose: "What HAPPENED over time"
- Contents (from migration):
-- Operation identity operation_type text NOT NULL, -- 'rewrite' | 'expand' | 'edit' | 'unknown' status text NOT NULL DEFAULT 'started', -- 'started' | 'completed' | 'failed' -- Prompt payload user_prompt text NOT NULL, meta_prompt text NULL, -- Selection info (replay capability) selection_start int NULL, selection_end int NULL, -- Version linkage version_before int NULL, version_after int NULL, checksum_before text NULL, checksum_after text NULL, error text NULL
Why Both?
Benefits of separate audit table:
- ✅ Query operation history without parsing JSONB
- ✅ Time-series analysis of edit operations (indexed by
created_at) - ✅ Error tracking/debugging (stores error details per operation)
- ✅ Replay capability (has prompts + selections + versions)
- ✅ Performance - doesn't bloat
thread.metadata, efficient time-based indexes - ✅ Best-effort logging (see AI summary: "best-effort logging without blocking UX")
The migration comment confirms this design: "store document-edit prompts separately... Keep additive + safe patterns; do not modify existing chat tables."
Regarding the operator class issue: Even if auto-generated, the schema should be corrected to prevent PostgreSQL index issues. Would you like me to verify if this matches the actual migration SQL, or if there's a generation mismatch?
🏁 Script executed:
#!/bin/bash
# Search for thread.metadata usage patterns
echo "=== thread.metadata usage ==="
rg "thread\.metadata" -t ts -C2 | head -80
echo -e "\n=== Log document operation usage ==="
rg "logDocumentOperation" -t ts -C5 | head -80
echo -e "\n=== Check AI summary for document_operations purpose ==="
cat << 'EOF'
From the AI summary, the document_operations table purpose is described in:
apps/hasura/migrations/masterbots/1767498310896_add_public_document_operations/up.sql
EOF
Length of output: 11309
@AndlerRL, excellent question! I found something important: there's a generation mismatch.
🐛 Operator Class Issue is REAL (Generation Bug)
Comparing the migration SQL (source of truth) vs Drizzle schema:
Migration SQL (✅ Correct):
CREATE INDEX IF NOT EXISTS document_operations_doc_node_scope_created_idx
ON public.document_operations (doc_node_id, scope_type, scope_id, created_at DESC);
-- ^ PostgreSQL infers text_ops for text columns automaticallyDrizzle Schema (❌ Incorrect):
table.docNodeId.asc().nullsLast().op('timestamptz_ops'), // WRONG!
table.scopeType.asc().nullsLast().op('timestamptz_ops'), // WRONG!This suggests Drizzle's schema introspection generated incorrect operator classes. The migration is correct, but the TypeScript schema needs manual correction.
📊 Architecture: Why Both Tables?
From the codebase, here's the concrete evidence:
thread.metadata (Current State - JSONB)
// apps/pro-web/lib/cron/refresh-workspace-document-links.ts
sql`${thread.metadata}::jsonb ? 'documents' AND
jsonb_array_length(${thread.metadata}::jsonb->'documents') > 0`
// apps/pro-web/components/routes/thread/thread-list.tsx
const docs = (thread.metadata as { documents?: DocMeta[] } | null)?.documentsPurpose: Stores current document list, attachments, relationships
document_operations (Audit Trail - Relational)
// apps/pro-web/lib/hooks/use-workspace-chat.tsx
docOpIdPromiseRef.current = logDocumentOperation({
mode: 'start',
threadId, project, documentName, documentKey,
docNodeId, sectionId, scopeType, scopeId,
operationType,
userPrompt, metaPrompt,
selectionStart, selectionEnd,
versionBefore, checksumBefore,
})
// Later...
await logDocumentOperation({
mode: 'complete',
id: opId,
versionAfter, checksumAfter,
})
// Or on error...
await logDocumentOperation({
mode: 'fail',
id: opId,
error: error.message,
})Purpose: Operation audit trail with lifecycle tracking
Key Differences
| Aspect | thread.metadata.documents |
document_operations |
|---|---|---|
| Type | JSONB (flexible) | Relational (queryable) |
| Content | Current state | Historical operations |
| Query | Parse JSONB | Indexed columns |
| Use Case | "What docs exist now?" | "What operations happened?" |
| Performance | Full scan for history | Time-indexed queries |
| Debugging | Current values only | Full audit trail with prompts, errors |
Real-World Benefit
// Best-effort logging - doesn't block UX
// apps/pro-web/app/actions/document-operations/log-document-operation.ts
export async function logDocumentOperation(args) {
// NOTE: This action must be best-effort for zero regressions.
// Callers should catch errors / never block UX.
try {
// ... insert/update document_operations
} catch (err) {
console.error('Failed to log operation:', err)
// Doesn't throw - operation continues
}
}This allows debugging edit failures (what prompt? what selection? what error?) without affecting user experience.
🔧 Recommended Fix
- Operator classes: Update Drizzle schema manually or regenerate with correct introspection
- Keep both tables: They serve different purposes (state vs audit), and the code is already using
document_operationsfor observability
Would you like me to generate a corrected version of the index definitions in the schema?
🧠 Learnings used
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 542
File: apps/web/components/layout/profile/layout.tsx:67-68
Timestamp: 2025-08-29T20:34:24.433Z
Learning: AndlerRL prefers skeleton loading states over conditional rendering for better UX when components are waiting for data to load. Components should show loading indicators rather than blank spaces.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-21T19:47:50.195Z
Learning: For repo bitcashorg/masterbots (apps/pro-web), AndlerRL prefers integrating TanStack Query into React Context Hooks wherever they fit to manage shared server state and side effects, with a longer-term plan to align broader state management with TanStack Query and potentially replace React Context hooks. Captured from PR `#584` discussion on Nov 21, 2025.
| index('message_doc_node_id_idx').using( | ||
| 'btree', | ||
| table.docNodeId.asc().nullsLast().op('text_ops'), | ||
| ), | ||
| index('message_thread_doc_created_idx').using( | ||
| 'btree', | ||
| table.threadId.asc().nullsLast().op('uuid_ops'), | ||
| table.docNodeId.asc().nullsLast().op('uuid_ops'), | ||
| table.createdAt.desc().nullsFirst().op('text_ops'), | ||
| ), | ||
| index('message_thread_doc_scope_created_idx').using( | ||
| 'btree', | ||
| table.threadId.asc().nullsLast().op('enum_ops'), | ||
| table.docNodeId.asc().nullsLast().op('timestamptz_ops'), | ||
| table.scopeType.asc().nullsLast().op('timestamptz_ops'), | ||
| table.scopeId.asc().nullsLast().op('text_ops'), | ||
| table.createdAt.desc().nullsFirst().op('enum_ops'), | ||
| ), | ||
| index('message_thread_scope_created_at_idx').using( | ||
| 'btree', | ||
| table.threadId.asc().nullsLast().op('text_ops'), | ||
| table.scopeType.asc().nullsLast().op('enum_ops'), | ||
| table.scopeId.asc().nullsLast().op('text_ops'), | ||
| table.createdAt.asc().nullsLast().op('uuid_ops'), | ||
| ), | ||
| index('message_thread_scope_qchain_idx') | ||
| .using( | ||
| 'btree', | ||
| table.threadId.asc().nullsLast().op('uuid_ops'), | ||
| table.scopeType.asc().nullsLast().op('text_ops'), | ||
| table.scopeId.asc().nullsLast().op('text_ops'), | ||
| table.createdAt.asc().nullsLast().op('uuid_ops'), | ||
| ) | ||
| .where(sql`((role = 'user'::text) AND (exclude_from_q_chain = false))`), | ||
| index('message_thread_scope_recent_idx').using( | ||
| 'btree', | ||
| table.threadId.asc().nullsLast().op('uuid_ops'), | ||
| table.scopeType.asc().nullsLast().op('text_ops'), | ||
| table.scopeId.asc().nullsLast().op('enum_ops'), | ||
| table.createdAt.desc().nullsFirst().op('text_ops'), | ||
| ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Multiple operator class mismatches in message table indexes.
The new indexes have several mismatched operator classes:
| Line | Column | Type | Current Op | Expected Op |
|---|---|---|---|---|
| 913 | docNodeId | text | uuid_ops |
text_ops |
| 914 | createdAt | timestamp | text_ops |
timestamptz_ops |
| 918 | threadId | uuid | enum_ops |
uuid_ops |
| 919 | docNodeId | text | timestamptz_ops |
text_ops |
| 920 | scopeType | enum | timestamptz_ops |
enum_ops |
| 922 | createdAt | timestamp | enum_ops |
timestamptz_ops |
| 926 | threadId | uuid | text_ops |
uuid_ops |
| 929 | createdAt | timestamp | uuid_ops |
timestamptz_ops |
| 937 | createdAt | timestamp | uuid_ops |
timestamptz_ops |
| 944 | scopeId | text | enum_ops |
text_ops |
| 945 | createdAt | timestamp | text_ops |
timestamptz_ops |
🔧 Proposed fix
index('message_thread_doc_created_idx').using(
'btree',
table.threadId.asc().nullsLast().op('uuid_ops'),
- table.docNodeId.asc().nullsLast().op('uuid_ops'),
- table.createdAt.desc().nullsFirst().op('text_ops'),
+ table.docNodeId.asc().nullsLast().op('text_ops'),
+ table.createdAt.desc().nullsFirst().op('timestamptz_ops'),
),
index('message_thread_doc_scope_created_idx').using(
'btree',
- table.threadId.asc().nullsLast().op('enum_ops'),
- table.docNodeId.asc().nullsLast().op('timestamptz_ops'),
- table.scopeType.asc().nullsLast().op('timestamptz_ops'),
+ table.threadId.asc().nullsLast().op('uuid_ops'),
+ table.docNodeId.asc().nullsLast().op('text_ops'),
+ table.scopeType.asc().nullsLast().op('enum_ops'),
table.scopeId.asc().nullsLast().op('text_ops'),
- table.createdAt.desc().nullsFirst().op('enum_ops'),
+ table.createdAt.desc().nullsFirst().op('timestamptz_ops'),
),
index('message_thread_scope_created_at_idx').using(
'btree',
- table.threadId.asc().nullsLast().op('text_ops'),
+ table.threadId.asc().nullsLast().op('uuid_ops'),
table.scopeType.asc().nullsLast().op('enum_ops'),
table.scopeId.asc().nullsLast().op('text_ops'),
- table.createdAt.asc().nullsLast().op('uuid_ops'),
+ table.createdAt.asc().nullsLast().op('timestamptz_ops'),
),
index('message_thread_scope_qchain_idx')
.using(
'btree',
table.threadId.asc().nullsLast().op('uuid_ops'),
- table.scopeType.asc().nullsLast().op('text_ops'),
+ table.scopeType.asc().nullsLast().op('enum_ops'),
table.scopeId.asc().nullsLast().op('text_ops'),
- table.createdAt.asc().nullsLast().op('uuid_ops'),
+ table.createdAt.asc().nullsLast().op('timestamptz_ops'),
)
.where(sql`((role = 'user'::text) AND (exclude_from_q_chain = false))`),
index('message_thread_scope_recent_idx').using(
'btree',
table.threadId.asc().nullsLast().op('uuid_ops'),
- table.scopeType.asc().nullsLast().op('text_ops'),
- table.scopeId.asc().nullsLast().op('enum_ops'),
- table.createdAt.desc().nullsFirst().op('text_ops'),
+ table.scopeType.asc().nullsLast().op('enum_ops'),
+ table.scopeId.asc().nullsLast().op('text_ops'),
+ table.createdAt.desc().nullsFirst().op('timestamptz_ops'),
),🤖 Prompt for AI Agents
In `@packages/mb-drizzle/src/drizzle/schema.ts` around lines 906 - 946, The new
message table index definitions (index names message_doc_node_id_idx,
message_thread_doc_created_idx, message_thread_doc_scope_created_idx,
message_thread_scope_created_at_idx, message_thread_scope_qchain_idx,
message_thread_scope_recent_idx) use incorrect operator classes for several
columns; update the .op(...) calls on the referenced columns (table.docNodeId,
table.createdAt, table.threadId, table.scopeType, table.scopeId) to match their
actual column types: use text_ops for text columns (docNodeId, scopeId),
uuid_ops for uuid columns (threadId), timestamptz_ops for timestamp/timestamptz
columns (createdAt), and enum_ops for enum columns (scopeType); ensure each
index's .op(...) entries are corrected accordingly in the index(...) .using(...)
definitions where those columns appear.
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@apps/pro-web/app/actions/chat-memory/embedding.ts`:
- Around line 10-41: embedText1536 can hang because the fetch to OpenAI has no
timeout; wrap the fetch in an AbortController with a configurable timeout (e.g.,
10s), pass controller.signal into the fetch call, start a setTimeout that calls
controller.abort() after the timeout, and clear the timer after a successful
response (and on error) so the timer doesn't leak; ensure you catch an abort
error and rethrow a meaningful Error (e.g., "OpenAI embeddings request timed
out") before continuing to parse the response.
In `@apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts`:
- Around line 147-318: The query currently hardcodes minCosine's GraphQL var
type to "float8" in makeSearchQuery and never adapts on GraphQL type errors;
update makeSearchQuery and searchAssistantAnswers to treat minCosine like
queryEmbedding: add a minCosineVarType parameter (default "float8") to
makeSearchQuery and use it in varDefs (`$minCosine: ${minCosineVarType}!`),
ensure the hasuraGql call still passes minCosine value, and inside the retry
loop call tryExtractExpectedType(msg, 'minCosine') (e.g., expMinCosine) and if
present assign minCosineVarType = expMinCosine and continue so the code retries
with the corrected numeric type; reference functions/identifiers:
makeSearchQuery, searchAssistantAnswers, minCosine, tryExtractExpectedType.
♻️ Duplicate comments (4)
apps/pro-web/components/routes/chat/chat-list/index.tsx (1)
109-119: Type casting improved but runtime validation still absent.The casts have been simplified from double assertions, which is an improvement. However, the underlying concern remains: if upstream message shapes diverge from
ThreadMessage, this will fail silently at runtime.Consider adding a lightweight runtime check or normalizer as previously suggested, especially since
messagesis typed asunknown[].♻️ Optional: minimal runtime guard
function isThreadMessage(m: unknown): m is ThreadMessage { return ( typeof m === 'object' && m !== null && 'content' in m && 'role' in m ) } const chatMessages = (messages || activeThread?.messages || []) .filter(isThreadMessage)apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx (2)
92-97: Incomplete timer cleanup on unmount.The cleanup effect clears
typingTimeoutRefandsectionSaveTimerRef, butsourceSaveTimerRef(declared at line 282) is not cleaned up. This could cause a state update on an unmounted component if a source save is pending during unmount.🔧 Suggested fix
useEffect(() => { return () => { if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current) if (sectionSaveTimerRef.current) clearTimeout(sectionSaveTimerRef.current) + if (sourceSaveTimerRef.current) clearTimeout(sourceSaveTimerRef.current) } }, [])Note: This requires moving
sourceSaveTimerRefdeclaration (line 282) before this effect, or restructuring the refs together near line 87.
304-336: Sections/Source buttons don't toggleviewMode.Both buttons display styling based on
viewModebut clicking them only callssetIsPreview(false). The component doesn't receive a handler to changeviewMode, so users cannot switch between sections and source views using these buttons.Either pass
handleViewSourceToggleas a prop and wire it to these buttons, or remove the buttons if view mode switching is handled elsewhere.🔧 Suggested fix
Add a prop and wire the handlers:
export type WorkspaceTextEditorProps = { // ... existing props + handleViewSourceToggle: (mode: 'sections' | 'source') => void }Then update the button handlers:
<Button type="button" variant={viewMode === 'sections' ? 'secondary' : 'outline'} size="sm" - onClick={() => setIsPreview(false)} + onClick={() => { + handleViewSourceToggle('sections') + setIsPreview(false) + }} disabled={isBusy} > <FileText className="size-4 mr-2" /> Sections </Button> <Button type="button" variant={viewMode === 'source' ? 'secondary' : 'outline'} size="sm" - onClick={() => setIsPreview(false)} + onClick={() => { + handleViewSourceToggle('source') + setIsPreview(false) + }} disabled={isBusy} > <TextCursorInputIcon className="size-4 mr-2" /> Source </Button>apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts (1)
152-171: Avoid silently swallowing non-schema insert failures.Non-schema errors are dropped with no trace, which makes ingestion issues hard to diagnose. Logging (or otherwise surfacing) the failure would help without breaking chat flow.
🔧 Suggested fix
} catch (error) { // Retry once without doc_node_id if that was the cause (schema lag during rollout). if (docNodeId && typeof (error as Error)?.message === 'string') { const msg = (error as Error).message if ( msg.includes('doc_node_id') || msg.includes('field') || msg.includes('assistant_answers_insert_input') ) { try { const { doc_node_id: _ignored, ...withoutDocNode } = baseObject await hasuraGql(insert, { object: withoutDocNode }) + return } catch { + console.warn('[ingestAssistantAnswer] Retry without doc_node_id failed') return } } } + console.warn('[ingestAssistantAnswer] Insert failed:', (error as Error)?.message) return }
🧹 Nitpick comments (7)
apps/pro-web/components/routes/chat/chat-list/index.tsx (2)
186-199: Redundant?? undefinedafter type assertion.On lines 188-189, the
?? undefinedis unnecessary since the type assertion already includes| undefined:-const md = - (activeThread?.metadata as Record<string, unknown> | undefined) ?? - undefined +const md = activeThread?.metadata as Record<string, unknown> | undefinedThe same pattern repeats at lines 217 and 218-219.
213-234: Comprehensive but verbose doc-anchor detection.The
hasDocAnchorfunction defensively checks multiple possible field paths (doc_node_id,docNodeId, inmetadata, inmb_op). While thorough, this suggests inconsistent data shapes upstream. Consider documenting which path is canonical and standardizing upstream if possible.apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx (2)
381-441: Consider extracting repeated selection range update logic.The
onBlur,onKeyUp, andonChangehandlers (lines 391-426) all compute and set the global selection range with similar logic. This is a minor duplication that could be consolidated into a helper function.♻️ Optional refactor
const updateSelectionFromTextarea = useCallback( (el: HTMLTextAreaElement) => { if (!activeSectionObj) return setGlobalSelectionRange({ start: activeSectionObj.contentStart + el.selectionStart, end: activeSectionObj.contentStart + el.selectionEnd, }) }, [activeSectionObj, setGlobalSelectionRange], )Then use it in handlers:
onBlur={(e) => { setIsFocused(false) - if (!activeSectionObj) return - const startRel = e.currentTarget.selectionStart - const endRel = e.currentTarget.selectionEnd - setGlobalSelectionRange({ - start: activeSectionObj.contentStart + startRel, - end: activeSectionObj.contentStart + endRel, - }) + updateSelectionFromTextarea(e.currentTarget) }}
510-526: Consider usinginterfacefor props type definition.Per coding guidelines,
interfaceis preferred for object shapes whiletypeis for unions/tuples/aliases. This props definition represents an object shape.♻️ Suggested refactor
-export type WorkspaceTextEditorProps = { +export interface WorkspaceTextEditorProps { activeSection: string | null setActiveSection: (sectionId: string | null) => void viewMode: 'sections' | 'source' sectionTextareaRef: React.RefObject<HTMLTextAreaElement> sourceTextareaRef: React.RefObject<HTMLTextAreaElement> handleCursorPositionChange: (e?: React.SyntheticEvent) => void handleSectionClick: (sectionId: string) => void markUserTyping: () => void handleExpandSection: (sectionTitle: string) => void handleRewriteSection: (sectionTitle: string) => void }CLAUDE.md (1)
116-231: Excellent architecture documentation restructuring.The reorganization significantly improves the documentation:
- Clear separation of Core Applications vs Shared Packages (lines 122-135)
- New "State Management Patterns" section documents the provider architecture and data flow patterns (lines 154-174)
- "AI Integration Environment" section provides context for the RAG infrastructure being added (lines 176-191)
- Expanded "GraphQL Integration" guidance aligns with new database operations (lines 192-207)
- Comprehensive "Development Guidelines" with external documentation links (lines 208-231)
These changes align well with the established patterns documented in learnings and support the PR's RAG feature additions.
💡 Optional: Add code examples for RORO pattern and event handlers
Line 275 - RORO Pattern Example:
Consider adding a code example similar to the named exports example:
// RORO pattern example // Prefer this export function createUser({ name, email, role }: CreateUserParams): CreateUserResult { // ... return { user, error: null }; } // Over this export function createUser(name: string, email: string, role: string) { // ... return user; }Line 277 - Event Handler Example:
The guidance "use regular function calls instead of arrow functions" could be clarified:
// Prefer this - stable function reference function handleClick() { // ... } <Button onClick={handleClick} /> // Avoid this - creates new function on each render <Button onClick={() => handleClick()} />apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts (1)
175-211: Consider routing Hasura calls throughhasura.service.ts.Centralizing GraphQL concerns (auth, error shaping, retries) reduces duplication and keeps Hasura integration consistent across the app. If server actions can’t use it, please confirm the constraint. As per coding guidelines, ...
apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts (1)
515-547: Prefer the shared Hasura service layer.Using
hasura.service.tshere would centralize auth/error handling and reduce duplicate GraphQL plumbing. As per coding guidelines, ...
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
AGENTS.mdCLAUDE.mdREADME.mdapps/hasura/migrations/masterbots/1766624501677_assistant_answers/down.sqlapps/pro-web/app/actions/chat-memory/embedding.tsapps/pro-web/app/actions/chat-memory/ingest-assistant-answer.tsapps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.tsapps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/hasura/migrations/masterbots/1766624501677_assistant_answers/down.sql
🧰 Additional context used
📓 Path-based instructions (8)
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: Use lowercase with dash-separated directory and file names; use specific extensions: .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Use Bun for package management and scripts instead of npm/yarn
Use Biome for formatting and linting via bun format-and-lint:fix; avoid Prettier/ESLint configs
Files:
README.mdapps/pro-web/app/actions/chat-memory/embedding.tsapps/pro-web/components/routes/chat/chat-list/index.tsxCLAUDE.mdapps/pro-web/app/actions/chat-memory/ingest-assistant-answer.tsapps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.tsAGENTS.mdapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
apps/{web,pro-web}/app/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/app/**: Prefer Next.js Server Components; use 'use client' only when necessary
Leverage Streaming and Suspense for responsiveness in Next.js 15
Follow Next.js file conventions in the App Router
apps/{web,pro-web}/app/**: Leverage Server Components by default; use the use client directive only when client-side features are needed
Implement Nested Layouts using the App Router
Use Streaming and Suspense features in Next.js 14/15 where appropriate
Follow Next.js file conventions for routing and layout in the App Router
Optimize data fetching: fetch on the server, fetch where needed, and use preload patterns to avoid waterfalls
Files:
apps/pro-web/app/actions/chat-memory/embedding.tsapps/pro-web/app/actions/chat-memory/ingest-assistant-answer.tsapps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use meaningful variable names; prefix booleans with is/has/does/should
Use interface for objects/classes; type for unions/tuples/aliases; enum for fixed sets; const for literals
Avoid any; prefer specific types
Leverage type inference where clear; annotate function params and return types
Prefer type assertions with 'as' over angle-bracket syntax
Use type guards to narrow types in conditionals
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use modern JavaScript features with TypeScript across the codebase
Use meaningful names; prefix booleans with is/has/does/should (e.g., isDisabled)
Receive an object and return an object (RORO) for functions interfacing with external services
Type definitions: use interface for objects/classes; type for unions/tuples/aliases; const for literals; enum for fixed sets
Avoid any; prefer specific types
Leverage type inference; omit explicit annotations when obvious
Add explicit type annotations for function parameters and return values
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types within conditionals
Favor composition over inheritance in code structure
**/*.{ts,tsx}: Avoid default exports; prefer named exports
Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions
Use interface for object shapes/classes; type for unions/tuples/aliases; const for literals; enum for fixed enumerations
Avoid using any; prefer precise types
Leverage type inference when clear; omit redundant annotations
Explicitly annotate function parameters and return types
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types safely
Use meaningful variable names; prefix booleans with is/has/does/should
Favor composition over inheritance
Use lowercase dash-separated names for files and folders; use extensions like .config.ts, .test.ts, .context.tsx, .typ...
Files:
apps/pro-web/app/actions/chat-memory/embedding.tsapps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/app/actions/chat-memory/ingest-assistant-answer.tsapps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.tsapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
apps/{web,pro-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/**/*.{ts,tsx}: Use lowercase kebab-case for directories and filenames (components can omit extension)
Verify DOM operations and consider timing (RAF/delays) when manipulating the DOM in React
Use Tailwind CSS utility classes for styling React components
apps/{web,pro-web}/**/*.{ts,tsx}: Use TypeScript to define React props types (interfaces for props)
Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Separate presentation from business logic and compose small components
Use hasura.service.ts for all GraphQL operations from UI layers
apps/{web,pro-web}/**/*.{ts,tsx}: Prefer Server Components; use the use client directive only when client-side features are required
Implement Nested Layouts using the App Router
Use Streaming and Suspense in Next.js 14/15 where appropriate
Follow Next.js file conventions as per documentation
Use IndexedDB for local caching to enable immediate UI updates
Files:
apps/pro-web/app/actions/chat-memory/embedding.tsapps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/app/actions/chat-memory/ingest-assistant-answer.tsapps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.tsapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
apps/{web,pro-web}/components/routes/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Place page-specific components under components/routes/[feature]/
Organize page-specific components under components/routes/
Files:
apps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
apps/{web,pro-web}/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/{web,pro-web}/**/*.tsx: Declare React components with the function keyword
Name React components using PascalCase
Order component files: imports → constants → component → styled components → types/interfaces
Use functional components and hooks; type props with a TypeScript interface
Use regular function references (not inline arrows) for React event handlers to avoid extra re-renders
apps/{web,pro-web}/**/*.tsx: Use regular function references for event handlers instead of inline arrow functions in JSX
Declare React components with the function keyword (function ComponentName() {})
Use PascalCase for React component names
Order React component files: imports, constants, component, styled components, types/interfaces
Style components with Tailwind CSS utility classes
Files:
apps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
**/*.tsx: Avoid inline arrow functions in JSX props; use stable function references for event handlers
Declare React components with the function keyword (functional components)
Use PascalCase for React component names
Use functional components and hooks instead of class components
Type component props with TypeScript interfaces
Files:
apps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
apps/{web,pro-web}/components/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Order component files: imports → constants → component → styled components → types/interfaces
Files:
apps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
🧠 Learnings (77)
📓 Common learnings
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-21T19:47:50.195Z
Learning: For repo bitcashorg/masterbots (apps/pro-web), AndlerRL prefers integrating TanStack Query into React Context Hooks wherever they fit to manage shared server state and side effects, with a longer-term plan to align broader state management with TanStack Query and potentially replace React Context hooks. Captured from PR `#584` discussion on Nov 21, 2025.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 542
File: apps/web/components/layout/profile/layout.tsx:67-68
Timestamp: 2025-08-29T20:34:24.433Z
Learning: AndlerRL prefers skeleton loading states over conditional rendering for better UX when components are waiting for data to load. Components should show loading indicators rather than blank spaces.
📚 Learning: 2024-12-05T01:08:50.085Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The project is a monorepo using Turborepo with Next.js frontend (apps/masterbots.ai), Hasura backend (apps/hasura), and shared packages for types, GraphQL operations, and utilities.
Applied to files:
README.mdCLAUDE.mdAGENTS.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Run backend with task boot before frontend development
Applied to files:
README.md
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to **/* : Use Bun for package management and scripts instead of npm/yarn
Applied to files:
README.mdAGENTS.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Use Bun for all package management operations instead of npm/yarn
Applied to files:
README.md
📚 Learning: 2025-06-13T08:55:45.217Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 508
File: apps/masterbots.ai/lib/helpers/ai-helpers.ts:280-283
Timestamp: 2025-06-13T08:55:45.217Z
Learning: In `apps/masterbots.ai/lib/helpers/ai-helpers.ts` the `verifyDuplicateMessage` function should:
• Use `message.content` as the sole de-duplication key so provisional (no-slug) and persisted (slugged) messages merge properly.
• Return the full `message` object (not just a string key) for the continuation prompts (`CONTINUE_GENERATION_PROMPT`, `CONTINUE_GENERATION_PROMPT_2`) because their content repeats and they must remain distinct.
• No explicit `!message.content` guard is needed; `return message.content` already yields `undefined` when content is missing.
Applied to files:
apps/pro-web/app/actions/chat-memory/embedding.tsapps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/app/actions/chat-memory/ingest-assistant-answer.tsapps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts
📚 Learning: 2025-02-17T16:51:19.102Z
Learnt from: Bran18
Repo: bitcashorg/masterbots PR: 376
File: apps/masterbots.ai/components/routes/chat/chat.tsx:92-102
Timestamp: 2025-02-17T16:51:19.102Z
Learning: For the Chat component in apps/masterbots.ai/components/routes/chat/chat.tsx, the developer prefers to verify potential optimizations through local testing before accepting suggestions, particularly for performance-related changes like useCallback usage in debounce implementations.
Applied to files:
apps/pro-web/app/actions/chat-memory/embedding.tsapps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-07-02T19:46:45.701Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 519
File: apps/masterbots.ai/lib/hooks/use-chat-attachments.ts:276-297
Timestamp: 2025-07-02T19:46:45.701Z
Learning: In `apps/masterbots.ai/lib/hooks/use-chat-attachments.ts`, the `validateTextContent` function intentionally uses `slugify()` with character removal (`remove: /[^\w\s]/g`) followed by splitting on hyphens to count words. This approach is preferred over simple whitespace splitting because it removes special characters (like those found in code text) and counts only "true words", making it more robust for validating pasted content that may contain mixed text and code.
Applied to files:
apps/pro-web/app/actions/chat-memory/embedding.ts
📚 Learning: 2024-11-14T21:12:19.789Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 302
File: apps/masterbots.ai/lib/hooks/use-mb-chat.ts:0-0
Timestamp: 2024-11-14T21:12:19.789Z
Learning: In `apps/masterbots.ai/lib/hooks/use-mb-chat.ts`, for handling message ordering issues, prefer simple solutions like using `setTimeout` over adding sequence numbers to the message schema, to keep the codebase simple.
Applied to files:
apps/pro-web/app/actions/chat-memory/embedding.tsapps/pro-web/components/routes/chat/chat-list/index.tsxAGENTS.md
📚 Learning: 2025-04-26T10:58:44.341Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 465
File: apps/masterbots.ai/components/routes/browse/browse-list.tsx:1-1
Timestamp: 2025-04-26T10:58:44.341Z
Learning: The browse-list.tsx component is redundant as thread-list.tsx has been developed to support all the same functionality including public pages. The codebase should be cleaned up to remove browse-list.tsx and related components in the browse directory, along with the BrowseProvider context.
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsxCLAUDE.mdAGENTS.mdapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-04-26T10:58:44.341Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 465
File: apps/masterbots.ai/components/routes/browse/browse-list.tsx:1-1
Timestamp: 2025-04-26T10:58:44.341Z
Learning: The browse-list.tsx component and related components in the browse directory are redundant since thread-list.tsx has been developed to support all pages including public pages, and should be removed to simplify the codebase.
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsx
📚 Learning: 2025-10-24T20:40:56.510Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 573
File: apps/pro-web/components/routes/pro/pro.tsx:49-49
Timestamp: 2025-10-24T20:40:56.510Z
Learning: In apps/pro-web/lib/hooks/use-mb-chat.tsx, the useMBChat() hook returns a tuple [MBChatHookState, MBChatHookActions], not a direct object. Use array destructuring to access state (index 0) and actions (index 1), e.g., const [state, actions] = useMBChat() or const [{ isLoading }] = useMBChat().
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsx
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/*.{ts,tsx} : Prefer type assertions with as over angle-bracket syntax
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsx
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/*.{ts,tsx} : Prefer type assertions with 'as' over angle-bracket syntax
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsx
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Prefer Server Components; use the use client directive only when client-side features are required
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/**/*.tsx : Use functional components and hooks; type props with a TypeScript interface
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsxapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Separate presentation from business logic and compose small components
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsxCLAUDE.mdAGENTS.md
📚 Learning: 2024-12-11T19:40:37.103Z
Learnt from: Bran18
Repo: bitcashorg/masterbots PR: 324
File: apps/masterbots.ai/components/routes/browse/browse-chatbot-mobile-details.tsx:145-146
Timestamp: 2024-12-11T19:40:37.103Z
Learning: In `apps/masterbots.ai/components/routes/browse/browse-chatbot-mobile-details.tsx`, the follower and following counts are currently hardcoded as these features are being developed in other PRs.
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsx
📚 Learning: 2025-04-01T03:48:44.950Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 428
File: apps/masterbots.ai/lib/hooks/use-mb-chat.tsx:196-216
Timestamp: 2025-04-01T03:48:44.950Z
Learning: The `getCurrentSearchParams` function in `use-mb-chat.tsx` is intentionally using `window.location.search` to handle cases where the `useSearchParams` hook doesn't update when the page is reloaded. This is safe because the component has the 'use client' directive.
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsx
📚 Learning: 2024-12-05T01:14:50.365Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:14:50.365Z
Learning: The masterbots.ai project follows Next.js 14 App Router conventions with server components as default and client components marked explicitly with 'use client' directive when needed.
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsxCLAUDE.mdAGENTS.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Implement Nested Layouts using the App Router
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsxAGENTS.md
📚 Learning: 2024-11-20T04:00:45.413Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 300
File: apps/masterbots.ai/components/routes/thread/user-thread-panel.tsx:120-123
Timestamp: 2024-11-20T04:00:45.413Z
Learning: In React code, when reviewing `useEffect` dependencies, it's not always necessary to add functions like `handleThreadsChange` to the dependency array, as this may lead to unnecessary re-renders and performance issues. Only include dependencies that are essential and may change.
Applied to files:
apps/pro-web/components/routes/chat/chat-list/index.tsx
📚 Learning: 2025-08-26T06:40:51.075Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-08-26T06:40:51.075Z
Learning: The masterbots project uses a dual-app architecture with apps/web (masterbots.ai) and apps/pro-web (pro.masterbots.ai), where code duplication exists between the apps that should be addressed with a shared packages/ui package.
Applied to files:
CLAUDE.mdAGENTS.md
📚 Learning: 2024-12-05T01:08:50.085Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The codebase follows TypeScript with strict type checking and functional programming principles. Code organization follows a feature-based structure with dedicated directories for types, utils, and API endpoints.
Applied to files:
CLAUDE.mdAGENTS.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store and manage Hasura migrations under apps/hasura/migrations/masterbots/
Applied to files:
CLAUDE.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Follow Next.js file conventions as per documentation
Applied to files:
CLAUDE.mdAGENTS.md
📚 Learning: 2024-12-05T01:08:50.085Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The frontend uses shadcn/ui components and follows a strict component organization with proper separation of client/server components in Next.js.
Applied to files:
CLAUDE.md
📚 Learning: 2025-11-21T19:47:50.195Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-21T19:47:50.195Z
Learning: For repo bitcashorg/masterbots (apps/pro-web), AndlerRL prefers integrating TanStack Query into React Context Hooks wherever they fit to manage shared server state and side effects, with a longer-term plan to align broader state management with TanStack Query and potentially replace React Context hooks. Captured from PR `#584` discussion on Nov 21, 2025.
Applied to files:
CLAUDE.mdAGENTS.md
📚 Learning: 2024-11-06T05:58:49.546Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 295
File: apps/masterbots.ai/app/b/[id]/layout.tsx:13-18
Timestamp: 2024-11-06T05:58:49.546Z
Learning: In the codebase, the main layout (`apps/masterbots.ai/app/layout.tsx`) includes the `<main>` tag. Therefore, child layouts should avoid using `<main>` to prevent duplication.
Applied to files:
CLAUDE.md
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/app/** : Follow Next.js file conventions for routing and layout in the App Router
Applied to files:
CLAUDE.mdAGENTS.md
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Pro workspace editors: controlled/uncontrolled patterns, Markdown utilities, section state sync, and timed DOM operations
Applied to files:
CLAUDE.mdapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to packages/mb-genql/** : Use generated types and client from mb-genql for type-safe GraphQL operations
Applied to files:
CLAUDE.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to packages/mb-genql/** : Use generated types and client from mb-genql for type-safe GraphQL calls
Applied to files:
CLAUDE.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/*.{ts,tsx} : Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions
Applied to files:
CLAUDE.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/*.{ts,tsx} : Avoid default exports; prefer named exports
Applied to files:
CLAUDE.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/*.{ts,tsx} : Use meaningful variable names; prefix booleans with is/has/does/should
Applied to files:
CLAUDE.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/*.tsx : Avoid inline arrow functions in JSX props; use stable function references for event handlers
Applied to files:
CLAUDE.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/*.{ts,tsx} : Use lowercase dash-separated names for files and folders; use extensions like .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Applied to files:
CLAUDE.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/*.{ts,tsx} : Use interface for object shapes/classes; type for unions/tuples/aliases; const for literals; enum for fixed enumerations
Applied to files:
CLAUDE.mdAGENTS.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/*.{ts,tsx} : Use interface for objects/classes; type for unions/tuples/aliases; enum for fixed sets; const for literals
Applied to files:
CLAUDE.mdAGENTS.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Adopt unidirectional data flow: User Input → Component State → Custom Hooks → Server Actions → Database
Applied to files:
CLAUDE.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/components/ui/**/*.{ts,tsx} : Follow Radix UI patterns under components/ui/
Applied to files:
CLAUDE.mdAGENTS.md
📚 Learning: 2024-12-05T01:08:50.085Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: Test files follow the pattern `*.test.ts` or `*.spec.ts` and should implement the AAA (Arrange-Act-Assert) pattern with proper mocking of external dependencies.
Applied to files:
CLAUDE.mdAGENTS.md
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Follow unidirectional data flow: User Input → Component State → Custom Hooks → Server Actions → Database
Applied to files:
CLAUDE.md
📚 Learning: 2024-12-05T01:19:41.647Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:19:41.647Z
Learning: The project follows strict naming conventions: lowercase with dashes for directories/names, and specific extensions (.config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts) for different file types.
Applied to files:
CLAUDE.mdAGENTS.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single point for all GraphQL interactions
Applied to files:
apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single interaction point for GraphQL operations with Hasura
Applied to files:
apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: For DOM/Browser work: feature-detect, progressively enhance, use timing (delays/RAF), and verify operations
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: For React/State: minimize useEffect deps, use refs for non-reactive values, separate state vs DOM manipulation
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Follow environment-aware, resilient problem-solving (verify results, add fallbacks, consider timing, add observability, test incrementally)
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Verify DOM operations and consider timing (RAF/delays) when manipulating the DOM in React
Applied to files:
AGENTS.mdapps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Include error handling, fallbacks, verification, observability, and incremental testing in solutions
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Embrace React’s declarative style; avoid imperative DOM manipulation
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/app/** : Follow Next.js file conventions in the App Router
Applied to files:
AGENTS.md
📚 Learning: 2024-12-05T01:14:50.365Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:14:50.365Z
Learning: The codebase enforces TypeScript strict mode and follows Biome formatting rules across all TypeScript/JavaScript files.
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/*.{ts,tsx} : Avoid using any; prefer precise types
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/*.{ts,tsx} : Avoid any; prefer specific types
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to **/*.{ts,tsx} : Leverage type inference; omit explicit annotations when obvious
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/*.{ts,tsx} : Leverage type inference where clear; annotate function params and return types
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/*.{ts,tsx} : Leverage type inference when clear; omit redundant annotations
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to **/*.{ts,tsx} : Type definitions: use interface for objects/classes; type for unions/tuples/aliases; const for literals; enum for fixed sets
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to **/*.{ts,tsx} : Add explicit type annotations for function parameters and return values
Applied to files:
AGENTS.md
📚 Learning: 2024-12-05T01:14:50.365Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:14:50.365Z
Learning: The project uses shadcn/ui conventions for UI components and follows atomic design principles for component organization.
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/components/shared/**/*.{ts,tsx} : Store reusable UI elements in components/shared/
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/components/ui/** : Follow Radix UI patterns for UI components in components/ui/
Applied to files:
AGENTS.md
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.tsx : Order React component files: imports, constants, component, styled components, types/interfaces
Applied to files:
AGENTS.md
📚 Learning: 2025-09-04T21:31:18.830Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: The pro-web workspace system has identified performance bottlenecks in markdown parsing, AI streaming response handling, selection range management, Base64 document processing, and multiple debounced state operations that require optimization and better coordination.
Applied to files:
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-09-04T21:31:18.830Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: The pro-web workspace features have complex performance considerations including markdown parsing optimization, AI streaming response handling, state persistence coordination, and selection range management that require careful monitoring and optimization.
Applied to files:
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-09-04T21:31:18.830Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: Edge cases for pro-web workspace include concurrent document editing, AI streaming interruptions, large document handling, selection range boundary conditions, and storage failure scenarios that need comprehensive testing and error handling.
Applied to files:
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-06-13T04:19:05.145Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 508
File: apps/masterbots.ai/components/shared/markdown.tsx:21-33
Timestamp: 2025-06-13T04:19:05.145Z
Learning: `MemoizedReactMarkdown` is always used with `children` as a plain string (never React nodes) in the masterbots.ai codebase.
Applied to files:
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-01-21T16:52:47.056Z
Learnt from: Bran18
Repo: bitcashorg/masterbots PR: 337
File: package.json:32-34
Timestamp: 2025-01-21T16:52:47.056Z
Learning: The markdown rendering in this codebase uses `react-markdown` with `remark-gfm` and `remark-math` plugins for standard markdown and GitHub Flavored Markdown support. Raw HTML parsing through `rehype-raw` is not needed.
Applied to files:
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/app/** : Prefer Next.js Server Components; use 'use client' only when necessary
Applied to files:
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/**/*.tsx : Declare React components with the function keyword
Applied to files:
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use Streaming and Suspense in Next.js 14/15 where appropriate
Applied to files:
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2024-10-23T07:33:36.041Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 287
File: apps/masterbots.ai/lib/hooks/use-thread-visibility.tsx:3-3
Timestamp: 2024-10-23T07:33:36.041Z
Learning: Always use explicit imports for React hooks to maintain consistency across the monorepo, and avoid using both named and default imports simultaneously.
Applied to files:
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/lib/hooks/**/*.{ts,tsx} : Place custom React hooks in lib/hooks/
Applied to files:
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx
🧬 Code graph analysis (3)
apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts (3)
apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts (1)
AssistantAnswerScopeType(32-35)apps/pro-web/app/actions/chat-memory/index.ts (2)
IngestAssistantAnswerArgs(6-6)ingestAssistantAnswer(7-7)apps/pro-web/app/actions/chat-memory/embedding.ts (3)
embedText1536(10-42)sha256(44-46)vectorToPgText(48-51)
apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts (2)
apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts (1)
AssistantAnswerScopeType(5-8)apps/pro-web/app/actions/chat-memory/embedding.ts (2)
embedText1536(10-42)vectorToPgText(48-51)
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx (6)
apps/pro-web/lib/queries/use-document-content.ts (1)
useUpdateDocumentContent(168-407)apps/pro-web/lib/section-tree-utils.ts (1)
buildSectionTree(20-33)apps/pro-web/lib/markdown-utils.ts (2)
combineMarkdownSections(365-377)replaceSectionContent(379-405)apps/pro-web/lib/memoized-markdown-components.tsx (2)
p(62-68)memoizedMarkdownComponents(54-229)apps/pro-web/lib/utils.ts (1)
cn(9-11)apps/pro-web/components/shared/markdown.tsx (1)
MemoizedReactMarkdown(21-40)
🪛 ast-grep (0.40.5)
apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts
[warning] 130-132: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
\\$${safeVarName}" of type "[^"]+" used in position expecting type "([^"]+)",
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🔇 Additional comments (25)
apps/pro-web/components/routes/chat/chat-list/index.tsx (5)
21-22: Good type alias for message consistency.Using
MessagePair['userMessage']as the canonical message shape ensures type alignment with the pairing logic.
35-44: Good use of dynamic import with SSR disabled.The DocumentThreadOutline component is appropriately code-split with a loading skeleton, consistent with the existing MessagePairs pattern.
46-74: Interface properly documented with compatibility notes.The JSDoc explaining the dual
outlineTree/outlineprops for call-site compatibility is helpful. Consider deprecatingoutlinein favor ofoutlineTreein a future cleanup.
134-145: Good use of isEqual for referential stability.The
isEqualcheck beforesetPairsprevents unnecessary re-renders when the computed pairs are structurally identical. This is a sound optimization pattern.
248-274: Clean conditional rendering with maintained parity.The ternary structure clearly separates the outline and fallback paths. Both renderers receive consistent props which ensures behavioral parity.
apps/pro-web/components/routes/workspace/document-tab/workspace-document-text-editor.tsx (6)
23-72: LGTM!Component declaration follows guidelines with the
functionkeyword, and the memoized derivations foractiveSectionObjandactiveSectionContenthave correct dependencies.
99-130: LGTM!The sync effects properly guard against overwriting user edits during remote updates by checking
isUserTypingRef.currentand comparing with last-synced values.
132-164: LGTM!Good use of
requestAnimationFramefor DOM operations per coding guidelines. The coordinate conversion from absolute to section-relative positions is correctly implemented with clamping.
202-243: LGTM!The deletion handler robustly matches sections by id or title, provides user confirmation, and properly clears the active section when the deleted section was selected.
245-298: LGTM!The debounced save handlers correctly clear previous timers before scheduling new ones, and use appropriate utilities (
replaceSectionContentfor section edits, direct content for source edits).
452-501: LGTM!Source view mode correctly handles preview and edit states, with debounced saves and absolute selection range tracking.
CLAUDE.md (2)
80-80: Improved section naming.The rename from "Common Development Commands" to "Setup Commands" is more accurate and helps readers quickly identify installation and initialization procedures.
139-141: Documentation versions are accurate and match dependencies.The documented versions (Next.js 15, React 19, TypeScript 5.8, Bun 1.x) correctly reflect the actual versions in package.json:
apps/pro-webandapps/web: Next.js 15.5.7, React 19.2.1, TypeScript ^5.8.2- Root
package.json: Bun 1.x specified in enginesNo changes needed to the documentation.
README.md (1)
55-58: Bun dev command aligns with repo tooling.Nice update to keep the frontend instructions consistent with Bun usage.
AGENTS.md (3)
55-78: Implementation guidelines are clear and actionable.This section fits well with the environment-awareness framing and is easy to apply.
116-231: Monorepo architecture and workflow guidance reads well.The structure, tech stack, and data-flow notes are concise and helpful.
287-291: TypeScript “avoid any” reminder is a good touch.Keeps the conventions section crisp and aligned with strict typing.
apps/pro-web/app/actions/chat-memory/embedding.ts (2)
44-46: Hash helper is straightforward.Clear and deterministic implementation.
48-50: Pgvector literal formatting looks correct.Exact no-space serialization is the right shape for pgvector.
apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts (2)
5-32: Scope and argument typing looks solid.The public types are clear and narrow the scope inputs well.
34-45: Normalization helpers are clean and readable.The scope/type guards are straightforward and safe.
apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts (4)
6-30: Row normalization is robust.Handles camelCase/snake_case inputs without resorting to
any.
71-121: Token budgeting logic looks solid.Clear defaults, deterministic estimation, and sensible pruning behavior.
123-145: Regex escaping + arg disabling helpers are safe.Good guardrails for schema-variance error handling.
327-513: Primary + fallback retrieval flow is clear.The scoped search + doc-root fallback behavior is easy to follow and preserves legacy compatibility.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| export async function embedText1536(input: string): Promise<number[]> { | ||
| const apiKey = process.env.OPENAI_API_KEY | ||
| const text = (input ?? '').trim() | ||
|
|
||
| if (!apiKey) throw new Error('Missing OPENAI_API_KEY') | ||
| if (!text) return [] | ||
|
|
||
| const res = await fetch('https://api.openai.com/v1/embeddings', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'content-type': 'application/json', | ||
| authorization: `Bearer ${apiKey}`, | ||
| }, | ||
| body: JSON.stringify({ | ||
| model: EMBEDDING_MODEL, | ||
| input: text, | ||
| encoding_format: 'float', | ||
| }), | ||
| // ? We do not store cache for embeddings... that wouldn't be safe and would lead to stale embeddings | ||
| cache: 'no-store', | ||
| }) | ||
|
|
||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => '') | ||
| throw new Error( | ||
| `OpenAI embeddings failed: ${res.status} — ${res.statusText} — ${body}`, | ||
| ) | ||
| } | ||
|
|
||
| const json = (await res.json()) as OpenAIEmbeddingsResponse | ||
| const vec = json.data?.[0]?.embedding ?? [] | ||
| return vec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find apps/pro-web/app/actions -name "embedding.ts" -o -name "*embedding*"Repository: bitcashorg/masterbots
Length of output: 114
🏁 Script executed:
git ls-files | grep -i "embedding"Repository: bitcashorg/masterbots
Length of output: 114
🏁 Script executed:
find apps -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "AbortController" | head -20Repository: bitcashorg/masterbots
Length of output: 264
🏁 Script executed:
cat -n apps/pro-web/app/actions/chat-memory/embedding.tsRepository: bitcashorg/masterbots
Length of output: 1792
🏁 Script executed:
rg "AbortController|setTimeout.*abort" apps --type ts --type tsx -A 3 -B 1Repository: bitcashorg/masterbots
Length of output: 92
🏁 Script executed:
rg "AbortController|setTimeout.*abort" apps -A 3 -B 1Repository: bitcashorg/masterbots
Length of output: 3040
🏁 Script executed:
rg "await fetch" apps/pro-web/app/actions -A 5 | head -80Repository: bitcashorg/masterbots
Length of output: 5943
🏁 Script executed:
rg "signal.*controller" apps -A 2 -B 2Repository: bitcashorg/masterbots
Length of output: 18794
🏁 Script executed:
rg "setTimeout.*abort|try.*finally.*clearTimeout" apps/pro-web/app/actions -A 5 -B 2Repository: bitcashorg/masterbots
Length of output: 47
🏁 Script executed:
cat -n apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts | head -50Repository: bitcashorg/masterbots
Length of output: 1715
🏁 Script executed:
cat -n apps/pro-web/app/actions/chat-memory/ingest-assistant-answer.ts | sed -n '50,120p'Repository: bitcashorg/masterbots
Length of output: 2602
🏁 Script executed:
rg "fetch.*openai|fetch.*embeddings" apps -A 10Repository: bitcashorg/masterbots
Length of output: 897
Add a timeout to the OpenAI embeddings request.
The external fetch call can hang indefinitely and block the server action. Add an AbortController timeout to prevent resource exhaustion.
🛠️ Suggested fix
export async function embedText1536(input: string): Promise<number[]> {
const apiKey = process.env.OPENAI_API_KEY
const text = (input ?? '').trim()
if (!apiKey) throw new Error('Missing OPENAI_API_KEY')
if (!text) return []
+
+ const controller = new AbortController()
+ const timeoutId = setTimeout(() => controller.abort(), 15_000)
- const res = await fetch('https://api.openai.com/v1/embeddings', {
- method: 'POST',
- headers: {
- 'content-type': 'application/json',
- authorization: `Bearer ${apiKey}`,
- },
- body: JSON.stringify({
- model: EMBEDDING_MODEL,
- input: text,
- encoding_format: 'float',
- }),
- // ? We do not store cache for embeddings... that wouldn't be safe and would lead to stale embeddings
- cache: 'no-store',
- })
+ let res: Response
+ try {
+ res = await fetch('https://api.openai.com/v1/embeddings', {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/json',
+ authorization: `Bearer ${apiKey}`,
+ },
+ body: JSON.stringify({
+ model: EMBEDDING_MODEL,
+ input: text,
+ encoding_format: 'float',
+ }),
+ // ? We do not store cache for embeddings... that wouldn't be safe and would lead to stale embeddings
+ cache: 'no-store',
+ signal: controller.signal,
+ })
+ } finally {
+ clearTimeout(timeoutId)
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function embedText1536(input: string): Promise<number[]> { | |
| const apiKey = process.env.OPENAI_API_KEY | |
| const text = (input ?? '').trim() | |
| if (!apiKey) throw new Error('Missing OPENAI_API_KEY') | |
| if (!text) return [] | |
| const res = await fetch('https://api.openai.com/v1/embeddings', { | |
| method: 'POST', | |
| headers: { | |
| 'content-type': 'application/json', | |
| authorization: `Bearer ${apiKey}`, | |
| }, | |
| body: JSON.stringify({ | |
| model: EMBEDDING_MODEL, | |
| input: text, | |
| encoding_format: 'float', | |
| }), | |
| // ? We do not store cache for embeddings... that wouldn't be safe and would lead to stale embeddings | |
| cache: 'no-store', | |
| }) | |
| if (!res.ok) { | |
| const body = await res.text().catch(() => '') | |
| throw new Error( | |
| `OpenAI embeddings failed: ${res.status} — ${res.statusText} — ${body}`, | |
| ) | |
| } | |
| const json = (await res.json()) as OpenAIEmbeddingsResponse | |
| const vec = json.data?.[0]?.embedding ?? [] | |
| return vec | |
| export async function embedText1536(input: string): Promise<number[]> { | |
| const apiKey = process.env.OPENAI_API_KEY | |
| const text = (input ?? '').trim() | |
| if (!apiKey) throw new Error('Missing OPENAI_API_KEY') | |
| if (!text) return [] | |
| const controller = new AbortController() | |
| const timeoutId = setTimeout(() => controller.abort(), 15_000) | |
| let res: Response | |
| try { | |
| res = await fetch('https://api.openai.com/v1/embeddings', { | |
| method: 'POST', | |
| headers: { | |
| 'content-type': 'application/json', | |
| authorization: `Bearer ${apiKey}`, | |
| }, | |
| body: JSON.stringify({ | |
| model: EMBEDDING_MODEL, | |
| input: text, | |
| encoding_format: 'float', | |
| }), | |
| // ? We do not store cache for embeddings... that wouldn't be safe and would lead to stale embeddings | |
| cache: 'no-store', | |
| signal: controller.signal, | |
| }) | |
| } finally { | |
| clearTimeout(timeoutId) | |
| } | |
| if (!res.ok) { | |
| const body = await res.text().catch(() => '') | |
| throw new Error( | |
| `OpenAI embeddings failed: ${res.status} — ${res.statusText} — ${body}`, | |
| ) | |
| } | |
| const json = (await res.json()) as OpenAIEmbeddingsResponse | |
| const vec = json.data?.[0]?.embedding ?? [] | |
| return vec |
🤖 Prompt for AI Agents
In `@apps/pro-web/app/actions/chat-memory/embedding.ts` around lines 10 - 41,
embedText1536 can hang because the fetch to OpenAI has no timeout; wrap the
fetch in an AbortController with a configurable timeout (e.g., 10s), pass
controller.signal into the fetch call, start a setTimeout that calls
controller.abort() after the timeout, and clear the timer after a successful
response (and on error) so the timer doesn't leak; ensure you catch an abort
error and rethrow a meaningful Error (e.g., "OpenAI embeddings request timed
out") before continuing to parse the response.
| function makeSearchQuery(opts: { | ||
| embeddingVarType: string | ||
| scopeTypeVarType: string | ||
| scopeIdVarType: string | ||
| docNodeIdVarType: string | ||
| includeScope: boolean | ||
| includeDocNode: boolean | ||
| includeExclude: boolean | ||
| }) { | ||
| const varDefs: string[] = [ | ||
| '$threadId: uuid!', | ||
| `$queryEmbedding: ${opts.embeddingVarType}!`, | ||
| '$k: Int!', | ||
| '$minCosine: float8!', | ||
| ] | ||
| if (opts.includeExclude) varDefs.push('$excludeMessageId: uuid') | ||
| if (opts.includeScope) { | ||
| varDefs.push(`$scopeType: ${opts.scopeTypeVarType}`) | ||
| varDefs.push(`$scopeId: ${opts.scopeIdVarType}`) | ||
| } | ||
| if (opts.includeDocNode) { | ||
| varDefs.push(`$docNodeId: ${opts.docNodeIdVarType}`) | ||
| } | ||
|
|
||
| const args: string[] = [ | ||
| 'thread_id: $threadId', | ||
| 'query_embedding: $queryEmbedding', | ||
| 'k: $k', | ||
| 'min_cosine: $minCosine', | ||
| ] | ||
| if (opts.includeExclude) args.push('exclude_message_id: $excludeMessageId') | ||
| if (opts.includeScope) { | ||
| args.push('scope_type: $scopeType') | ||
| args.push('scope_id: $scopeId') | ||
| } | ||
| if (opts.includeDocNode) { | ||
| args.push('doc_node_id: $docNodeId') | ||
| } | ||
|
|
||
| return /* GraphQL */ ` | ||
| query SearchAssistantAnswers(${varDefs.join(', ')}) { | ||
| search_assistant_answers_gql(args: { ${args.join(', ')} }) { | ||
| message_id | ||
| turn_index | ||
| content | ||
| cosine | ||
| created_at | ||
| } | ||
| } | ||
| ` | ||
| } | ||
|
|
||
| function mergeRowsPreferBestCosine(rows: SearchRow[]): SearchRow[] { | ||
| if (!rows.length) return rows | ||
| const byId = new Map<string, SearchRow>() | ||
| for (const r of rows) { | ||
| const prev = byId.get(r.message_id) | ||
| if (!prev) { | ||
| byId.set(r.message_id, r) | ||
| continue | ||
| } | ||
| // Prefer higher cosine; tie-breaker: higher turn_index | ||
| const better = | ||
| (r.cosine ?? 0) > (prev.cosine ?? 0) || | ||
| ((r.cosine ?? 0) === (prev.cosine ?? 0) && | ||
| (r.turn_index ?? 0) > (prev.turn_index ?? 0)) | ||
| if (better) byId.set(r.message_id, r) | ||
| } | ||
| return [...byId.values()] | ||
| } | ||
|
|
||
| async function searchAssistantAnswers(params: { | ||
| threadId: string | ||
| queryEmbeddingText: string | ||
| k: number | ||
| minCosine: number | ||
| excludeMessageId?: string | null | ||
| scopeType?: string | null | ||
| scopeId?: string | null | ||
| docNodeId?: string | null | ||
| }): Promise<SearchRow[]> { | ||
| /** | ||
| * We try a “modern” query (scope args + doc_node_id included) and gracefully fall back if: | ||
| * - the schema doesn’t support doc_node_id arg yet | ||
| * - the schema doesn’t support scope/exclude args | ||
| * - enum/scalar types differ | ||
| */ | ||
| let includeScope = params.scopeType != null | ||
| let includeExclude = params.excludeMessageId != null | ||
| let includeDocNode = params.docNodeId != null | ||
|
|
||
| let embeddingVarType = 'vector' | ||
| let scopeTypeVarType = 'String' | ||
| let scopeIdVarType = 'String' | ||
| let docNodeIdVarType = 'String' | ||
|
|
||
| for (let attempt = 0; attempt < 6; attempt++) { | ||
| const query = makeSearchQuery({ | ||
| embeddingVarType, | ||
| scopeTypeVarType, | ||
| scopeIdVarType, | ||
| docNodeIdVarType, | ||
| includeScope, | ||
| includeDocNode, | ||
| includeExclude, | ||
| }) | ||
|
|
||
| try { | ||
| type Data = { search_assistant_answers_gql: SearchRow[] } | ||
| const data = await hasuraGql<Data>(query, { | ||
| threadId: params.threadId, | ||
| queryEmbedding: params.queryEmbeddingText, | ||
| k: params.k, | ||
| minCosine: params.minCosine, | ||
| ...(includeExclude | ||
| ? { excludeMessageId: params.excludeMessageId } | ||
| : {}), | ||
| ...(includeScope | ||
| ? { scopeType: params.scopeType, scopeId: params.scopeId ?? null } | ||
| : {}), | ||
| ...(includeDocNode ? { docNodeId: params.docNodeId } : {}), | ||
| }) | ||
| return data.search_assistant_answers_gql ?? [] | ||
| } catch (e) { | ||
| const msg = e instanceof Error ? e.message : String(e) | ||
|
|
||
| // If doc_node_id arg isn’t in the schema, drop it and retry. | ||
| if (includeDocNode && shouldDisableArg(msg, 'doc_node_id')) { | ||
| includeDocNode = false | ||
| continue | ||
| } | ||
|
|
||
| // If scope/exclude args aren’t in the schema, drop them and retry. | ||
| if (includeScope && shouldDisableArg(msg, 'scope_type')) { | ||
| includeScope = false | ||
| continue | ||
| } | ||
| if (includeExclude && shouldDisableArg(msg, 'exclude_message_id')) { | ||
| includeExclude = false | ||
| continue | ||
| } | ||
|
|
||
| // If GraphQL expects different types for vars, adjust and retry. | ||
| const expScopeType = includeScope | ||
| ? tryExtractExpectedType(msg, 'scopeType') | ||
| : null | ||
| if (expScopeType) { | ||
| scopeTypeVarType = expScopeType | ||
| continue | ||
| } | ||
|
|
||
| const expScopeId = includeScope | ||
| ? tryExtractExpectedType(msg, 'scopeId') | ||
| : null | ||
| if (expScopeId) { | ||
| scopeIdVarType = expScopeId | ||
| continue | ||
| } | ||
|
|
||
| const expDocNode = includeDocNode | ||
| ? tryExtractExpectedType(msg, 'docNodeId') | ||
| : null | ||
| if (expDocNode) { | ||
| docNodeIdVarType = expDocNode | ||
| continue | ||
| } | ||
|
|
||
| const expEmbed = tryExtractExpectedType(msg, 'queryEmbedding') | ||
| if (expEmbed) { | ||
| embeddingVarType = expEmbed | ||
| continue | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
wc -l apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.tsRepository: bitcashorg/masterbots
Length of output: 143
🏁 Script executed:
cat -n apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.tsRepository: bitcashorg/masterbots
Length of output: 18622
Add adaptive type handling for minCosine to match the pattern used for queryEmbedding.
Line 160 hardcodes minCosine to float8, but if the Hasura schema expects a different numeric type (e.g., float4), the query fails without fallback. Unlike queryEmbedding (which adapts at lines 314–318), minCosine has no error-driven type adjustment. Implement the same pattern for consistency.
🛠️ Suggested fix
function makeSearchQuery(opts: {
embeddingVarType: string
+ minCosineVarType: string
scopeTypeVarType: string
scopeIdVarType: string
docNodeIdVarType: string
includeScope: boolean
includeDocNode: boolean
includeExclude: boolean
}) {
const varDefs: string[] = [
'$threadId: uuid!',
`$queryEmbedding: ${opts.embeddingVarType}!`,
'$k: Int!',
- '$minCosine: float8!',
+ `$minCosine: ${opts.minCosineVarType}!`,
] let embeddingVarType = 'vector'
+ let minCosineVarType = 'float8'
let scopeTypeVarType = 'String'
let scopeIdVarType = 'String'
let docNodeIdVarType = 'String'
for (let attempt = 0; attempt < 6; attempt++) {
const query = makeSearchQuery({
embeddingVarType,
+ minCosineVarType,
scopeTypeVarType,
scopeIdVarType,
docNodeIdVarType,
includeScope,
includeDocNode,
includeExclude,
}) const expEmbed = tryExtractExpectedType(msg, 'queryEmbedding')
if (expEmbed) {
embeddingVarType = expEmbed
continue
}
+
+ const expMinCosine = tryExtractExpectedType(msg, 'minCosine')
+ if (expMinCosine) {
+ minCosineVarType = expMinCosine
+ continue
+ }🤖 Prompt for AI Agents
In `@apps/pro-web/app/actions/chat-memory/retrieve-assistant-answer-memories.ts`
around lines 147 - 318, The query currently hardcodes minCosine's GraphQL var
type to "float8" in makeSearchQuery and never adapts on GraphQL type errors;
update makeSearchQuery and searchAssistantAnswers to treat minCosine like
queryEmbedding: add a minCosineVarType parameter (default "float8") to
makeSearchQuery and use it in varDefs (`$minCosine: ${minCosineVarType}!`),
ensure the hasuraGql call still passes minCosine value, and inside the retry
loop call tryExtractExpectedType(msg, 'minCosine') (e.g., expMinCosine) and if
present assign minCosineVarType = expMinCosine and continue so the code retries
with the corrected numeric type; reference functions/identifiers:
makeSearchQuery, searchAssistantAnswers, minCosine, tryExtractExpectedType.
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.