Skip to content

update dashboard ui (#508)#2

Merged
Israeltheminer merged 1 commit into
mainfrom
update-ui
Dec 2, 2025
Merged

update dashboard ui (#508)#2
Israeltheminer merged 1 commit into
mainfrom
update-ui

Conversation

@Israeltheminer
Copy link
Copy Markdown
Collaborator

@Israeltheminer Israeltheminer commented Dec 2, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Pagination now navigates by page number instead of page size selection.
  • Style

    • Increased heading sizes for better visual hierarchy across multiple sections.
    • Improved UI element layering and stacking for better overlay visibility.
    • Removed visual blur effects for a cleaner interface.
    • Refined spacing and styling throughout the dashboard.
  • Chores

    • Corrected navigation label from "Configurations" to "Configuration."
    • Updated component naming for improved clarity.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 2, 2025

📝 Walkthrough

Walkthrough

This 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:

  • Pagination component logic — routing behavior and query parameter handling changed significantly; verify page navigation and parameter preservation work correctly
  • Error boundary API changes — reset prop removal and internal page-reload logic; verify all call sites are updated and error recovery flows work as intended
  • AutomationsTable rename — component and interface renaming with status badge variant changes; confirm all imports and usages are updated across the codebase
  • Z-index elevation strategy — verify the 10→50 changes across multiple components don't create unintended stacking issues or overlay problems in overlapping UI scenarios

Possibly related PRs

  • tale-project/poc2#508 — Applies overlapping code-level changes to the same files (z-index updates, typography adjustments, error-boundary reset removal, AutomationsList→AutomationsTable rename), suggesting parallel or dependent UI refinement work.
  • tale-project/poc2#382 — Modifies services/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsx with overlapping UI and styling adjustments to the same component.
  • talecorp/poc#204 — Modifies the same conversation UI components (conversation-panel.tsx, message-editor.tsx, message.tsx) with code-level changes to message rendering and container styling.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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-50 for 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 token

The 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/10 usages 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

handlePageSelect correctly validates the target page and mirrors the prev/next routing behavior, but there are two maintainability/UX issues:

  • The URL param construction and queryString merging logic is now duplicated between handlePageChange and handlePageSelect. Extracting a shared helper like navigateToPage(page: number) would reduce drift risk and make future changes (e.g., handling more params) safer.
  • Unlike handlePageChange, this path never updates loadingStates, so users won't see spinners/disabled arrows when changing pages via the Select. For a consistent experience, consider setting a generic loading flag or reusing the existing loadingStates before startTransition.

Both are non-blocking but would make this component more robust and consistent.

services/platform/app/(app)/dashboard/[id]/error.tsx (1)

6-17: reset prop is now unused and no longer affects retry behavior

ErrorProps still defines reset, and DashboardError still receives it, but it’s not forwarded anywhere and retry is now handled via window.location.reload() inside ErrorDisplay. This is a behavior change from using Next.js’s reset() callback to a full page reload.

If this change is intentional, consider either:

  • Removing reset from ErrorProps / function signature or renaming to _reset to avoid unused-parameter lint warnings, or
  • Wiring reset back into the retry flow (e.g., via a callback prop on DashboardErrorBoundary) 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 spacing

In ErrorDisplay the current classes:

  • py-[10rem] (Line 51) uses an arbitrary spacing value.
  • bg-red-100 and text-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-40 instead of py-[10rem] (10rem is Tailwind’s 40 scale 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 blur

The 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-sm for consistency with other updated headers; otherwise this is fine as-is.


331-359: Bottom sticky composer/status bar behavior is coherent

The bottom sticky container with z-50 bg-background cleanly 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)

📥 Commits

Reviewing files that changed from the base of the PR and between 3c9285b and 62e08e4.

📒 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 specifying font-['Inter:Regular',_sans-serif] as it should be the default
Only specify font-family when using non-default fonts like font-['Inter:Medium',_sans-serif]
Ensure font-family matches font-weight (Inter:Regular with font-normal, Inter:Medium with font-medium)
Use leading-normal instead of leading-[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 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 borders
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

Files:

  • services/platform/app/(app)/dashboard/[id]/approvals/approvals-navigation.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsx
  • services/platform/components/ui/pagination.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/page.tsx
  • services/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
  • services/platform/app/(app)/dashboard/[id]/error.tsx
  • services/platform/components/error-boundary.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/message.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsx
  • services/platform/app/(app)/dashboard/[id]/settings/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsx
  • services/platform/components/ui/pagination.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/page.tsx
  • services/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
  • services/platform/app/(app)/dashboard/[id]/error.tsx
  • services/platform/components/error-boundary.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/message.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsx
  • services/platform/app/(app)/dashboard/[id]/settings/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsx
  • services/platform/components/ui/pagination.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/page.tsx
  • services/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
  • services/platform/app/(app)/dashboard/[id]/error.tsx
  • services/platform/components/error-boundary.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/message.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsx
  • services/platform/app/(app)/dashboard/[id]/settings/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsx
  • services/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.tsx as server components by default; use 'use client' only for interactions and state

Files:

  • services/platform/app/(app)/dashboard/[id]/approvals/approvals-navigation.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/page.tsx
  • services/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
  • services/platform/app/(app)/dashboard/[id]/error.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/message.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsx
  • services/platform/app/(app)/dashboard/[id]/settings/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/message-editor.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/conversations.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/automation-assistant.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/tone-of-voice-form.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/documents/components/document-preview-pdf.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/automations-table.tsx
  • services/platform/components/ui/pagination.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/page.tsx
  • services/platform/app/(app)/dashboard/[id]/chat/components/chat-interface.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/tone-of-voice/components/example-messages-table.tsx
  • services/platform/app/(app)/dashboard/[id]/error.tsx
  • services/platform/components/error-boundary.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/message.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/components/step-details-dialog.tsx
  • services/platform/app/(app)/dashboard/[id]/settings/layout.tsx
  • services/platform/app/(app)/dashboard/[id]/chat/components/chat-input.tsx
  • services/platform/app/(app)/dashboard/[id]/conversations/components/conversation-panel.tsx
  • services/platform/app/(app)/dashboard/[id]/(knowledge)/knowledge-navigation.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/approvals/components/approval-detail-modal.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/page.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/page.tsx
  • services/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.tsx
  • services/platform/app/(app)/dashboard/[id]/automations/page.tsx
  • services/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.tsx
  • services/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.tsx
  • services/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 SelectionState union type and the getConversationsPage query 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-base to text-lg aligns 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-lg should 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-base to text-lg maintains 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-lg should 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-sm and 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 to z-50 is 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 for step.config is safe and scoped appropriately

Casting step.config to { 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 guidelines

The toast for disabled step editing uses a clear, descriptive title and a short description for 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 rules

Same as above: descriptive title, concise description, 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 spacing

Switching the DialogHeader to sticky top-0 bg-background shadow-sm px-6 py-4 rounded-t-2xl removes 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-10 to z-50 appropriately 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-base to text-lg correctly 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-10 while other dashboard navigation components inconsistently use both z-10 and z-50. Specifically:

  • z-50: knowledge-navigation.tsx, approvals-navigation.tsx
  • z-10: automation-navigation.tsx, conversations-navigation.tsx, settings-navigation.tsx, breadcrumb-navigation.tsx

Clarify 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) to mb-6 (1.5rem), reducing vertical spacing by 33%. While using the semantic class mb-6 aligns 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-end is 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. The unoptimized flag 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-auto CSS 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 unchanged

Reflowing ChatInputProps over multiple lines is a readability win and preserves the original Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> behavior with no functional impact.


215-217: Container styling now better aligned with semantic tokens

Using bg-background with border-muted-foreground/50 and a semantic isDragOver ? '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-only

The 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 behavior

Removing the scrollbar-hide utility and keeping overflow-y-auto maintains expected scrolling while allowing native scrollbars, which is preferable for accessibility and platform consistency.


399-403: Empty-state layout condition remains logically identical

The multi-line cn condition 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 on ChatInput doesn’t regress stacking with overlays

Adding z-50 to a sticky bottom-0 ChatInput inside the scroll container changes stacking order relative to the scroll-to-bottom button (z-10 on 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 sound

The comparison between last_message_at and last_read_at is 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 fine

Adding px-3 on 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.5 gives a clean scrollable area with semantic spacing and keeps the containerRef behavior intact. This aligns with the spacing and color guidelines.


382-383: Chat input wrapper styling is sound; consider avoiding arbitrary border size

The 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 replace border-[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 borders
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} : 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 borders
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 : 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-300
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
services/platform/app/(app)/dashboard/[id]/automations/page.tsx (1)

4-4: AutomationsTable import and usage are consistent and align with table-based UI refactor

The new AutomationsTable import 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

AutomationsTableProps and the AutomizationsTable default 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 fine

Switching deleteAutomation’s useMutation call to a single line is a no-op stylistic change; it keeps behavior unchanged and is still used correctly in handleDeleteConfirm.

Comment thread services/platform/app/(app)/dashboard/[id]/automations/automation-navigation.tsx Outdated
Comment thread services/platform/components/error-boundary.tsx
Comment thread services/platform/components/ui/pagination.tsx
@Israeltheminer Israeltheminer merged commit f6c4bf2 into main Dec 2, 2025
2 checks passed
@Israeltheminer Israeltheminer deleted the update-ui branch December 2, 2025 15:07
larryro added a commit that referenced this pull request Dec 30, 2025
- 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>
larryro added a commit that referenced this pull request Dec 30, 2025
- 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>
yannickmonney pushed a commit that referenced this pull request Apr 8, 2026
larryro added a commit that referenced this pull request May 7, 2026
…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.
larryro added a commit that referenced this pull request May 7, 2026
…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".
larryro added a commit that referenced this pull request May 7, 2026
…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>.
larryro added a commit that referenced this pull request May 7, 2026
…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.
larryro added a commit that referenced this pull request May 7, 2026
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.
larryro added a commit that referenced this pull request May 7, 2026
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).
larryro added a commit that referenced this pull request May 8, 2026
…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.
larryro added a commit that referenced this pull request May 8, 2026
…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".
larryro added a commit that referenced this pull request May 8, 2026
…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>.
larryro added a commit that referenced this pull request May 8, 2026
…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.
larryro added a commit that referenced this pull request May 8, 2026
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.
larryro added a commit that referenced this pull request May 8, 2026
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).
larryro added a commit that referenced this pull request May 8, 2026
…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.
larryro added a commit that referenced this pull request May 8, 2026
…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".
larryro added a commit that referenced this pull request May 8, 2026
…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>.
larryro added a commit that referenced this pull request May 8, 2026
…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.
larryro added a commit that referenced this pull request May 8, 2026
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.
larryro added a commit that referenced this pull request May 8, 2026
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).
larryro added a commit that referenced this pull request May 9, 2026
…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.
larryro added a commit that referenced this pull request May 9, 2026
…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".
larryro added a commit that referenced this pull request May 9, 2026
…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>.
larryro added a commit that referenced this pull request May 9, 2026
…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.
larryro added a commit that referenced this pull request May 9, 2026
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.
larryro added a commit that referenced this pull request May 9, 2026
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).
larryro added a commit that referenced this pull request May 17, 2026
…— 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`).
larryro added a commit that referenced this pull request May 17, 2026
…— 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`).
larryro added a commit that referenced this pull request May 21, 2026
… 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant