update dashboard ui (#508)#2
Conversation
📝 WalkthroughWalkthroughThis PR encompasses a comprehensive UI refinement across the dashboard, implementing consistent styling and structural updates. Changes include: elevating z-index values from 10 to 50 across multiple navigation and layout components for improved stacking hierarchy; updating typography from text-base to text-lg for section headers; removing backdrop blur effects from several UI elements; adjusting scrollbar visibility in chat containers; renaming the AutomationsList component to AutomationsTable with corresponding prop type updates; removing the reset prop from error boundary components and consolidating reset logic; and refactoring the pagination component to use page-based navigation instead of page-size selection with updated query parameter handling. Minor formatting and indentation changes are also present across several files. Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Areas requiring extra attention:
Possibly related PRs
Comment |
There was a problem hiding this comment.
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 (9)
services/platform/app/(app)/dashboard/[id]/conversations/conversations-navigation.tsx (1)
75-75: Update z-index to align with PR changes—z-10 should be z-50.Per the PR objectives, z-index values are being elevated from 10 to 50 across navigation and layout components for improved stacking hierarchy. This navigation element should be updated to
z-50for consistency with related changes (e.g., knowledge-navigation, approvals-navigation, conversation-panel header).Apply this diff:
- <nav className="sticky top-0 z-10 border-b border-border px-4 py-3 h-12 flex items-center gap-4"> + <nav className="sticky top-0 z-50 border-b border-border px-4 py-3 h-12 flex items-center gap-4">services/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsx (1)
82-84: Replace hardcoded colors with semantic color tokens.The icon colors use hardcoded values (
text-blue-500,text-purple-500,text-green-500), which violates the coding guidelines requiring semantic design system colors. This makes theming and maintenance harder and breaks visual consistency.Apply this diff to use semantic colors:
const getFileIcon = (type: AttachedFile['type'], size = 'size-4') => { switch (type) { case 'image': - return <ImageIcon className={`${size} text-blue-500`} />; + return <ImageIcon className={`${size} text-primary`} />; case 'video': - return <VideoIcon className={`${size} text-purple-500`} />; + return <VideoIcon className={`${size} text-muted-foreground`} />; case 'audio': - return <MusicIcon className={`${size} text-green-500`} />; + return <MusicIcon className={`${size} text-muted-foreground`} />; case 'document': return <FileIcon className={`${size} text-muted-foreground`} />; default:As per coding guidelines: "NEVER use hardcoded colors like
text-gray-500; ALWAYS use design system semantic colors."services/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsx (1)
262-262: Replace hardcoded gray text color with design-system semantic tokenThe loading state uses
text-gray-500, which violates the guideline to avoid hardcoded colors and to use semantic tokens (text-muted-foreground, etc.).- <div className="mt-4 text-gray-500 text-center">Loading PDF...</div> + <div className="mt-4 text-center text-muted-foreground">Loading PDF...</div>You may also want to audit nearby
white/10usages and align them with the design system’s semantic color tokens if available. As per coding guidelines, ALWAYS use semantic colors over raw palette utilities.services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx (1)
91-91: Consider converting pixel values to rem units for consistency.While not part of the current changes, several measurements use pixel values that should be converted to rem units per coding guidelines. This would align with the PR's UI refinement objectives.
Suggested conversions:
- Line 91:
w-[376px]→w-[23.5rem]- Lines 120, 141, 151, 162:
w-[90px]→w-[5.625rem]- Line 179:
size-[60px]→size-[3.75rem]Example refactor for label widths
-<div className="w-[90px] text-xs text-muted-foreground"> +<div className="w-[5.625rem] text-xs text-muted-foreground">Also applies to: 120-120, 141-141, 151-151, 162-162, 179-179
services/platform/components/ui/pagination.tsx (1)
82-111: Unify navigation logic and consider loading state for page selection
handlePageSelectcorrectly validates the target page and mirrors the prev/next routing behavior, but there are two maintainability/UX issues:
- The URL param construction and
queryStringmerging logic is now duplicated betweenhandlePageChangeandhandlePageSelect. Extracting a shared helper likenavigateToPage(page: number)would reduce drift risk and make future changes (e.g., handling more params) safer.- Unlike
handlePageChange, this path never updatesloadingStates, so users won't see spinners/disabled arrows when changing pages via the Select. For a consistent experience, consider setting a genericloadingflag or reusing the existingloadingStatesbeforestartTransition.Both are non-blocking but would make this component more robust and consistent.
services/platform/app/(app)/dashboard/[id]/error.tsx (1)
6-17:resetprop is now unused and no longer affects retry behavior
ErrorPropsstill definesreset, andDashboardErrorstill receives it, but it’s not forwarded anywhere and retry is now handled viawindow.location.reload()insideErrorDisplay. This is a behavior change from using Next.js’sreset()callback to a full page reload.If this change is intentional, consider either:
- Removing
resetfromErrorProps/ function signature or renaming to_resetto avoid unused-parameter lint warnings, or- Wiring
resetback into the retry flow (e.g., via a callback prop onDashboardErrorBoundary) if you want the lighter-weight segment reset behavior.services/platform/components/error-boundary.tsx (1)
51-76: Align error UI Tailwind classes with design system tokens and semantic spacingIn
ErrorDisplaythe current classes:
py-[10rem](Line 51) uses an arbitrary spacing value.bg-red-100andtext-red-600(Lines 58–62) hardcode color tokens instead of using semantic design system colors.Per the Tailwind / design guidelines:
- Prefer semantic spacing utilities, e.g.
py-40instead ofpy-[10rem](10rem is Tailwind’s40scale by default).- Prefer semantic color tokens (e.g.,
bg-destructive/10,text-destructive) rather than hardcoded palette values for destructive/error states.Updating these keeps the error UI consistent with the rest of the design system. As per coding guidelines.
Also applies to: 58-62, 88-103
services/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsx (2)
291-301: Header z-index and styling look correct; optional note on blurThe sticky header with
z-50, semantic colors, and border/shadow is consistent and should layer cleanly above the scrollable content and date headers.If the broader design direction is moving away from blur on structural chrome, you may want to revisit
bg-background/50 backdrop-blur-smfor consistency with other updated headers; otherwise this is fine as-is.
331-359: Bottom sticky composer/status bar behavior is coherentThe bottom
stickycontainer withz-50 bg-backgroundcleanly keeps the composer (or closed/spam message) anchored while scrolling and uses semantic colors and spacing. Given the DOM order, it shouldn’t obscure the last messages; they will scroll just above the composer.If you anticipate more global overlays, consider centralizing z-index tiers (e.g., via a small constants map or Tailwind plugin) so header, composer, and future modals/tooltips remain consistently stacked, but that’s not required for this change.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro (Legacy)
📒 Files selected for processing (25)
services/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsx(1 hunks)services/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsx(2 hunks)services/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsx(1 hunks)services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx(1 hunks)services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsx(1 hunks)services/platform/app/(app)/dashboard/[id]/approvals/approvals-navigation.tsx(2 hunks)services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx(2 hunks)services/platform/app/(app)/dashboard/[id]/approvals/layout.tsx(1 hunks)services/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsx(2 hunks)services/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsx(3 hunks)services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsx(3 hunks)services/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsx(4 hunks)services/platform/app/(app)/dashboard/[id]/automations/page.tsx(2 hunks)services/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsx(2 hunks)services/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsx(3 hunks)services/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsx(4 hunks)services/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsx(2 hunks)services/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsx(1 hunks)services/platform/app/(app)/dashboard/[id]/conversations/components/message.tsx(1 hunks)services/platform/app/(app)/dashboard/[id]/conversations/conversations-navigation.tsx(1 hunks)services/platform/app/(app)/dashboard/[id]/error.tsx(1 hunks)services/platform/app/(app)/dashboard/[id]/settings/layout.tsx(1 hunks)services/platform/app/(app)/error.tsx(0 hunks)services/platform/components/error-boundary.tsx(1 hunks)services/platform/components/ui/pagination.tsx(3 hunks)
💤 Files with no reviewable changes (1)
- services/platform/app/(app)/error.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/figma_rules.mdc)
**/*.{tsx,jsx}: Avoid specifyingfont-['Inter:Regular',_sans-serif]as it should be the default
Only specify font-family when using non-default fonts likefont-['Inter:Medium',_sans-serif]
Ensure font-family matches font-weight (Inter:Regular with font-normal, Inter:Medium with font-medium)
Useleading-normalinstead ofleading-[normal]in Tailwind classes
Use standard font size classes instead of arbitrary values:text-[12px]→text-xs,text-[14px]→text-sm,text-[16px]→text-base,text-[18px]→text-lg,text-[20px]→text-xl,text-[24px]→text-2xl
Use semantic spacing classes:p-[4px]→p-1,p-[8px]→p-2,m-[4px]→m-1,m-[8px]→m-2
Convert pixel values to rem using the 16px base for width and height measurements:w-[278px]→w-[17.375rem],h-[48px]→h-[3rem],min-w-[120px]→min-w-[7.5rem],max-w-[400px]→max-w-[25rem]
NEVER use hardcoded colors liketext-gray-500,bg-gray-100,border-gray-200; ALWAYS use design system semantic colors:text-foregroundfor primary text,text-muted-foregroundfor secondary text and icons,bg-backgroundfor main backgrounds,bg-mutedfor subtle backgrounds and hover states,border-borderfor borders
ALWAYS use the Table component instead of custom flex layouts; useTable,TableHeader,TableBody,TableRow,TableHead,TableCellcomponents with proper column widths using rem units and semantic colors
Files:
services/platform/app/(app)/dashboard/[id]/approvals/approvals-navigation.tsxservices/platform/app/(app)/dashboard/[id]/approvals/layout.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsxservices/platform/components/ui/pagination.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsxservices/platform/app/(app)/dashboard/[id]/automations/page.tsxservices/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsxservices/platform/app/(app)/dashboard/[id]/error.tsxservices/platform/components/error-boundary.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/message.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsxservices/platform/app/(app)/dashboard/[id]/settings/layout.tsxservices/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsxservices/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsxservices/platform/app/(app)/dashboard/[id]/conversations/conversations-navigation.tsx
**/*
📄 CodeRabbit inference engine (.cursor/rules/workspace_rules.mdc)
Use English only for ALL user-facing content including UI components, labels, buttons, dialogs, forms, toast messages, error messages, success messages, comments, documentation, README files, variable names, function names, and type names
Files:
services/platform/app/(app)/dashboard/[id]/approvals/approvals-navigation.tsxservices/platform/app/(app)/dashboard/[id]/approvals/layout.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsxservices/platform/components/ui/pagination.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsxservices/platform/app/(app)/dashboard/[id]/automations/page.tsxservices/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsxservices/platform/app/(app)/dashboard/[id]/error.tsxservices/platform/components/error-boundary.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/message.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsxservices/platform/app/(app)/dashboard/[id]/settings/layout.tsxservices/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsxservices/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsxservices/platform/app/(app)/dashboard/[id]/conversations/conversations-navigation.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/workspace_rules.mdc)
**/*.{ts,tsx,js,jsx}: Use Vercel AI SDK with OpenAI - import from 'ai' and '@ai-sdk/openai', never use raw OpenAI SDK or OpenRouter
Never hallucinate API keys - always use environment variables and existing .env configuration
Use camelCase for function names (e.g.,getUserData)
Use SCREAMING_SNAKE_CASE for constants (e.g.,API_BASE_URL,MAX_RETRIES)
Use feature flags with enums (TypeScript) or const objects (JavaScript) with UPPERCASE_WITH_UNDERSCORE naming
Implement error handling with try-catch pattern: check for result.error and display descriptive toast messages using result.error as title
Files:
services/platform/app/(app)/dashboard/[id]/approvals/approvals-navigation.tsxservices/platform/app/(app)/dashboard/[id]/approvals/layout.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsxservices/platform/components/ui/pagination.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsxservices/platform/app/(app)/dashboard/[id]/automations/page.tsxservices/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsxservices/platform/app/(app)/dashboard/[id]/error.tsxservices/platform/components/error-boundary.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/message.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsxservices/platform/app/(app)/dashboard/[id]/settings/layout.tsxservices/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsxservices/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsxservices/platform/app/(app)/dashboard/[id]/conversations/conversations-navigation.tsx
**/app/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/workspace_rules.mdc)
In Next.js App Router, use
page.tsxas server components by default; use'use client'only for interactions and state
Files:
services/platform/app/(app)/dashboard/[id]/approvals/approvals-navigation.tsxservices/platform/app/(app)/dashboard/[id]/approvals/layout.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsxservices/platform/app/(app)/dashboard/[id]/automations/page.tsxservices/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsxservices/platform/app/(app)/dashboard/[id]/error.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/message.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsxservices/platform/app/(app)/dashboard/[id]/settings/layout.tsxservices/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsxservices/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsxservices/platform/app/(app)/dashboard/[id]/conversations/conversations-navigation.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/workspace_rules.mdc)
**/*.{ts,tsx}: Use kebab-case for file names (e.g.,user-profile.tsx)
Use PascalCase for component names (e.g.,UserProfile)
Use descriptive messages as toast title (never generic 'Error'), with optional description for additional context only
Follow component structure: 'use client' directive, imports, interface Props, hooks, effects, event handlers, then render
Prioritize data fetching methods in order: Server Actions (preferred), Route Handlers (when needed), Client-side (minimal use)
Use React.memo for expensive components to optimize performance
Use Next.js Image component for all images instead of native img tags
Use dynamic imports for code splitting
Files:
services/platform/app/(app)/dashboard/[id]/approvals/approvals-navigation.tsxservices/platform/app/(app)/dashboard/[id]/approvals/layout.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsxservices/platform/components/ui/pagination.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsxservices/platform/app/(app)/dashboard/[id]/automations/page.tsxservices/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsxservices/platform/app/(app)/dashboard/[id]/error.tsxservices/platform/components/error-boundary.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/message.tsxservices/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsxservices/platform/app/(app)/dashboard/[id]/settings/layout.tsxservices/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsxservices/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsxservices/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsxservices/platform/app/(app)/dashboard/[id]/conversations/conversations-navigation.tsx
🧠 Learnings (29)
📚 Learning: 2025-10-01T17:12:39.508Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-10-01T17:12:39.508Z
Learning: Applies to **/*.tsx : Use bg-muted for subtle backgrounds and hover states instead of bg-gray-100 or bg-gray-50
Applied to files:
services/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsx
📚 Learning: 2025-12-02T08:13:34.246Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:34.246Z
Learning: Applies to **/*.{tsx,jsx} : Use semantic spacing classes: `p-[4px]` → `p-1`, `p-[8px]` → `p-2`, `m-[4px]` → `m-1`, `m-[8px]` → `m-2`
Applied to files:
services/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsx
📚 Learning: 2025-11-25T04:37:44.394Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-11-25T04:37:44.394Z
Learning: Applies to **/*.{tsx,jsx} : Use semantic spacing classes when available: `p-1` for 4px, `p-2` for 8px, `m-1` for 4px, `m-2` for 8px
Applied to files:
services/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsx
📚 Learning: 2025-10-01T17:12:39.508Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-10-01T17:12:39.508Z
Learning: Applies to **/*.tsx : Replace text-[18px] with text-lg
Applied to files:
services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
📚 Learning: 2025-10-01T17:12:39.508Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-10-01T17:12:39.508Z
Learning: Applies to **/*.tsx : Replace text-[20px] with text-xl
Applied to files:
services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
📚 Learning: 2025-10-01T17:12:39.508Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-10-01T17:12:39.508Z
Learning: Applies to **/*.tsx : Replace text-[24px] with text-2xl
Applied to files:
services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
📚 Learning: 2025-10-01T17:12:39.508Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-10-01T17:12:39.508Z
Learning: Applies to **/*.tsx : Replace text-[14px] with text-sm
Applied to files:
services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
📚 Learning: 2025-12-02T08:13:34.246Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:34.246Z
Learning: Applies to **/*.{tsx,jsx} : Use standard font size classes instead of arbitrary values: `text-[12px]` → `text-xs`, `text-[14px]` → `text-sm`, `text-[16px]` → `text-base`, `text-[18px]` → `text-lg`, `text-[20px]` → `text-xl`, `text-[24px]` → `text-2xl`
Applied to files:
services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
📚 Learning: 2025-10-01T17:12:39.508Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-10-01T17:12:39.508Z
Learning: Applies to **/*.tsx : Replace text-[16px] with text-base
Applied to files:
services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
📚 Learning: 2025-12-02T08:13:51.379Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/workspace_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:51.379Z
Learning: Applies to **/*.{ts,tsx} : Use PascalCase for component names (e.g., `UserProfile`)
Applied to files:
services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsx
📚 Learning: 2025-11-25T04:37:44.394Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-11-25T04:37:44.394Z
Learning: Applies to **/*.{tsx,jsx} : Use standard font size classes instead of arbitrary values: `text-xs` for 12px, `text-sm` for 14px, `text-base` for 16px, `text-lg` for 18px, `text-xl` for 20px, `text-2xl` for 24px
Applied to files:
services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsxservices/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
📚 Learning: 2025-11-25T04:37:44.394Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-11-25T04:37:44.394Z
Learning: Applies to **/*.{tsx,jsx} : ALWAYS use the Table component (`Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`) instead of custom flex layouts; apply proper column widths using rem units and use semantic colors for all text and backgrounds
Applied to files:
services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsxservices/platform/app/(app)/dashboard/[id]/automations/page.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
📚 Learning: 2025-12-02T08:13:34.246Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:34.246Z
Learning: Applies to **/*.{tsx,jsx} : ALWAYS use the Table component instead of custom flex layouts; use `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell` components with proper column widths using rem units and semantic colors
Applied to files:
services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsxservices/platform/app/(app)/dashboard/[id]/automations/page.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
📚 Learning: 2025-10-01T17:12:39.508Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-10-01T17:12:39.508Z
Learning: Applies to **/*.tsx : Use Table, TableHeader, TableBody, TableRow, TableHead, and TableCell components for tables
Applied to files:
services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsxservices/platform/app/(app)/dashboard/[id]/automations/page.tsxservices/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
📚 Learning: 2025-10-01T17:12:39.508Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-10-01T17:12:39.508Z
Learning: Applies to **/*.tsx : Always use the Table component instead of custom flex layouts for tabular data
Applied to files:
services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsxservices/platform/app/(app)/dashboard/[id]/automations/page.tsx
📚 Learning: 2025-10-03T11:34:20.628Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T11:34:20.628Z
Learning: Applies to convex/**/*.{ts,js} : Define paginated queries using paginationOptsValidator and .paginate(opts)
Applied to files:
services/platform/components/ui/pagination.tsx
📚 Learning: 2025-12-02T08:13:24.266Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:24.266Z
Learning: Applies to convex/**/*.{ts,tsx} : Use the `paginationOptsValidator` from `convex/server` for paginated queries with `numItems` and `cursor` parameters. Queries ending in `.paginate()` return objects with `page`, `isDone`, and `continueCursor` properties.
Applied to files:
services/platform/components/ui/pagination.tsx
📚 Learning: 2025-11-30T03:53:00.316Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.316Z
Learning: Applies to convex/**/*.ts : Define pagination using `paginationOptsValidator` with `numItems` and `cursor` properties, and use `.paginate()` on queries which returns objects with `page`, `isDone`, and `continueCursor` properties
Applied to files:
services/platform/components/ui/pagination.tsx
📚 Learning: 2025-12-02T08:13:51.379Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/workspace_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:51.379Z
Learning: Applies to **/app/**/*.tsx : In Next.js App Router, use `page.tsx` as server components by default; use `'use client'` only for interactions and state
Applied to files:
services/platform/components/ui/pagination.tsx
📚 Learning: 2025-07-19T15:30:00.886Z
Learnt from: CR
Repo: talecorp/poc PR: 0
File: .cursor/rules/core-rules.mdc:0-0
Timestamp: 2025-07-19T15:30:00.886Z
Learning: Applies to **/*.{ts,tsx} : Do not use Pages Router patterns in new code
Applied to files:
services/platform/components/ui/pagination.tsx
📚 Learning: 2025-11-30T12:29:39.745Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/workspace_rules.mdc:0-0
Timestamp: 2025-11-30T12:29:39.745Z
Learning: Applies to **/app/**/page.tsx : Use `page.tsx` for server components by default in Next.js App Router
Applied to files:
services/platform/components/ui/pagination.tsx
📚 Learning: 2025-07-03T08:43:49.346Z
Learnt from: CR
Repo: talecorp/poc PR: 0
File: .cursor/rules/next-best-practice.mdc:0-0
Timestamp: 2025-07-03T08:43:49.346Z
Learning: Applies to **/*.{tsx,jsx} : Use Suspense for loading states in components
Applied to files:
services/platform/app/(app)/dashboard/[id]/automations/page.tsx
📚 Learning: 2025-07-03T08:43:49.346Z
Learnt from: CR
Repo: talecorp/poc PR: 0
File: .cursor/rules/next-best-practice.mdc:0-0
Timestamp: 2025-07-03T08:43:49.346Z
Learning: Applies to app/**/*.tsx : Use server components for data fetching, database queries, and server-side logic
Applied to files:
services/platform/app/(app)/dashboard/[id]/automations/page.tsx
📚 Learning: 2025-10-01T17:12:39.508Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/figma_rules.mdc:0-0
Timestamp: 2025-10-01T17:12:39.508Z
Learning: Applies to **/*.tsx : Replace text-[12px] with text-xs
Applied to files:
services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
📚 Learning: 2025-07-03T08:43:49.346Z
Learnt from: CR
Repo: talecorp/poc PR: 0
File: .cursor/rules/next-best-practice.mdc:0-0
Timestamp: 2025-07-03T08:43:49.346Z
Learning: Applies to **/*.{tsx,jsx} : Implement error boundaries for error handling in components
Applied to files:
services/platform/app/(app)/dashboard/[id]/error.tsxservices/platform/components/error-boundary.tsx
📚 Learning: 2025-11-30T12:29:39.745Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/workspace_rules.mdc:0-0
Timestamp: 2025-11-30T12:29:39.745Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Implement error handling with try-catch blocks, check for result.error, and display descriptive toast messages with 'destructive' variant
Applied to files:
services/platform/components/error-boundary.tsx
📚 Learning: 2025-12-02T08:13:51.379Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/workspace_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:51.379Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Implement error handling with try-catch pattern: check for result.error and display descriptive toast messages using result.error as title
Applied to files:
services/platform/components/error-boundary.tsx
📚 Learning: 2025-12-02T08:13:51.379Z
Learnt from: CR
Repo: tale-project/tale PR: 0
File: .cursor/rules/workspace_rules.mdc:0-0
Timestamp: 2025-12-02T08:13:51.379Z
Learning: Applies to **/*.{ts,tsx} : Use descriptive messages as toast title (never generic 'Error'), with optional description for additional context only
Applied to files:
services/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsx
📚 Learning: 2025-11-30T12:29:39.745Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/workspace_rules.mdc:0-0
Timestamp: 2025-11-30T12:29:39.745Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use descriptive message as toast `title` (never generic 'Error'), with optional `description` only for additional context
Applied to files:
services/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsx
🧬 Code graph analysis (2)
services/platform/app/(app)/dashboard/[id]/automations/page.tsx (1)
services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsx (1)
AutomationsTable(44-306)
services/platform/app/(app)/dashboard/[id]/error.tsx (1)
services/platform/components/error-boundary.tsx (1)
DashboardErrorBoundary(173-186)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build services/platform
🔇 Additional comments (37)
services/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsx (1)
28-35: Formatting improvements look good.The indentation adjustments to the
SelectionStateunion type and thegetConversationsPagequery payload enhance readability without changing logic or behavior. The formatting is consistent and aligns with standard TypeScript conventions.Also applies to: 60-64
services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx (1)
74-74: LGTM! Typography update follows coding guidelines.The header text size update from
text-basetotext-lgaligns with the PR's objective to implement consistent styling across the dashboard. The change follows the coding guidelines for using standard font size classes.Based on learnings, standard font size classes like
text-lgshould be used instead of arbitrary values.services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsx (1)
219-219: LGTM! Consistent typography update.The header text size update from
text-basetotext-lgmaintains visual consistency with the Example messages section header and follows the coding guidelines for standard font size classes.Based on learnings, standard font size classes like
text-lgshould be used for consistent typography.services/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsx (1)
384-384: Container styling aligns with UI refinement goals.The removal of
backdrop-blur-smand use of semantic background color (bg-background) creates a cleaner visual hierarchy for the editor container. The styling is consistent with the PR's focus on improving z-index and layer clarity.services/platform/app/(app)/dashboard/[id]/settings/layout.tsx (1)
28-28: Z-index elevation toz-50is appropriate and aligns with the stacking hierarchy improvements.The backdrop blur effect should remain—it's consistently maintained across all similar navigation and layout components throughout the dashboard (knowledge, approvals, conversations, automations sections). There is no evidence of backdrop blur removal in this PR.
services/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsx (4)
276-288: Type assertion forstep.configis safe and scoped appropriatelyCasting
step.configto{ type?: string; [key: string]: unknown }is narrow enough for the subsequent destructuring and keeps the rest of the config opaque, which is appropriate here. No behavioral change introduced.
308-313: Toast copy and structure align with toast guidelinesThe toast for disabled step editing uses a clear, descriptive
titleand a shortdescriptionfor extra context, all in English. This matches the workspace rules about descriptive toast titles (no generic “Error”) and minimal contextual descriptions. Based on learnings, this is correctly implemented.
321-326: Deletion toast messaging also complies with toast and language rulesSame as above: descriptive
title, concisedescription, and English-only user-facing text. This keeps UX consistent with the rest of the app and adheres to the toast/message guidelines. Based on learnings, this is correctly implemented.
336-339: Dialog header styling uses semantic background and consistent spacingSwitching the
DialogHeadertosticky top-0 bg-background shadow-sm px-6 py-4 rounded-t-2xlremoves the blur/opacity while keeping a semantic background token and standard Tailwind spacing. This matches the design-system guidance (semantic colors, no arbitrary px sizing) and should simplify the visual hierarchy without affecting behavior.services/platform/app/(app)/dashboard/[id]/approvals/layout.tsx (1)
17-17: LGTM! Z-index elevation improves stacking hierarchy.The z-index change from
z-10toz-50appropriately elevates the sticky header in the stacking context, ensuring it stays above other UI elements as intended.services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx (2)
173-175: LGTM! Typography update follows guidelines.The change from
text-basetotext-lgcorrectly applies the standard font size class for section headings, improving visual hierarchy.
221-223: LGTM! Consistent typography improvement.The heading size update matches the "Recommended products" section above, maintaining consistent visual hierarchy throughout the modal.
services/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsx (2)
25-49: LGTM! Formatting improvement.The reformatting of the navigation items array improves readability with consistent indentation while maintaining all functional behavior.
106-106: Z-index elevation is consistent with the PR pattern.The navigation bar's z-index increase to 50 matches the stacking hierarchy updates applied across other navigation components in this PR.
services/platform/app/(app)/dashboard/[id]/approvals/approvals-navigation.tsx (2)
15-23: LGTM! Formatting improvement.The reformatting of the navigation items array maintains all functionality while improving code consistency.
67-67: Z-index elevation aligns with the consistent stacking updates.The navigation's z-index increase to 50 is consistent with the pattern applied throughout this PR to improve UI layering hierarchy.
services/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsx (1)
22-22: Z-index value aligns with similar layout headers but review incorrectly frames this as a change.The z-50 value is consistent with other main section headers (Settings layout, Approvals layout) that also use z-50. However, some secondary navigation components use z-10, so this isn't universally consistent across all sticky elements. The z-50 value matches the design system's use of z-50 for UI overlays (dialogs, dropdowns, popovers), making it appropriate for a sticky header that needs to layer above content. No conflicts with other z-index values in the codebase.
services/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsx (3)
44-56: LGTM! Navigation items structure is clean.The navigation items are well-structured with consistent hrefs. The label update to "Configuration" (singular) provides better clarity.
118-124: LGTM! Active item logic correctly handles nested routes.The special handling for the Editor item (exact match) vs sub-pages (startsWith) ensures the correct tab is highlighted when navigating between the base automation editor and its sub-routes like Executions and Configuration.
268-268: Review z-index consistency across navigation components.This file uses
z-10while other dashboard navigation components inconsistently use bothz-10andz-50. Specifically:
z-50: knowledge-navigation.tsx, approvals-navigation.tsxz-10: automation-navigation.tsx, conversations-navigation.tsx, settings-navigation.tsx, breadcrumb-navigation.tsxClarify whether this variation is intentional or if all navigation components should use the same z-index value for consistent stacking hierarchy.
services/platform/app/(app)/dashboard/[id]/conversations/components/message.tsx (4)
36-44: Verify the spacing reduction is intentional.The margin bottom changed from
mb-[2.25rem](2.25rem) tomb-6(1.5rem), reducing vertical spacing by 33%. While using the semantic classmb-6aligns with coding guidelines, this is a significant visual change that affects message spacing.
82-84: LGTM!The EmailPreview wrapper uses semantic text size classes correctly, following the coding guidelines.
88-88: LGTM!The addition of
justify-endis a straightforward alignment adjustment with no functional concerns.
45-81: Image component implementation is correct and follows guidelines.The placeholder image exists at
services/platform/public/assets/placeholder-image.png, and the error handler properly prevents infinite loops. Theunoptimizedflag is intentionally applied to external URLs (http/https) and follows a consistent pattern used elsewhere in the dashboard.The hardcoded dimensions (460×300) paired with the responsive
w-full h-autoCSS classes provide appropriate responsive behavior without requiring dynamic sizing.services/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsx (2)
21-24: Type-only reformat keeps public props contract unchangedReflowing
ChatInputPropsover multiple lines is a readability win and preserves the originalOmit<ComponentPropsWithoutRef<'div'>, 'onChange'>behavior with no functional impact.
215-217: Container styling now better aligned with semantic tokensUsing
bg-backgroundwithborder-muted-foreground/50and a semanticisDragOver ? 'border-primary bg-primary/5' : ''state fits the design-system guidance and removes the always-on blur from the main input surface while retaining the drag-over affordance.services/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsx (4)
197-202: Callback dependency array change is formatting-onlyThe dependency array still includes all relevant external values used in
handleChatComplete(threadId,clearActiveRunIdMutation, and the layout setters), so behavior is unchanged.
393-394: Scrollbar visibility preserved while keeping scrollable behaviorRemoving the
scrollbar-hideutility and keepingoverflow-y-automaintains expected scrolling while allowing native scrollbars, which is preferable for accessibility and platform consistency.
399-403: Empty-state layout condition remains logically identicalThe multi-line
cncondition for the empty state is just a formatting tweak; the logic (!threadId && threadMessages?.length === 0 && !userDraftMessage) is unchanged and still correctly guards the centered “How can I assist you?” view.
453-453: Confirm z-50 onChatInputdoesn’t regress stacking with overlaysAdding
z-50to asticky bottom-0ChatInputinside the scroll container changes stacking order relative to the scroll-to-bottom button (z-10on an absolutely positioned sibling) and any other floating UI in the dashboard. Please visually verify on smaller viewports that:
- The scroll-to-bottom button still appears above content when needed.
- Global modals/tooltips aren’t being occluded by the chat input.
If you do see overlap issues, consider harmonizing z-index levels across these elements (e.g., bumping the scroll button above or slightly lowering the input’s z-index while keeping it above page content).
services/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsx (1)
104-110: Unread detection logic remains soundThe comparison between
last_message_atandlast_read_atis correct and safely guarded by the preceding null checks; this change is effectively non-functional.services/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsx (3)
96-96: Thinking animation padding change is fineAdding
px-3on the thinking animation wrapper keeps typography and semantic colors intact while improving spacing. No issues from a layout or guideline perspective.
316-316: Messages container layout and scrolling look correct
flex-1 flex flex-col overflow-y-auto p-2 space-y-2.5gives a clean scrollable area with semantic spacing and keeps thecontainerRefbehavior intact. This aligns with the spacing and color guidelines.
382-383: Chat input wrapper styling is sound; consider avoiding arbitrary border sizeThe sticky input bar uses semantic colors (
border-muted,bg-background,border-muted-foreground/50) and standard spacing, which is good. To avoid arbitrary values, you could replaceborder-[0.5rem]with the equivalent Tailwind token (e.g.,border-8) if the exact thickness isn’t critical.⛔ Skipped due to learnings
Learnt from: CR Repo: tale-project/tale PR: 0 File: .cursor/rules/figma_rules.mdc:0-0 Timestamp: 2025-12-02T08:13:34.246Z Learning: Applies to **/*.{tsx,jsx} : NEVER use hardcoded colors like `text-gray-500`, `bg-gray-100`, `border-gray-200`; ALWAYS use design system semantic colors: `text-foreground` for primary text, `text-muted-foreground` for secondary text and icons, `bg-background` for main backgrounds, `bg-muted` for subtle backgrounds and hover states, `border-border` for bordersLearnt from: CR Repo: tale-project/poc2 PR: 0 File: .cursor/rules/figma_rules.mdc:0-0 Timestamp: 2025-11-25T04:37:44.394Z Learning: Applies to **/*.{tsx,jsx} : NEVER use hardcoded colors like `text-gray-500`, `bg-gray-100`, `border-gray-200`; ALWAYS use design system semantic colors: `text-foreground` for primary text, `text-muted-foreground` for secondary text, `bg-background` for main backgrounds, `bg-muted` for subtle backgrounds and hover states, `border-border` for bordersLearnt from: CR Repo: talecorp/poc2 PR: 0 File: .cursor/rules/figma_rules.mdc:0-0 Timestamp: 2025-10-01T17:12:39.508Z Learning: Applies to **/*.tsx : Never use hardcoded Tailwind gray/black/white color utilities (e.g., text-gray-500, bg-gray-100, border-gray-200, text-black)Learnt from: CR Repo: talecorp/poc2 PR: 0 File: .cursor/rules/figma_rules.mdc:0-0 Timestamp: 2025-10-01T17:12:39.508Z Learning: Applies to **/*.tsx : Use border-border for borders instead of border-gray-200 or border-gray-300Learnt from: CR Repo: talecorp/poc2 PR: 0 File: .cursor/rules/figma_rules.mdc:0-0 Timestamp: 2025-10-01T17:12:39.508Z Learning: Applies to **/*.tsx : Use bg-muted for subtle backgrounds and hover states instead of bg-gray-100 or bg-gray-50services/platform/app/(app)/dashboard/[id]/automations/page.tsx (1)
4-4: AutomationsTable import and usage are consistent and align with table-based UI refactorThe new
AutomationsTableimport and JSX usage correctly match the renamed component and its props (automations,organizationId), and keep the page’s data flow unchanged while leveraging the table-based layout as per the table usage guidelines.Also applies to: 54-57
services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsx (2)
39-42: Component and props renaming are consistent with file and naming guidelines
AutomationsTablePropsand theAutomizationsTabledefault export now align with the file name and PascalCase component convention, without changing behavior. This also keeps the public surface clear for consumers.Also applies to: 44-47
65-65: Inline deleteAutomation mutation initialization is fineSwitching
deleteAutomation’suseMutationcall to a single line is a no-op stylistic change; it keeps behavior unchanged and is still used correctly inhandleDeleteConfirm.
62e08e4 to
95e410a
Compare
95e410a to
28cdaee
Compare
- Changed import from type-only to value import for JEXL_TRANSFORMS - Replaced duplicated local jexlTransforms array with spread of shared constant - Ensures single source of truth for JEXL transform definitions Addresses CodeRabbit review comments #2 and #3. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Changed import from type-only to value import for JEXL_TRANSFORMS - Replaced duplicated local jexlTransforms array with spread of shared constant - Ensures single source of truth for JEXL transform definitions Addresses CodeRabbit review comments #2 and #3. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ow-list
The `search` branch routes through `resolveFileIds`, which falls back to
`getAgentScopedFileIds(orgId, agentTeamId, includeOrgKnowledge, …)` when
no explicit fileIds are supplied. The `retrieve` branch had no such
gate: it forwarded `args.fileId` straight to
`ragFetch('/api/v1/documents/{fileId}/content')`. The RAG service treats
`file_id` as a global identifier (no tenant filter on the documents
router), so any agent in any org could read any other org's indexed
chunks by guessing or leaking a `_storage` id — cross-org / cross-agent
IDOR.
Round-2-confirmed (v14). Fix mirrors the `search` branch: resolve the
agent-scoped allow-list once, refuse with a clean tool error when
`args.fileId` is not in it. Refusal returns
`{ success: false, response: '…not in the agent's authorized scope.' }`
to keep the agent loop running.
Out-of-scope here:
- RAG-side tenant filtering (services/rag/app/routers/documents.py).
- Held-document subtraction from the resolver — covered by W1 #2 and
W6 once the hold-aware resolver lands.
…ascade Round-1 reviewer-confirmed: 12 of 14 retention cleanup categories did not consult holds.orgHeld, and cascadeDeleteThreadChildren recursed into sub-threads with no hold check. Both let a litigation/preservation hold be silently bypassed for the table that records *why* the hold exists (auditLogs), every PII-bearing table, and any held sub-thread when its parent ages out — direct US FRCP 37(e) / EU GDPR Art 21 spoliation risk. retention_cleanup.ts (W1 #2) - Added `holds: ActiveHolds` to every cleanup category that lacked it: cleanupTempFiles (user + agent), cleanupAuditLogs, cleanupWorkflowLogs, cleanupUsageLedger, cleanupChatFilterEvents, cleanupPromptTemplates, cleanupMessageFeedback, cleanupMemoryAudit, cleanupCustomers, cleanupVendors, cleanupExternalConversations, cleanupMessageMetadata. - Each short-circuits with a clear info log when holds.orgHeld is true. - cleanupWorkflowLogs additionally consults holds.executionIds for per-execution holds (`targetType: 'execution'`) — until now those rows were silently ignored by cleanup. - The dispatcher's category list updated to thread `holds` to all 15 category invocations. cascade_helpers.ts (W1 #3 + partial W1 #4) - cascadeDeleteThreadChildren accepts an optional `holds` snapshot. If omitted AND organizationId is set, the helper re-loads `legalHolds` itself — closing the snapshot-once race where a hold placed mid-run provided zero protection because the dispatcher's pre-fetched Set was already stale by the time per-thread cascade fired. - Sub-thread recursion now passes the snapshot through, so the per-sub-thread hold check uses the same authoritative read. - The helper returns `{ done: true, remaining: 0 }` (no-op) on a held thread; callers (retention / erasure) treat that as "skip and continue" rather than "delete completed".
…fect cron W4 #8 — runCategory used to swallow per-category failures with only a console.warn; lastError on retentionRuns stayed undefined even when every category crashed. Operator dashboards reported green for runs where zero data was actually deleted. Now an out-param categoryErrors collects each failure, and the finally block joins them into lastError when the outer try succeeded. A wider try-block throw still wins. W4 #11 — effectApprovedReleases was only invoked from the cleanup worker. If TALE_RETENTION_DISABLED=true, an approved release sat at status='approved' indefinitely past its 24h cooldown — a compliance regression because the dual-control flow exists precisely to be auditable on a deterministic schedule. Adds a sibling internal action effectReleasesOnly that iterates every org, runs effectApprovedReleases, and is wired to its own daily cron at 01:00 UTC (off-peak vs the main 04:00 retention cron). The standalone path is independent of the retention kill-switch and per-category failures. Out-of-scope: - W4 #2 (processedCount delta wiring) — separate follow-up; requires changing every cleanup function's return type to Promise<number>.
…eout Round-2 v15 confirmed: /config unauthenticated, /openapi.json + /docs + /redoc unauthenticated, RAG container ran as root, default token baked into image ENV, strict-mode env name diverged across the wire, non-constant-time token compare, plus three SSRF-guard gaps. services/rag/app/auth.py - W7 #3: hmac.compare_digest replaces == on the bearer compare. Removes the dead-code EXEMPT_PATHS frozenset. services/rag/app/routers/health.py - W7 #1: split into public_router (`/`, `/health`) and protected_router (`/config`). main.py mounts the protected one under Depends(verify_internal_token). Old `router` re-export stays for backwards compat. services/rag/app/main.py - W7 #2: docs_url / redoc_url / openapi_url are None outside debug. - W7 #4: CORS allow_credentials flipped to False (bearer rides Authorization, never cookies). - W7 #1 wiring: mount health-public + health-protected separately. services/rag/app/config.py - W7 #8: require_custom_internal_token accepts BOTH RAG_REQUIRE_CUSTOM_INTERNAL_TOKEN and TALE_REQUIRE_CUSTOM_RAG_TOKEN via pydantic AliasChoices. services/rag/Dockerfile + services/convex/Dockerfile - W7 #5: RAG container runs as non-root (uid:gid 1001:1001 `app`). RAG ingests untrusted PDFs/DOCX through native parsers; biggest blast radius in the stack, now hardened. - W7 #6: removed RAG_INTERNAL_TOKEN=tale-rag-dev-only ENV bake from both runtime + scratch-squash stages and the matching bake in services/convex/Dockerfile. Operators MUST supply via env / compose / k8s secret. services/platform/convex/lib/helpers/rag_config.ts - W7 #9 F1: `redirect: 'manual'` on every ragFetch. - W7 #9 F2: added fc00::/7 (IPv6 ULA) to v6 blocklist (AWS IPv6 IMDSv2). - W7 #9 F3: strip trailing `.` before hostname blocklist lookup. - W7 #9 F4: re-validate URL per ragFetch invocation (DNS rebinding + env rotation mitigation). - W7 #9 F9: deleted path.startsWith('http') override branch (future- bypass foot-gun). services/platform/convex/agent_tools/rag/helpers/fetch_document_chunks.ts - W7 #10: pass timeoutMs=60_000 (default 10s was a regression). - Plus MAX_ITERATIONS=30 cap and "cursor did not advance" break to defend against an adversarial RAG response.
Round-2 W8 batch (six independent UI / docs items, all small). i18n keys (W8 #1) - Add governance.retentionPolicy.group.deletionBehavior.{title,description} to en/de/fr — previously missing in all three locales, retention editor fell back to the inline English string in every locale. - Add governance.retentionPolicy.chatFilterEvents.{title,description, placeholder,helper} to en/de/fr — previously the row title rendered the literal id "chatFilterEvents". useTranslation namespace (W8 #2) - use-data-classification-notice.ts now binds useTranslation('dataNotice') so the dataNotice.default fallback string is reachable; the prior default-namespace bind meant the DE/FR fallbacks were unreachable. UI delete copy (W8 #7) - en/de/fr.json deletePermanentMessage now matches the actual user-visible behavior (Trash + grace + retention) instead of saying "permanently deleted" while the code soft-trashes. Stale test (W8 #10) - delete_chat_thread.test.ts was asserting the legacy `{ status: 'deleted' }` patch shape; the helper now writes `{ status: 'trashed', statusChangedAt: <ms> }` in user-trash mode. Docs cron + maker-checker (W8 #8) - docs/{en,de,fr}/self-hosted/configuration/retention.md: cron 03:00 UTC → 04:00 UTC (matches crons.ts), plus a sibling note about the new 01:00 UTC effectReleasesOnly cron. - en docs: replace the non-existent `releaseLegalHold` reference with the correct dual-control flow names plus the new RELEASE_APPROVAL_MIN_DELAY_MS, ORG_HOLD_REQUIRES_DUAL_CONTROL, and closeLegalMatter fan-out behavior. - retention_cleanup.ts stale "03:00 UTC" comment fixed.
W4 #2 — `processedCount` on `retentionRuns` was always 0 because `recordRetentionRunCheckpoint` accepts a `processedDelta` arg that no caller ever passed. Operator dashboards (and the existing "view runs" admin panel) showed zero rows reaped regardless of actual cleanup volume. Refactored every cleanup function from `Promise<void>` to `Promise<number>` returning the per-category delete count: - cleanupDocuments / cleanupTempFiles - cleanupChatHistory (Pass A flips + Pass B cascades both counted) - cleanupAuditLogs (uses deleteOldLogs's per-batch deletedCount sum) - cleanupWorkflowLogs (executions + triggerLogs) - cleanupUsageLedger / cleanupChatFilterEvents / cleanupPromptTemplates - cleanupMessageFeedback / cleanupMemoryAudit / cleanupCustomers - cleanupVendors / cleanupExternalConversations / cleanupMessageMetadata - cleanupLoginAttemptsGlobal (loginAttempts + blockCounters + 2FA) `runCategory` now also returns the count (or 0 on caught error so crashed categories don't poison the total). The dispatcher captures each category's delta and passes it to recordRetentionRunCheckpoint; that mutation already accumulates into `retentionRuns.processedCount`. End-to-end effect: `getRetentionRunStatus` (admin operator panel) shows the actual rows-reaped per run instead of a stuck 0, including across continuations (each continuation contributes its own delta).
…ow-list
The `search` branch routes through `resolveFileIds`, which falls back to
`getAgentScopedFileIds(orgId, agentTeamId, includeOrgKnowledge, …)` when
no explicit fileIds are supplied. The `retrieve` branch had no such
gate: it forwarded `args.fileId` straight to
`ragFetch('/api/v1/documents/{fileId}/content')`. The RAG service treats
`file_id` as a global identifier (no tenant filter on the documents
router), so any agent in any org could read any other org's indexed
chunks by guessing or leaking a `_storage` id — cross-org / cross-agent
IDOR.
Round-2-confirmed (v14). Fix mirrors the `search` branch: resolve the
agent-scoped allow-list once, refuse with a clean tool error when
`args.fileId` is not in it. Refusal returns
`{ success: false, response: '…not in the agent's authorized scope.' }`
to keep the agent loop running.
Out-of-scope here:
- RAG-side tenant filtering (services/rag/app/routers/documents.py).
- Held-document subtraction from the resolver — covered by W1 #2 and
W6 once the hold-aware resolver lands.
…ascade Round-1 reviewer-confirmed: 12 of 14 retention cleanup categories did not consult holds.orgHeld, and cascadeDeleteThreadChildren recursed into sub-threads with no hold check. Both let a litigation/preservation hold be silently bypassed for the table that records *why* the hold exists (auditLogs), every PII-bearing table, and any held sub-thread when its parent ages out — direct US FRCP 37(e) / EU GDPR Art 21 spoliation risk. retention_cleanup.ts (W1 #2) - Added `holds: ActiveHolds` to every cleanup category that lacked it: cleanupTempFiles (user + agent), cleanupAuditLogs, cleanupWorkflowLogs, cleanupUsageLedger, cleanupChatFilterEvents, cleanupPromptTemplates, cleanupMessageFeedback, cleanupMemoryAudit, cleanupCustomers, cleanupVendors, cleanupExternalConversations, cleanupMessageMetadata. - Each short-circuits with a clear info log when holds.orgHeld is true. - cleanupWorkflowLogs additionally consults holds.executionIds for per-execution holds (`targetType: 'execution'`) — until now those rows were silently ignored by cleanup. - The dispatcher's category list updated to thread `holds` to all 15 category invocations. cascade_helpers.ts (W1 #3 + partial W1 #4) - cascadeDeleteThreadChildren accepts an optional `holds` snapshot. If omitted AND organizationId is set, the helper re-loads `legalHolds` itself — closing the snapshot-once race where a hold placed mid-run provided zero protection because the dispatcher's pre-fetched Set was already stale by the time per-thread cascade fired. - Sub-thread recursion now passes the snapshot through, so the per-sub-thread hold check uses the same authoritative read. - The helper returns `{ done: true, remaining: 0 }` (no-op) on a held thread; callers (retention / erasure) treat that as "skip and continue" rather than "delete completed".
…fect cron W4 #8 — runCategory used to swallow per-category failures with only a console.warn; lastError on retentionRuns stayed undefined even when every category crashed. Operator dashboards reported green for runs where zero data was actually deleted. Now an out-param categoryErrors collects each failure, and the finally block joins them into lastError when the outer try succeeded. A wider try-block throw still wins. W4 #11 — effectApprovedReleases was only invoked from the cleanup worker. If TALE_RETENTION_DISABLED=true, an approved release sat at status='approved' indefinitely past its 24h cooldown — a compliance regression because the dual-control flow exists precisely to be auditable on a deterministic schedule. Adds a sibling internal action effectReleasesOnly that iterates every org, runs effectApprovedReleases, and is wired to its own daily cron at 01:00 UTC (off-peak vs the main 04:00 retention cron). The standalone path is independent of the retention kill-switch and per-category failures. Out-of-scope: - W4 #2 (processedCount delta wiring) — separate follow-up; requires changing every cleanup function's return type to Promise<number>.
…eout Round-2 v15 confirmed: /config unauthenticated, /openapi.json + /docs + /redoc unauthenticated, RAG container ran as root, default token baked into image ENV, strict-mode env name diverged across the wire, non-constant-time token compare, plus three SSRF-guard gaps. services/rag/app/auth.py - W7 #3: hmac.compare_digest replaces == on the bearer compare. Removes the dead-code EXEMPT_PATHS frozenset. services/rag/app/routers/health.py - W7 #1: split into public_router (`/`, `/health`) and protected_router (`/config`). main.py mounts the protected one under Depends(verify_internal_token). Old `router` re-export stays for backwards compat. services/rag/app/main.py - W7 #2: docs_url / redoc_url / openapi_url are None outside debug. - W7 #4: CORS allow_credentials flipped to False (bearer rides Authorization, never cookies). - W7 #1 wiring: mount health-public + health-protected separately. services/rag/app/config.py - W7 #8: require_custom_internal_token accepts BOTH RAG_REQUIRE_CUSTOM_INTERNAL_TOKEN and TALE_REQUIRE_CUSTOM_RAG_TOKEN via pydantic AliasChoices. services/rag/Dockerfile + services/convex/Dockerfile - W7 #5: RAG container runs as non-root (uid:gid 1001:1001 `app`). RAG ingests untrusted PDFs/DOCX through native parsers; biggest blast radius in the stack, now hardened. - W7 #6: removed RAG_INTERNAL_TOKEN=tale-rag-dev-only ENV bake from both runtime + scratch-squash stages and the matching bake in services/convex/Dockerfile. Operators MUST supply via env / compose / k8s secret. services/platform/convex/lib/helpers/rag_config.ts - W7 #9 F1: `redirect: 'manual'` on every ragFetch. - W7 #9 F2: added fc00::/7 (IPv6 ULA) to v6 blocklist (AWS IPv6 IMDSv2). - W7 #9 F3: strip trailing `.` before hostname blocklist lookup. - W7 #9 F4: re-validate URL per ragFetch invocation (DNS rebinding + env rotation mitigation). - W7 #9 F9: deleted path.startsWith('http') override branch (future- bypass foot-gun). services/platform/convex/agent_tools/rag/helpers/fetch_document_chunks.ts - W7 #10: pass timeoutMs=60_000 (default 10s was a regression). - Plus MAX_ITERATIONS=30 cap and "cursor did not advance" break to defend against an adversarial RAG response.
Round-2 W8 batch (six independent UI / docs items, all small). i18n keys (W8 #1) - Add governance.retentionPolicy.group.deletionBehavior.{title,description} to en/de/fr — previously missing in all three locales, retention editor fell back to the inline English string in every locale. - Add governance.retentionPolicy.chatFilterEvents.{title,description, placeholder,helper} to en/de/fr — previously the row title rendered the literal id "chatFilterEvents". useTranslation namespace (W8 #2) - use-data-classification-notice.ts now binds useTranslation('dataNotice') so the dataNotice.default fallback string is reachable; the prior default-namespace bind meant the DE/FR fallbacks were unreachable. UI delete copy (W8 #7) - en/de/fr.json deletePermanentMessage now matches the actual user-visible behavior (Trash + grace + retention) instead of saying "permanently deleted" while the code soft-trashes. Stale test (W8 #10) - delete_chat_thread.test.ts was asserting the legacy `{ status: 'deleted' }` patch shape; the helper now writes `{ status: 'trashed', statusChangedAt: <ms> }` in user-trash mode. Docs cron + maker-checker (W8 #8) - docs/{en,de,fr}/self-hosted/configuration/retention.md: cron 03:00 UTC → 04:00 UTC (matches crons.ts), plus a sibling note about the new 01:00 UTC effectReleasesOnly cron. - en docs: replace the non-existent `releaseLegalHold` reference with the correct dual-control flow names plus the new RELEASE_APPROVAL_MIN_DELAY_MS, ORG_HOLD_REQUIRES_DUAL_CONTROL, and closeLegalMatter fan-out behavior. - retention_cleanup.ts stale "03:00 UTC" comment fixed.
W4 #2 — `processedCount` on `retentionRuns` was always 0 because `recordRetentionRunCheckpoint` accepts a `processedDelta` arg that no caller ever passed. Operator dashboards (and the existing "view runs" admin panel) showed zero rows reaped regardless of actual cleanup volume. Refactored every cleanup function from `Promise<void>` to `Promise<number>` returning the per-category delete count: - cleanupDocuments / cleanupTempFiles - cleanupChatHistory (Pass A flips + Pass B cascades both counted) - cleanupAuditLogs (uses deleteOldLogs's per-batch deletedCount sum) - cleanupWorkflowLogs (executions + triggerLogs) - cleanupUsageLedger / cleanupChatFilterEvents / cleanupPromptTemplates - cleanupMessageFeedback / cleanupMemoryAudit / cleanupCustomers - cleanupVendors / cleanupExternalConversations / cleanupMessageMetadata - cleanupLoginAttemptsGlobal (loginAttempts + blockCounters + 2FA) `runCategory` now also returns the count (or 0 on caught error so crashed categories don't poison the total). The dispatcher captures each category's delta and passes it to recordRetentionRunCheckpoint; that mutation already accumulates into `retentionRuns.processedCount`. End-to-end effect: `getRetentionRunStatus` (admin operator panel) shows the actual rows-reaped per run instead of a stuck 0, including across continuations (each continuation contributes its own delta).
…ow-list
The `search` branch routes through `resolveFileIds`, which falls back to
`getAgentScopedFileIds(orgId, agentTeamId, includeOrgKnowledge, …)` when
no explicit fileIds are supplied. The `retrieve` branch had no such
gate: it forwarded `args.fileId` straight to
`ragFetch('/api/v1/documents/{fileId}/content')`. The RAG service treats
`file_id` as a global identifier (no tenant filter on the documents
router), so any agent in any org could read any other org's indexed
chunks by guessing or leaking a `_storage` id — cross-org / cross-agent
IDOR.
Round-2-confirmed (v14). Fix mirrors the `search` branch: resolve the
agent-scoped allow-list once, refuse with a clean tool error when
`args.fileId` is not in it. Refusal returns
`{ success: false, response: '…not in the agent's authorized scope.' }`
to keep the agent loop running.
Out-of-scope here:
- RAG-side tenant filtering (services/rag/app/routers/documents.py).
- Held-document subtraction from the resolver — covered by W1 #2 and
W6 once the hold-aware resolver lands.
…ascade Round-1 reviewer-confirmed: 12 of 14 retention cleanup categories did not consult holds.orgHeld, and cascadeDeleteThreadChildren recursed into sub-threads with no hold check. Both let a litigation/preservation hold be silently bypassed for the table that records *why* the hold exists (auditLogs), every PII-bearing table, and any held sub-thread when its parent ages out — direct US FRCP 37(e) / EU GDPR Art 21 spoliation risk. retention_cleanup.ts (W1 #2) - Added `holds: ActiveHolds` to every cleanup category that lacked it: cleanupTempFiles (user + agent), cleanupAuditLogs, cleanupWorkflowLogs, cleanupUsageLedger, cleanupChatFilterEvents, cleanupPromptTemplates, cleanupMessageFeedback, cleanupMemoryAudit, cleanupCustomers, cleanupVendors, cleanupExternalConversations, cleanupMessageMetadata. - Each short-circuits with a clear info log when holds.orgHeld is true. - cleanupWorkflowLogs additionally consults holds.executionIds for per-execution holds (`targetType: 'execution'`) — until now those rows were silently ignored by cleanup. - The dispatcher's category list updated to thread `holds` to all 15 category invocations. cascade_helpers.ts (W1 #3 + partial W1 #4) - cascadeDeleteThreadChildren accepts an optional `holds` snapshot. If omitted AND organizationId is set, the helper re-loads `legalHolds` itself — closing the snapshot-once race where a hold placed mid-run provided zero protection because the dispatcher's pre-fetched Set was already stale by the time per-thread cascade fired. - Sub-thread recursion now passes the snapshot through, so the per-sub-thread hold check uses the same authoritative read. - The helper returns `{ done: true, remaining: 0 }` (no-op) on a held thread; callers (retention / erasure) treat that as "skip and continue" rather than "delete completed".
…fect cron W4 #8 — runCategory used to swallow per-category failures with only a console.warn; lastError on retentionRuns stayed undefined even when every category crashed. Operator dashboards reported green for runs where zero data was actually deleted. Now an out-param categoryErrors collects each failure, and the finally block joins them into lastError when the outer try succeeded. A wider try-block throw still wins. W4 #11 — effectApprovedReleases was only invoked from the cleanup worker. If TALE_RETENTION_DISABLED=true, an approved release sat at status='approved' indefinitely past its 24h cooldown — a compliance regression because the dual-control flow exists precisely to be auditable on a deterministic schedule. Adds a sibling internal action effectReleasesOnly that iterates every org, runs effectApprovedReleases, and is wired to its own daily cron at 01:00 UTC (off-peak vs the main 04:00 retention cron). The standalone path is independent of the retention kill-switch and per-category failures. Out-of-scope: - W4 #2 (processedCount delta wiring) — separate follow-up; requires changing every cleanup function's return type to Promise<number>.
…eout Round-2 v15 confirmed: /config unauthenticated, /openapi.json + /docs + /redoc unauthenticated, RAG container ran as root, default token baked into image ENV, strict-mode env name diverged across the wire, non-constant-time token compare, plus three SSRF-guard gaps. services/rag/app/auth.py - W7 #3: hmac.compare_digest replaces == on the bearer compare. Removes the dead-code EXEMPT_PATHS frozenset. services/rag/app/routers/health.py - W7 #1: split into public_router (`/`, `/health`) and protected_router (`/config`). main.py mounts the protected one under Depends(verify_internal_token). Old `router` re-export stays for backwards compat. services/rag/app/main.py - W7 #2: docs_url / redoc_url / openapi_url are None outside debug. - W7 #4: CORS allow_credentials flipped to False (bearer rides Authorization, never cookies). - W7 #1 wiring: mount health-public + health-protected separately. services/rag/app/config.py - W7 #8: require_custom_internal_token accepts BOTH RAG_REQUIRE_CUSTOM_INTERNAL_TOKEN and TALE_REQUIRE_CUSTOM_RAG_TOKEN via pydantic AliasChoices. services/rag/Dockerfile + services/convex/Dockerfile - W7 #5: RAG container runs as non-root (uid:gid 1001:1001 `app`). RAG ingests untrusted PDFs/DOCX through native parsers; biggest blast radius in the stack, now hardened. - W7 #6: removed RAG_INTERNAL_TOKEN=tale-rag-dev-only ENV bake from both runtime + scratch-squash stages and the matching bake in services/convex/Dockerfile. Operators MUST supply via env / compose / k8s secret. services/platform/convex/lib/helpers/rag_config.ts - W7 #9 F1: `redirect: 'manual'` on every ragFetch. - W7 #9 F2: added fc00::/7 (IPv6 ULA) to v6 blocklist (AWS IPv6 IMDSv2). - W7 #9 F3: strip trailing `.` before hostname blocklist lookup. - W7 #9 F4: re-validate URL per ragFetch invocation (DNS rebinding + env rotation mitigation). - W7 #9 F9: deleted path.startsWith('http') override branch (future- bypass foot-gun). services/platform/convex/agent_tools/rag/helpers/fetch_document_chunks.ts - W7 #10: pass timeoutMs=60_000 (default 10s was a regression). - Plus MAX_ITERATIONS=30 cap and "cursor did not advance" break to defend against an adversarial RAG response.
Round-2 W8 batch (six independent UI / docs items, all small). i18n keys (W8 #1) - Add governance.retentionPolicy.group.deletionBehavior.{title,description} to en/de/fr — previously missing in all three locales, retention editor fell back to the inline English string in every locale. - Add governance.retentionPolicy.chatFilterEvents.{title,description, placeholder,helper} to en/de/fr — previously the row title rendered the literal id "chatFilterEvents". useTranslation namespace (W8 #2) - use-data-classification-notice.ts now binds useTranslation('dataNotice') so the dataNotice.default fallback string is reachable; the prior default-namespace bind meant the DE/FR fallbacks were unreachable. UI delete copy (W8 #7) - en/de/fr.json deletePermanentMessage now matches the actual user-visible behavior (Trash + grace + retention) instead of saying "permanently deleted" while the code soft-trashes. Stale test (W8 #10) - delete_chat_thread.test.ts was asserting the legacy `{ status: 'deleted' }` patch shape; the helper now writes `{ status: 'trashed', statusChangedAt: <ms> }` in user-trash mode. Docs cron + maker-checker (W8 #8) - docs/{en,de,fr}/self-hosted/configuration/retention.md: cron 03:00 UTC → 04:00 UTC (matches crons.ts), plus a sibling note about the new 01:00 UTC effectReleasesOnly cron. - en docs: replace the non-existent `releaseLegalHold` reference with the correct dual-control flow names plus the new RELEASE_APPROVAL_MIN_DELAY_MS, ORG_HOLD_REQUIRES_DUAL_CONTROL, and closeLegalMatter fan-out behavior. - retention_cleanup.ts stale "03:00 UTC" comment fixed.
W4 #2 — `processedCount` on `retentionRuns` was always 0 because `recordRetentionRunCheckpoint` accepts a `processedDelta` arg that no caller ever passed. Operator dashboards (and the existing "view runs" admin panel) showed zero rows reaped regardless of actual cleanup volume. Refactored every cleanup function from `Promise<void>` to `Promise<number>` returning the per-category delete count: - cleanupDocuments / cleanupTempFiles - cleanupChatHistory (Pass A flips + Pass B cascades both counted) - cleanupAuditLogs (uses deleteOldLogs's per-batch deletedCount sum) - cleanupWorkflowLogs (executions + triggerLogs) - cleanupUsageLedger / cleanupChatFilterEvents / cleanupPromptTemplates - cleanupMessageFeedback / cleanupMemoryAudit / cleanupCustomers - cleanupVendors / cleanupExternalConversations / cleanupMessageMetadata - cleanupLoginAttemptsGlobal (loginAttempts + blockCounters + 2FA) `runCategory` now also returns the count (or 0 on caught error so crashed categories don't poison the total). The dispatcher captures each category's delta and passes it to recordRetentionRunCheckpoint; that mutation already accumulates into `retentionRuns.processedCount`. End-to-end effect: `getRetentionRunStatus` (admin operator panel) shows the actual rows-reaped per run instead of a stuck 0, including across continuations (each continuation contributes its own delta).
…ow-list
The `search` branch routes through `resolveFileIds`, which falls back to
`getAgentScopedFileIds(orgId, agentTeamId, includeOrgKnowledge, …)` when
no explicit fileIds are supplied. The `retrieve` branch had no such
gate: it forwarded `args.fileId` straight to
`ragFetch('/api/v1/documents/{fileId}/content')`. The RAG service treats
`file_id` as a global identifier (no tenant filter on the documents
router), so any agent in any org could read any other org's indexed
chunks by guessing or leaking a `_storage` id — cross-org / cross-agent
IDOR.
Round-2-confirmed (v14). Fix mirrors the `search` branch: resolve the
agent-scoped allow-list once, refuse with a clean tool error when
`args.fileId` is not in it. Refusal returns
`{ success: false, response: '…not in the agent's authorized scope.' }`
to keep the agent loop running.
Out-of-scope here:
- RAG-side tenant filtering (services/rag/app/routers/documents.py).
- Held-document subtraction from the resolver — covered by W1 #2 and
W6 once the hold-aware resolver lands.
…ascade Round-1 reviewer-confirmed: 12 of 14 retention cleanup categories did not consult holds.orgHeld, and cascadeDeleteThreadChildren recursed into sub-threads with no hold check. Both let a litigation/preservation hold be silently bypassed for the table that records *why* the hold exists (auditLogs), every PII-bearing table, and any held sub-thread when its parent ages out — direct US FRCP 37(e) / EU GDPR Art 21 spoliation risk. retention_cleanup.ts (W1 #2) - Added `holds: ActiveHolds` to every cleanup category that lacked it: cleanupTempFiles (user + agent), cleanupAuditLogs, cleanupWorkflowLogs, cleanupUsageLedger, cleanupChatFilterEvents, cleanupPromptTemplates, cleanupMessageFeedback, cleanupMemoryAudit, cleanupCustomers, cleanupVendors, cleanupExternalConversations, cleanupMessageMetadata. - Each short-circuits with a clear info log when holds.orgHeld is true. - cleanupWorkflowLogs additionally consults holds.executionIds for per-execution holds (`targetType: 'execution'`) — until now those rows were silently ignored by cleanup. - The dispatcher's category list updated to thread `holds` to all 15 category invocations. cascade_helpers.ts (W1 #3 + partial W1 #4) - cascadeDeleteThreadChildren accepts an optional `holds` snapshot. If omitted AND organizationId is set, the helper re-loads `legalHolds` itself — closing the snapshot-once race where a hold placed mid-run provided zero protection because the dispatcher's pre-fetched Set was already stale by the time per-thread cascade fired. - Sub-thread recursion now passes the snapshot through, so the per-sub-thread hold check uses the same authoritative read. - The helper returns `{ done: true, remaining: 0 }` (no-op) on a held thread; callers (retention / erasure) treat that as "skip and continue" rather than "delete completed".
…fect cron W4 #8 — runCategory used to swallow per-category failures with only a console.warn; lastError on retentionRuns stayed undefined even when every category crashed. Operator dashboards reported green for runs where zero data was actually deleted. Now an out-param categoryErrors collects each failure, and the finally block joins them into lastError when the outer try succeeded. A wider try-block throw still wins. W4 #11 — effectApprovedReleases was only invoked from the cleanup worker. If TALE_RETENTION_DISABLED=true, an approved release sat at status='approved' indefinitely past its 24h cooldown — a compliance regression because the dual-control flow exists precisely to be auditable on a deterministic schedule. Adds a sibling internal action effectReleasesOnly that iterates every org, runs effectApprovedReleases, and is wired to its own daily cron at 01:00 UTC (off-peak vs the main 04:00 retention cron). The standalone path is independent of the retention kill-switch and per-category failures. Out-of-scope: - W4 #2 (processedCount delta wiring) — separate follow-up; requires changing every cleanup function's return type to Promise<number>.
…eout Round-2 v15 confirmed: /config unauthenticated, /openapi.json + /docs + /redoc unauthenticated, RAG container ran as root, default token baked into image ENV, strict-mode env name diverged across the wire, non-constant-time token compare, plus three SSRF-guard gaps. services/rag/app/auth.py - W7 #3: hmac.compare_digest replaces == on the bearer compare. Removes the dead-code EXEMPT_PATHS frozenset. services/rag/app/routers/health.py - W7 #1: split into public_router (`/`, `/health`) and protected_router (`/config`). main.py mounts the protected one under Depends(verify_internal_token). Old `router` re-export stays for backwards compat. services/rag/app/main.py - W7 #2: docs_url / redoc_url / openapi_url are None outside debug. - W7 #4: CORS allow_credentials flipped to False (bearer rides Authorization, never cookies). - W7 #1 wiring: mount health-public + health-protected separately. services/rag/app/config.py - W7 #8: require_custom_internal_token accepts BOTH RAG_REQUIRE_CUSTOM_INTERNAL_TOKEN and TALE_REQUIRE_CUSTOM_RAG_TOKEN via pydantic AliasChoices. services/rag/Dockerfile + services/convex/Dockerfile - W7 #5: RAG container runs as non-root (uid:gid 1001:1001 `app`). RAG ingests untrusted PDFs/DOCX through native parsers; biggest blast radius in the stack, now hardened. - W7 #6: removed RAG_INTERNAL_TOKEN=tale-rag-dev-only ENV bake from both runtime + scratch-squash stages and the matching bake in services/convex/Dockerfile. Operators MUST supply via env / compose / k8s secret. services/platform/convex/lib/helpers/rag_config.ts - W7 #9 F1: `redirect: 'manual'` on every ragFetch. - W7 #9 F2: added fc00::/7 (IPv6 ULA) to v6 blocklist (AWS IPv6 IMDSv2). - W7 #9 F3: strip trailing `.` before hostname blocklist lookup. - W7 #9 F4: re-validate URL per ragFetch invocation (DNS rebinding + env rotation mitigation). - W7 #9 F9: deleted path.startsWith('http') override branch (future- bypass foot-gun). services/platform/convex/agent_tools/rag/helpers/fetch_document_chunks.ts - W7 #10: pass timeoutMs=60_000 (default 10s was a regression). - Plus MAX_ITERATIONS=30 cap and "cursor did not advance" break to defend against an adversarial RAG response.
Round-2 W8 batch (six independent UI / docs items, all small). i18n keys (W8 #1) - Add governance.retentionPolicy.group.deletionBehavior.{title,description} to en/de/fr — previously missing in all three locales, retention editor fell back to the inline English string in every locale. - Add governance.retentionPolicy.chatFilterEvents.{title,description, placeholder,helper} to en/de/fr — previously the row title rendered the literal id "chatFilterEvents". useTranslation namespace (W8 #2) - use-data-classification-notice.ts now binds useTranslation('dataNotice') so the dataNotice.default fallback string is reachable; the prior default-namespace bind meant the DE/FR fallbacks were unreachable. UI delete copy (W8 #7) - en/de/fr.json deletePermanentMessage now matches the actual user-visible behavior (Trash + grace + retention) instead of saying "permanently deleted" while the code soft-trashes. Stale test (W8 #10) - delete_chat_thread.test.ts was asserting the legacy `{ status: 'deleted' }` patch shape; the helper now writes `{ status: 'trashed', statusChangedAt: <ms> }` in user-trash mode. Docs cron + maker-checker (W8 #8) - docs/{en,de,fr}/self-hosted/configuration/retention.md: cron 03:00 UTC → 04:00 UTC (matches crons.ts), plus a sibling note about the new 01:00 UTC effectReleasesOnly cron. - en docs: replace the non-existent `releaseLegalHold` reference with the correct dual-control flow names plus the new RELEASE_APPROVAL_MIN_DELAY_MS, ORG_HOLD_REQUIRES_DUAL_CONTROL, and closeLegalMatter fan-out behavior. - retention_cleanup.ts stale "03:00 UTC" comment fixed.
W4 #2 — `processedCount` on `retentionRuns` was always 0 because `recordRetentionRunCheckpoint` accepts a `processedDelta` arg that no caller ever passed. Operator dashboards (and the existing "view runs" admin panel) showed zero rows reaped regardless of actual cleanup volume. Refactored every cleanup function from `Promise<void>` to `Promise<number>` returning the per-category delete count: - cleanupDocuments / cleanupTempFiles - cleanupChatHistory (Pass A flips + Pass B cascades both counted) - cleanupAuditLogs (uses deleteOldLogs's per-batch deletedCount sum) - cleanupWorkflowLogs (executions + triggerLogs) - cleanupUsageLedger / cleanupChatFilterEvents / cleanupPromptTemplates - cleanupMessageFeedback / cleanupMemoryAudit / cleanupCustomers - cleanupVendors / cleanupExternalConversations / cleanupMessageMetadata - cleanupLoginAttemptsGlobal (loginAttempts + blockCounters + 2FA) `runCategory` now also returns the count (or 0 on caught error so crashed categories don't poison the total). The dispatcher captures each category's delta and passes it to recordRetentionRunCheckpoint; that mutation already accumulates into `retentionRuns.processedCount`. End-to-end effect: `getRetentionRunStatus` (admin operator panel) shows the actual rows-reaped per run instead of a stuck 0, including across continuations (each continuation contributes its own delta).
…— docs + openai.json honesty Closes round-5 findings #1, #2, #42, #43, #45. - Docs across en/de/fr no longer claim a browser `speechSynthesis` fallback. The code (use-voice-output-player.ts + tts/schema.ts) is explicitly provider-only — failed chunks are skipped silently. The "Provider vs browser fallback" section is replaced with a "When no provider is configured" section that states the actual behaviour: the personalization toggle is disabled and surfaces a Settings link. - Docs across en/de/fr correct the "no cron required" lie. The lifecycle text now reflects reality: a daily org-sweep cron is the primary GC, with opportunistic per-thread cleanup scheduled from the write path as a secondary trigger. - `examples/providers/openai.json` raises the `centsPerMillionCharacters` default from 200 to 1500 (tts-1 list rate, matches `providers.test.ts:81` and the docs' canonical example), and expands the description / de+fr i18n descriptions with the per-token-vs-per- character billing caveat that previously existed only in the English description. Operators on a non-English UI no longer miss the calibration warning. - The English doc consistently uses "Settings → AI providers" (matching the UI label) instead of "Settings > Providers". - `docs/fr/self-hosted/configuration/providers.md:319` anchor link to the FR attachments page is fixed (`#audio-and-video-transcription` was the English slug; the localized heading auto-slugifies to `#transcription-audio-et-video`). DE line 319 had the same English- slug bug, fixed alongside (DE auto-slugs to `#audio-und-video-transkription`).
…— docs + openai.json honesty Closes round-5 findings #1, #2, #42, #43, #45. - Docs across en/de/fr no longer claim a browser `speechSynthesis` fallback. The code (use-voice-output-player.ts + tts/schema.ts) is explicitly provider-only — failed chunks are skipped silently. The "Provider vs browser fallback" section is replaced with a "When no provider is configured" section that states the actual behaviour: the personalization toggle is disabled and surfaces a Settings link. - Docs across en/de/fr correct the "no cron required" lie. The lifecycle text now reflects reality: a daily org-sweep cron is the primary GC, with opportunistic per-thread cleanup scheduled from the write path as a secondary trigger. - `examples/providers/openai.json` raises the `centsPerMillionCharacters` default from 200 to 1500 (tts-1 list rate, matches `providers.test.ts:81` and the docs' canonical example), and expands the description / de+fr i18n descriptions with the per-token-vs-per- character billing caveat that previously existed only in the English description. Operators on a non-English UI no longer miss the calibration warning. - The English doc consistently uses "Settings → AI providers" (matching the UI label) instead of "Settings > Providers". - `docs/fr/self-hosted/configuration/providers.md:319` anchor link to the FR attachments page is fixed (`#audio-and-video-transcription` was the English slug; the localized heading auto-slugifies to `#transcription-audio-et-video`). DE line 319 had the same English- slug bug, fixed alongside (DE auto-slugs to `#audio-und-video-transkription`).
… heartbeat Three coupled fixes to the Convex side of the sandbox state machine that together close the failure modes round-2 verification confirmed: R2-B7 #1: `codeStorageId` was stored before `reserveSlotAndInsert` but the rollback set was constructed AFTER reservation. A QUOTA_EXCEEDED throw orphaned one `_storage` blob per rejected run. Catch the reserve error and `ctx.storage.delete()` the blob before rethrowing. R2-B7 #2: the 90-day audit GC dropped audit rows without touching their code/stdout/stderr storage blobs. Inline-delete those three blob types before the row delete (mutation contexts CAN call `ctx.storage.delete`, per `workflows/executions/delete_storage_blob.ts:20`). Watchdog reaps the same way so a stuck row doesn't sit on its blobs for 90 days. Output-file blobs are still owned by `fileMetadata` and not touched here. R2-B6 #1/#2/#3: `recoverStuckSandboxes` now caps each per-status scan at 200 rows so the mutation can't blow its doc-read budget mid-sweep (cron re-runs every 5 min and picks up the trailing rows). The heartbeat `setInterval` callback wraps the mutation call in try/catch+console.warn so a stalled heartbeat is visible rather than silently aging into a watchdog reap. Explicit `await tickHeartbeat()` between each `ctx.storage.store` keeps `heartbeatAt` fresh during multi-MB upload tails. Watchdog cutoff is now `max_timeout + 600s` so those upload tails fit inside the budget by construction.
Summary by CodeRabbit
Release Notes
New Features
Style
Chores
✏️ Tip: You can customize this high-level summary in your review settings.