Skip to content

feat(platform): projects — shared workspaces for files, instructions, threads, agents#1735

Merged
yannickmonney merged 7 commits into
mainfrom
feat/platform-projects
May 25, 2026
Merged

feat(platform): projects — shared workspaces for files, instructions, threads, agents#1735
yannickmonney merged 7 commits into
mainfrom
feat/platform-projects

Conversation

@yannickmonney

@yannickmonney yannickmonney commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a Projects feature to the Tale platform — analogous to Claude/ChatGPT Projects but adapted to Tale's team-based sharing. A project is a named, optionally team-shared container inside an organization that bundles a knowledge-file set, an instruction block, a curated subset of agents/models, and a chat space.

Built almost entirely on top of existing patterns:

  • No new sharing primitive (reuses teamId + sharedWithTeamIds)
  • No new RAG index (unions documents.projectId rows into the existing file_ids scope)
  • No new file pipeline (reuses the documents pipeline)
  • No new prompt-cache schema (block is identical across project members → cache-friendly)

What you can do

  • Create a project, give it a name, share it with one or more teams (or org-wide)
  • Attach documents — they're indexed once, retrievable from every chat opened in the project
  • Write a ≤ 6000-char instruction block — XML-injected into the system prompt of every chat in the project, between agent guardrails and user personalization
  • Start chats from the project — the new thread is tagged with projectId, project instructions + files are applied automatically
  • Share a chat with project members — atomically disables personalization so memories/custom instructions don't leak
  • Restrict which agents/models project members can pick (all / recommended / restricted tristate)
  • Archive (recoverable) / delete (detach or cascade, with confirm-phrase)

High-risk surface

PR 5 of the original 10-PR plan (chat path plumbing + project instructions injection) lives in this single PR. The risk is mitigated by:

  • Non-project chats are byte-identical — every project branch is gated on projectId !== undefined
  • Server-side defense-in-depthchatWithAgent calls assertProjectAccessForChat (PROJECT_FORBIDDEN / PROJECT_NOT_FOUND on denial); startChat enforces PROJECT_MISMATCH on cross-project continuation
  • Prompt-injection defensesanitizeForPromptInjection strips reserved tags (<system>, <user_*>, <governance_*>, <project_*>, nonce attrs) + XML-escapes content; 12 unit tests cover every escape attempt
  • Privacy cascadesetThreadSharedWithProject(true) atomically force-sets disablePersonalization: true (mirrors share_thread.ts:54)
  • Retention guardshouldDeferProjectSharedExpiry pure helper defers hard-delete of sharedWithProject trashed threads while the project is active

Stats

  • 69 files changed, +7,302 / -24 lines
  • 51 new unit tests across 11 test files (139 project-related tests total)
  • Full suite: 73,026 passed / 0 failed (was 72,975)
  • Typecheck clean, oxlint clean (0 warnings, 0 errors across 2,716 files)

Test plan

  • bun run check — lint + typecheck + tests all green
  • bunx vitest run — 73,026 tests pass
  • Schema migration safe (all new fields optional, no backfill needed)
  • CASL + Better Auth + RLS aligned: 'projects' subject in ability.ts, convex/auth.ts role matrix, access_control.ts permissions, rls_rules.ts table rules — all updated together
  • Error-code consistency test verifies every thrown ConvexError({ code }) has a matching projects.errors.<code> i18n key
  • getAgentScopedFileIds test covers project union + mutual-exclusivity (no leak via includeOrgKnowledge)
  • sanitizeForPromptInjection test covers every reserved-tag escape attempt
  • assembleProjectInstructionsBlock test covers nonce determinism, footer presence, truncation, cache-friendliness (no user identity in wrapper)
  • shouldDeferProjectSharedExpiry test covers all combinations of project existence, archivedAt, and graceCutoffMs
  • ProjectAvatar + ProjectModeRadio pass axe a11y audits
  • Manual QA — create project end-to-end on a deploy
  • coderabbit:code-review pass
  • /ultrareview on the chat-runtime change (project-instructions injection)

Deferred to follow-ups

  • Translations for de/fr/de-CH (currently English fallbacks; terminology skill review before customer release)
  • ProjectPicker + "Move to project" hover action in chat-history-sidebar
  • Composer chip surfacing project context inside ChatInput
  • Recommended/allowed list editors in the Agents & Models tab (UI tristate radio works; the actual slug-list editors are stubbed)
  • Documents library "Project" column + filter
  • TanStack DB collection migration (memory note was outdated; codebase uses useConvexQuery directly — adopted that pattern)
  • Mobile polish on detail tabs (works but not tuned for ≤320 px)

Plan reference

~/.claude/plans/we-would-like-to-sunny-thunder.md — 34-section plan with the original 10-PR phasing, ASCII wireframes, error catalog, risk register, success KPIs.

Summary by CodeRabbit

New Features

  • Project Management System: Create, configure, and manage projects with organization-wide or team-specific sharing.
  • Project-Scoped Chat: Associate conversations with projects; access project-specific instructions, knowledge base, and configured agents/models.
  • Project Settings & Controls: Customize project identity (name, description, icon, color), manage instructions, control knowledge indexing, restrict available agents/models, and archive or delete projects.
  • Project Organization: Share chats with projects, manage project files and knowledge base, and view project activity overview.

Review Change Stack

… threads, agents

Adds a Projects feature analogous to Claude/ChatGPT Projects but adapted to Tale's
team-based sharing. A project is a named, optionally team-shared container inside
an organization that bundles a knowledge-file set, an instruction block, a curated
subset of agents/models, and a chat space. Built almost entirely on top of existing
patterns — no new sharing primitive, no new RAG index, no new file pipeline.

Backend
- New `projects` table (org-scoped, 3 indexes + search index on name)
- Extends `threadMetadata` with `projectId` + `sharedWithProject` (2 indexes)
- Extends `documents` with `projectId` (1 index); mutual-exclusivity with `teamId`
  enforced server-side — `attachDocumentToProject` throws `DOCUMENT_SCOPE_CONFLICT`
  if the document already belongs to a team library
- `projects/access.ts` + 25-case unit-test matrix (admin override, org-wide,
  team-scoped, shared-team paths)
- Zod schemas (icon allowlist, color palette, char/token caps) in
  `lib/shared/schemas/projects.ts` + 30 unit tests
- 15 mutations (create/update identity/instructions/sharing/knowledgeMode/
  agents/models/integrations/file attach+detach/thread move+share/archive/restore/
  delete with detach OR cascade mode + confirm-phrase), all gated by access helpers
  and writing audit-log entries via the existing hash-chain
- 9 queries (list, get, stats, listDocuments, listThreads, search, sidebar, +2
  internal queries for chat-path injection and access-for-chat)
- CASL `'projects'` subject + Better Auth role matrix (`convex/auth.ts`) + RLS
  rules (`access_control.ts`, `rls_rules.ts`)

Chat runtime (high-risk path, byte-identical for no-project chats)
- `chatWithAgent` accepts optional `projectId`; defense-in-depth via
  `assertProjectAccessForChat` (org + role + team match); throws
  `PROJECT_FORBIDDEN` / `PROJECT_NOT_FOUND` on denial
- `startChat` persists `projectId` on `threadMetadata`; throws `PROJECT_MISMATCH`
  on cross-project continuation
- New `buildProjectInstructions` helper assembles an XML-wrapped, nonce-tagged,
  token-budgeted (~1200 tok) block; injected via `build_system_prompt.ts` between
  agent guardrails and user personalization (cache-friendly across project members)
- Prompt-injection defense: `sanitizeForPromptInjection` strips reserved tags
  (`<system>`, `<user_*>`, `<governance_*>`, `<project_*>`, nonce attrs) + XML-escapes
  remaining content; 12 unit tests cover every documented escape attempt
- `getAgentScopedFileIds` extended with `agentProjectIds` — unions project docs
  into RAG file scope. Mutual-exclusivity branch ensures project docs never leak
  via `includeOrgKnowledge`; 3 new unit tests
- Privacy: `setThreadSharedWithProject(true)` atomically force-sets
  `disablePersonalization: true` (mirror of `share_thread.ts:54`); conservative
  un-share keeps memories silenced until owner manually re-enables
- Retention cron: pure helper `shouldDeferProjectSharedExpiry` defers hard-delete
  of `sharedWithProject` trashed threads while the project is active (8 unit tests)

Frontend
- New routes under `/dashboard/$id/projects/` + 6 tab routes (overview, files,
  instructions, threads, agents, settings) under `$projectId/`
- 11 components in `app/features/projects/components/`: list table, create dialog,
  detail layout, overview, settings (with detach/cascade delete dialog), files tab,
  instructions editor (autosave + token counter), threads tab (share toggle +
  privacy disclosure), agents/models tab with tristate mode radio, avatar
- 7 hook modules incl. `useEffectiveProjectConfig` (RBAC ∩ project restrictions,
  8 unit tests) and `asProjectId` (centralized branded-Id coercion)
- Sidebar nav entry (gated `can('read', 'projects')`)
- `chat-interface.tsx` reads `projectId` from URL search params; `use-send-message.ts`
  forwards to `chatWithAgent` so new chats from a project page are tagged correctly

i18n
- New `projects.*` namespace in `en.json` (~210 keys: nav, list, create, overview,
  instructions, files, threads, agents, settings, composer, picker, errors, audit log)
- Stubbed in de/fr/de-CH for the terminology skill review before customer release
- `keys-dynamic.txt` covers dynamic error-code lookups + UI keys staged for Phase 7
- Error-code consistency test enforces every thrown `ConvexError({ code })` has a
  matching `projects.errors.<code>` i18n key (and vice versa)

Testing
- 51 new unit tests across 11 test files (139 project-related tests total)
- Full suite: 73,026 passed / 0 failed (was 72,975)
- Typecheck clean, oxlint clean
- Component a11y audited via `checkAccessibility` on ProjectAvatar + ProjectModeRadio

Deferred (follow-ups)
- Translations for de/fr/de-CH (currently English fallbacks; `terminology` skill
  review before customer release)
- ProjectPicker + Move-to-project hover action in chat-history-sidebar
- Composer chip surfacing project context inside ChatInput
- Recommended/allowed list editors in Agents & Models tab
- Documents library "Project" column + filter
- TanStack DB collection migration (memory note was outdated; codebase uses
  useConvexQuery directly)
@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a full Projects feature: database schema, queries, mutations, access control, and UI (list, overview, settings, instructions, files, agents, threads). Introduces routes under /dashboard/$id/projects and project subroutes. Threads and documents can link to projects. Chat flow accepts optional projectId from URL, validates access, persists on thread, scopes RAG files, and injects project instructions into system prompts. Updates i18n, permissions, and retention logic for project-shared threads. Includes comprehensive tests.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • Israeltheminer
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/platform-projects

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 22

Caution

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

⚠️ Outside diff range comments (2)
services/platform/app/features/chat/hooks/use-send-message.ts (1)

509-526: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Arena path drops projectId and can create unscoped threads.

projectId is forwarded in standard mode, but not in the arena branch. New arena threads/messages can be created without project linkage, which breaks project-scoped access/privacy behavior for “new chat in project” flows.

Suggested fix direction
 const newA = await createThread({
   organizationId,
   title,
   chatType: 'general',
   arenaGroupId,
   arenaModelId: modelA,
   teamId,
+  projectId: projectId ? asProjectId(projectId) : undefined,
 });
 const newB = await createThread({
   organizationId,
   title,
   chatType: 'general',
   arenaGroupId,
   arenaModelId: modelB,
   isBranch: true,
   forkedFrom: newA,
   teamId,
+  projectId: projectId ? asProjectId(projectId) : undefined,
 });

 await arenaChatRef.current({
   agentSlug: selectedAgent.name,
   threadIdA: tIdA,
   threadIdB: tIdB,
   organizationId,
   message: messageToSend,
   modelIdA: modelA,
   modelIdB: modelB,
+  projectId: projectId ? asProjectId(projectId) : undefined,
   attachments: mutationAttachments,
   userContext: userContext

Also applies to: 606-623

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/app/features/chat/hooks/use-send-message.ts` around lines
509 - 526, The arena branch createThread call is missing the projectId, allowing
unscoped threads; update both createThread invocations (the one that creates
newA and the one that creates newB/isBranch) to pass the same projectId used in
standard mode (include projectId in the argument object for newA and newB), and
ensure forkedFrom still points to the created thread identifier (forkedFrom:
newA or newA.id as appropriate to surrounding code) so arena threads remain
project-scoped and linked.
services/platform/convex/lib/agent_response/generate_response.ts (1)

939-944: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include project-instructions tokens in context accounting.

projectInstructionsBlock.text is now part of every system prompt, but context token stats are still computed from instructions + tools only. This under-reports prompt size in metadata/perf outputs.

📌 Proposed fix
@@
-          if (structuredThreadContext) {
+          if (structuredThreadContext) {
             const parts = [];
             if (agentInstructions) {
               parts.push(wrapInDetails('📋 System Prompt', agentInstructions));
             }
             if (toolsSummary) {
               parts.push(wrapInDetails('🔧 Tools', toolsSummary));
             }
             parts.push(structuredThreadContext.threadContext);
             cancelContextWindow = parts.join('\n\n');
 
             const sysTokens = instructions ? estimateTokens(instructions) : 0;
+            const projectTokens = projectInstructionsBlock.text
+              ? estimateTokens(projectInstructionsBlock.text)
+              : 0;
             const toolTokens = toolsSummary ? estimateTokens(toolsSummary) : 0;
             cancelContextStats = {
               ...structuredThreadContext.stats,
               totalTokens:
                 structuredThreadContext.stats.totalTokens +
                 sysTokens +
+                projectTokens +
                 toolTokens,
             };
           }
@@
-    const systemPromptTokens = instructions ? estimateTokens(instructions) : 0;
+    const systemPromptTokens = instructions ? estimateTokens(instructions) : 0;
+    const projectInstructionsTokens = projectInstructionsBlock.text
+      ? estimateTokens(projectInstructionsBlock.text)
+      : 0;
     const toolsTokens = toolsSummary ? estimateTokens(toolsSummary) : 0;
     const contextStats = {
       ...structuredThreadContext.stats,
       totalTokens:
         structuredThreadContext.stats.totalTokens +
         systemPromptTokens +
+        projectInstructionsTokens +
         toolsTokens,
     };

Also applies to: 1401-1406, 1640-1645

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/convex/lib/agent_response/generate_response.ts` around
lines 939 - 944, The system prompt now includes projectInstructionsBlock.text
via buildSystemPrompt (see systemPrompt), but token accounting still only sums
instructions + tools, so context token stats under-report; update the
token-counting logic to also include projectInstructionsBlock.text when
computing context/perf metadata (where instructions and tools are currently
used), and apply the same change to the other occurrences referenced (the
equivalent computations around lines 1401-1406 and 1640-1645) so all context
token stats reflect instructions + tools + projectInstructionsBlock.text.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@services/platform/app/features/projects/components/project-agents-tab.tsx`:
- Around line 32-37: State for agentMode and modelMode can diverge from the
server-loaded project because useState only sets the initial value; add a
useEffect that watches project (or project?.agentMode/project?.modelMode) and
calls setAgentMode(project?.agentMode as ProjectModeRadioValue ?? 'all') and
setModelMode(project?.modelMode as ProjectModeRadioValue ?? 'all') so the radio
state always syncs when the project is loaded or refetched; reference the
existing agentMode, setAgentMode, modelMode, setModelMode and
ProjectModeRadioValue identifiers when implementing the effect.

In
`@services/platform/app/features/projects/components/project-create-dialog.tsx`:
- Line 52: The description Zod validator uses the default English error text;
change the schema line "description: z.string().trim().max(500).optional()" to
supply a localized message via .max(500, { message:
t('projects.errors.descriptionTooLong') }) (or i18n.t(...)) and ensure the
schema is created inside the component or scope where the translation function
(useTranslation/useI18n) is available so t can be passed in; update any places
that construct the schema to import or call useTranslation before building the
Zod schema so the validation message is localized.

In `@services/platform/app/features/projects/components/project-files-tab.tsx`:
- Line 76: Replace the hardcoded fallback 'document' in the ProjectFilesTab
render (the expression {doc.title ?? doc.extension ?? 'document'}) with a
translation lookup via the component's useT hook; import/use useT(namespace) in
the ProjectFilesTab component (or reuse existing t if already present) and
change the fallback to t('document') or the appropriate namespace key (e.g.,
t('projectFiles.document')) so the UI string flows through i18n.

In
`@services/platform/app/features/projects/components/project-mode-radio.test.tsx`:
- Around line 12-22: The test currently hardcodes English labels/descriptions
(the options array in project-mode-radio.test.tsx) and assertions—replace these
literals with values produced by the app's translation layer (use the same i18n
helper used in the project, e.g. i18n.t or useTranslation hook) when building
fixtures and when asserting text for the ProjectModeRadio tests; update the
array entries (value/label/description) to use translated strings, update
assertions to call the translation helper instead of matching English literals,
and remove/comment text that repeats English literals (including the other
occurrences called out) so no test or comment compares against raw English
strings.
- Around line 45-46: Replace the unnecessary TypeScript casts to
HTMLInputElement in the test for ProjectModeRadio and use Jest DOM assertions
instead: locate the assertions where `recommended` (and the other radio element
around lines 79-80) are being cast like `) as HTMLInputElement` and change the
checks to use `expect(recommended).toBeChecked()` (or `toBeDisabled()` for the
disabled case) so the test asserts the actual rendered <input type="radio">
state via toBeChecked()/toBeDisabled() rather than casting to HTMLInputElement.

In `@services/platform/app/features/projects/components/project-overview.tsx`:
- Around line 174-175: The element with className "hover:bg-muted/50 -mx-2 flex
items-center gap-2 rounded px-2 py-1.5 text-sm transition-colors" needs a
reduced-motion override; update the className in the ProjectOverview component
(the element using that className) to include the Tailwind motion-reduce
utility, e.g. add "motion-reduce:transition-none" alongside "transition-colors"
so transitions are disabled when prefers-reduced-motion is enabled.
- Around line 45-46: Remove the unsafe cast and handle reduced-motion: replace
the inline "search: { projectId: String(projectId) } as Record<string, string>"
by building a properly typed search object (e.g. const search: { projectId:
string } = { projectId: String(projectId) }; then pass search into navigate) so
you avoid using "as Record<string,string>" and ensure projectId is stringified
safely; and update the thread <Link> element that currently uses
"transition-colors" to include a reduced-motion override such as adding
"motion-reduce:transition-none" (or "motion-reduce:transition-none
motion-reduce:duration-0") to the Link's className to disable the color
transition for users who prefer reduced motion.

In `@services/platform/app/features/projects/components/project-settings-tab.tsx`:
- Around line 168-170: The Button in the ProjectSettingsTab currently uses the
toast key t('settings.saveSuccess') as its label; change it to a proper action
label key (e.g. t('settings.saveButton')) used for the Button rendering inside
the JSX where saveIdentity is passed to onClick so the UI shows an action label
rather than a success message. Locate the Button component that calls
onClick={saveIdentity} and replace the translation key used in the content with
the new button-label key (update any tests or i18n resource files to include the
new settings.saveButton key as needed).

In `@services/platform/app/routes/dashboard/`$id/projects/$projectId.tsx:
- Around line 84-86: The ARIA label currently passes a hardcoded English
fallback via defaultValue to the translation call; remove the fallback so the
JSX uses the translation key only (e.g., change
ariaLabel={tCommon('aria.projectsNavigation', { defaultValue: 'Projects
navigation' })} to ariaLabel={tCommon('aria.projectsNavigation')} ), and do the
same for the similar tCommon usages later (the other occurrence around the
projects/navigation JSX). Ensure the translation keys (aria.projectsNavigation
etc.) exist in the i18n resources instead of supplying an inline English string.

In `@services/platform/convex/agents/unified_chat.ts`:
- Around line 114-139: The project access check using
ctx.runQuery(internal.projects.internal_queries.assertProjectAccessForChat, ...)
can throw and leave generation state set; wrap the runQuery call in a try/catch
so that on any thrown error you call
ctx.runMutation(internal.threads.internal_mutations.clearGenerationStatus, {
threadId: args.threadId, streamId: preAllocatedStreamId }) to rollback the
generation status, then rethrow the original error; keep the existing branch
that handles projectAccess.allowed as-is (it already clears and throws).

In `@services/platform/convex/documents/get_agent_scoped_file_ids.ts`:
- Around line 65-79: The team-scope branch can include rows that have both
teamId and projectId when the project doesn't match; update the team branch
condition (the if that checks needsTeamDocs && doc.teamId &&
agentTeamIdSet.has(doc.teamId)) to also require !doc.projectId so project-scoped
docs are excluded from team-only retrieval — change the condition to include &&
!doc.projectId and keep the existing fileIdSet.add(fileId); continue; behavior.

In `@services/platform/convex/lib/rls/helpers/rls_rules.ts`:
- Around line 120-150: The projects RLS rules (projects.read, projects.modify,
projects.insert) only check org membership and role via userOrgIds,
userOrganizations and authorizeRls, which bypasses team-scoped restrictions;
update each of these predicates to also call the project-scoped access predicate
used in projects/access.ts (e.g., hasProjectAccess or the exported project-scope
helper) with the current user (ruleUser for insert, user for read/modify) and
the project to enforce team membership before returning authorizeRls, so row
visibility requires both org-role and project/team membership.

In `@services/platform/convex/projects/access.ts`:
- Around line 61-74: The hasProjectAccess helper currently grants org-wide
access for any non-admin role which lets a 'disabled' role get access; modify
hasProjectAccess to explicitly check for the disabled role (e.g., if (userRole
=== 'disabled') return false) before the ADMIN_ROLES check or before the
org-wide/project-team checks, and apply the same "fail-closed for disabled"
guard to the sibling helper that computes { canRead: true } (the helper that
consumes getProjectTeamIds and returns read access) so that disabled always
returns false regardless of org-wide/team membership.

In `@services/platform/convex/projects/mutations.ts`:
- Around line 999-1007: The code currently calls .collect() on Convex query
cursors (e.g., the documents query:
ctx.db.query('documents').withIndex('by_organizationId_and_projectId', ...)
.collect()) which loads entire result sets into memory; replace those .collect()
usages in the deletion flow with streaming iteration using for await loops
directly over the query cursor (e.g., for await (const doc of
ctx.db.query('documents').withIndex(...)) { /* delete/process doc here */ }),
doing the delete operations inside the loop; apply the same change to the other
collected query in this function (the second .collect() in the project-scoped
delete path between lines ~1026-1033) so no result set is fully materialized.

In `@services/platform/convex/projects/queries.ts`:
- Around line 35-37: The current code calls authComponent.getAuthUser(ctx) and
throws on falsy, which turns read queries into hard failures; instead, in the
query helper (the function containing the line const authUser = await
authComponent.getAuthUser(ctx)), swallow authentication failures from
authComponent.getAuthUser(ctx) and return the appropriate unauthenticated
sentinel (null or an empty array/object consistent with the calling query)
rather than throwing; replace the throw new Error('Unauthenticated') with a
guarded return (or try/catch around authComponent.getAuthUser to catch errors
and return null/empty) so callers receive an unauthenticated value instead of an
exception.
- Around line 100-107: Replace the use of .collect() on the Convex query result
with an asynchronous iterator pattern: get the query iterator returned by
ctx.db.query("projects").withIndex("by_organization_updatedAt", q =>
q.eq("organizationId", args.organizationId)).order("desc"), then iterate with
for await (const row of iterator) to build the result array (and break early if
you have a cap) instead of materializing the entire set with .collect(); apply
the same change to the other project-list/stats queries in this file that call
.collect() (the other queries using ctx.db.query("projects") with different
indexes).

In `@services/platform/lib/shared/schemas/projects.test.ts`:
- Around line 257-268: Add a failing regression test that asserts
deleteProjectInputSchema rejects a cascade delete when confirmPhrase is missing:
create a new it block (e.g., "rejects cascade without confirmation phrase") that
calls deleteProjectInputSchema.parse({ mode: 'cascade' }) and expects it to
throw, ensuring the destructive-delete contract is locked down.

In `@services/platform/lib/shared/schemas/projects.ts`:
- Around line 89-230: Several constants and schema/type exports (e.g.,
PROJECT_NAME_MAX, PROJECT_DESCRIPTION_MAX, PROJECT_INSTRUCTIONS_MAX_CHARS,
PROJECT_INSTRUCTIONS_TOKEN_BUDGET, PROJECT_SHARED_TEAMS_MAX,
PROJECT_RECOMMENDED_AGENTS_MAX, PROJECT_ALLOWED_AGENTS_MAX,
PROJECT_RECOMMENDED_MODELS_MAX, PROJECT_ALLOWED_MODELS_MAX,
PROJECT_ALLOWED_INTEGRATIONS_MAX and any schema/type declarations not referenced
externally) are publicly exported but reported unused; make them internal by
removing the export modifier or moving them out of the public module (or delete
if truly unnecessary) and only export the symbols actually consumed by other
modules (leave public exports like createProjectInputSchema/updateProject*Schema
etc. that are used); after changes, run the linter/knip and tests to confirm no
external usage breaks.
- Around line 226-229: The schema deleteProjectInputSchema currently allows
confirmPhrase to be optional for all modes so { mode: 'cascade' } passes; update
the Zod schema to require confirmPhrase only when mode === 'cascade' by using a
refinement or .superRefine on deleteProjectInputSchema (or transform it into a
discriminated union on mode) to validate that confirmPhrase is present and
non-empty for the 'cascade' branch, keeping it optional for 'detach'.

In `@services/platform/messages/de-CH.json`:
- Around line 257-467: The new "projects" namespace in de-CH (keys under
"projects" including "title", "navigation", "list", "create", "overview",
"instructions", "files", "threads", "agents", "settings", "composer", "picker",
and "errors") contains English placeholder copy; replace every English string
with Swiss German translations using sentence case and the informal form ("du"),
ensuring error messages (e.g., "PROJECT_NOT_FOUND", "PROJECT_FORBIDDEN", etc.)
and UI labels ("New project", "Create project", "Add file", "New chat in this
project", "Create your first project") are localized consistently; run the
translations through existing i18n reviewers or translation pipeline and update
the values in the de-CH JSON so the locale no longer contains English
placeholders.

In `@services/platform/messages/de.json`:
- Around line 5213-5423: Translate the entire "projects" JSON namespace into
German using informal tone ("du"), sentence case, and match existing locale
conventions; update all keys under projects (e.g., navigation, list, create,
overview, instructions, files, threads, agents, settings, composer, picker,
errors) so UI strings and error messages are full German translations and
preserve interpolation tokens like {count}, {cap}, {name}, {agentName},
{modelName}, {when} exactly as-is.

In `@services/platform/messages/fr.json`:
- Around line 5213-5423: The projects namespace is still in English; translate
every string under "projects" into French (preserve placeholders like {count},
{cap}, {when}, and tokens such as "Projects", "create.title",
"list.navigation.list", "overview.getStartedFilesBody",
"instructions.placeholder", "files.ragStatusRunning",
"threads.shareToggleDisclosure", "agents.modeRestrictedDescription",
"settings.saveError", "composer.chipLabel", "picker.createNew", and all keys
under "errors") using sentence case and the informal tone for French locale;
ensure pluralization and interpolation formats remain unchanged and that strings
like "New project", "Create project", "Couldn't save settings", "No projects
yet" are replaced with proper French equivalents throughout the entire
"projects" block.

---

Outside diff comments:
In `@services/platform/app/features/chat/hooks/use-send-message.ts`:
- Around line 509-526: The arena branch createThread call is missing the
projectId, allowing unscoped threads; update both createThread invocations (the
one that creates newA and the one that creates newB/isBranch) to pass the same
projectId used in standard mode (include projectId in the argument object for
newA and newB), and ensure forkedFrom still points to the created thread
identifier (forkedFrom: newA or newA.id as appropriate to surrounding code) so
arena threads remain project-scoped and linked.

In `@services/platform/convex/lib/agent_response/generate_response.ts`:
- Around line 939-944: The system prompt now includes
projectInstructionsBlock.text via buildSystemPrompt (see systemPrompt), but
token accounting still only sums instructions + tools, so context token stats
under-report; update the token-counting logic to also include
projectInstructionsBlock.text when computing context/perf metadata (where
instructions and tools are currently used), and apply the same change to the
other occurrences referenced (the equivalent computations around lines 1401-1406
and 1640-1645) so all context token stats reflect instructions + tools +
projectInstructionsBlock.text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8648a04c-e745-4454-b678-712b8952f51e

📥 Commits

Reviewing files that changed from the base of the PR and between 97f5b8d and 36b034f.

⛔ Files ignored due to path filters (1)
  • services/platform/convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (68)
  • services/platform/app/features/chat/components/chat-interface.tsx
  • services/platform/app/features/chat/hooks/use-send-message.ts
  • services/platform/app/features/projects/components/project-agents-tab.tsx
  • services/platform/app/features/projects/components/project-avatar.test.tsx
  • services/platform/app/features/projects/components/project-avatar.tsx
  • services/platform/app/features/projects/components/project-create-dialog.tsx
  • services/platform/app/features/projects/components/project-files-tab.tsx
  • services/platform/app/features/projects/components/project-instructions-editor.tsx
  • services/platform/app/features/projects/components/project-mode-radio.test.tsx
  • services/platform/app/features/projects/components/project-mode-radio.tsx
  • services/platform/app/features/projects/components/project-overview.tsx
  • services/platform/app/features/projects/components/project-settings-tab.tsx
  • services/platform/app/features/projects/components/project-threads-tab.tsx
  • services/platform/app/features/projects/components/projects-table.tsx
  • services/platform/app/features/projects/hooks/mutations.ts
  • services/platform/app/features/projects/hooks/queries.ts
  • services/platform/app/features/projects/hooks/use-effective-project-config.test.ts
  • services/platform/app/features/projects/hooks/use-effective-project-config.ts
  • services/platform/app/features/projects/hooks/use-project-id-param.test.ts
  • services/platform/app/features/projects/hooks/use-project-id-param.ts
  • services/platform/app/hooks/use-navigation-items.ts
  • services/platform/app/routeTree.gen.ts
  • services/platform/app/routes/dashboard/$id/projects/$projectId.tsx
  • services/platform/app/routes/dashboard/$id/projects/$projectId/agents.tsx
  • services/platform/app/routes/dashboard/$id/projects/$projectId/files.tsx
  • services/platform/app/routes/dashboard/$id/projects/$projectId/index.tsx
  • services/platform/app/routes/dashboard/$id/projects/$projectId/instructions.tsx
  • services/platform/app/routes/dashboard/$id/projects/$projectId/settings.tsx
  • services/platform/app/routes/dashboard/$id/projects/$projectId/threads.tsx
  • services/platform/app/routes/dashboard/$id/projects/index.tsx
  • services/platform/convex/agent_tools/rag/rag_search_tool.ts
  • services/platform/convex/agents/start_chat.ts
  • services/platform/convex/agents/unified_chat.ts
  • services/platform/convex/auth.ts
  • services/platform/convex/documents/get_agent_scoped_file_ids.test.ts
  • services/platform/convex/documents/get_agent_scoped_file_ids.ts
  • services/platform/convex/documents/internal_queries.ts
  • services/platform/convex/documents/schema.ts
  • services/platform/convex/governance/internal_queries.ts
  • services/platform/convex/governance/retention_project_shared.test.ts
  • services/platform/convex/governance/retention_project_shared.ts
  • services/platform/convex/lib/agent_chat/internal_actions.ts
  • services/platform/convex/lib/agent_response/build_project_instructions.test.ts
  • services/platform/convex/lib/agent_response/build_project_instructions.ts
  • services/platform/convex/lib/agent_response/build_system_prompt.ts
  • services/platform/convex/lib/agent_response/generate_response.ts
  • services/platform/convex/lib/agent_response/types.ts
  • services/platform/convex/lib/rls/helpers/access_control.ts
  • services/platform/convex/lib/rls/helpers/rls_rules.ts
  • services/platform/convex/projects/access.test.ts
  • services/platform/convex/projects/access.ts
  • services/platform/convex/projects/assert_project_access_for_chat.test.ts
  • services/platform/convex/projects/audit_actions.ts
  • services/platform/convex/projects/error_codes.test.ts
  • services/platform/convex/projects/internal_queries.ts
  • services/platform/convex/projects/mutations.ts
  • services/platform/convex/projects/queries.ts
  • services/platform/convex/projects/schema.ts
  • services/platform/convex/schema.ts
  • services/platform/convex/threads/schema.ts
  • services/platform/lib/i18n/keys-dynamic.txt
  • services/platform/lib/permissions/ability.ts
  • services/platform/lib/shared/schemas/projects.test.ts
  • services/platform/lib/shared/schemas/projects.ts
  • services/platform/messages/de-CH.json
  • services/platform/messages/de.json
  • services/platform/messages/en.json
  • services/platform/messages/fr.json

Comment thread services/platform/app/features/projects/components/project-agents-tab.tsx Outdated
Comment thread services/platform/app/features/projects/components/project-files-tab.tsx Outdated
Comment on lines +12 to +22
{ value: 'all', label: 'All', description: 'Everyone sees everything.' },
{
value: 'recommended',
label: 'Recommended',
description: 'Pin some to the top.',
},
{
value: 'restricted',
label: 'Restricted',
description: 'Allowlist only.',
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove English literals from test fixtures and assertions.

This test hardcodes user-facing English labels/legend and matches against English role names. Use translated strings via the translation layer in tests as well (and avoid English literals in comments tied to assertions). As per coding guidelines, “Every user-facing string goes through the translation layer … never compare against English literals in code, tests, stories, or comments.”

Also applies to: 30-33, 43-45, 64-65, 83-93

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/platform/app/features/projects/components/project-mode-radio.test.tsx`
around lines 12 - 22, The test currently hardcodes English labels/descriptions
(the options array in project-mode-radio.test.tsx) and assertions—replace these
literals with values produced by the app's translation layer (use the same i18n
helper used in the project, e.g. i18n.t or useTranslation hook) when building
fixtures and when asserting text for the ProjectModeRadio tests; update the
array entries (value/label/description) to use translated strings, update
assertions to call the translation helper instead of matching English literals,
and remove/comment text that repeats English literals (including the other
occurrences called out) so no test or comment compares against raw English
strings.

Comment on lines +89 to +230
export const projectIntegrationsModeSchema = z.enum(['all', 'restricted']);
export type ProjectIntegrationsMode = z.infer<
typeof projectIntegrationsModeSchema
>;

/**
* Hard caps mirrored on the Convex mutation boundary. Token budget for
* instructions is ~1200; using 6000 chars as a generous overhead since
* a token is typically 4 chars in English. Truncation to the token
* budget happens at chat-time in `buildProjectInstructions`.
*/
export const PROJECT_NAME_MAX = 80;
export const PROJECT_DESCRIPTION_MAX = 500;
export const PROJECT_INSTRUCTIONS_MAX_CHARS = 6000;
export const PROJECT_INSTRUCTIONS_TOKEN_BUDGET = 1200;
export const PROJECT_SHARED_TEAMS_MAX = 20;
export const PROJECT_RECOMMENDED_AGENTS_MAX = 20;
export const PROJECT_ALLOWED_AGENTS_MAX = 50;
export const PROJECT_RECOMMENDED_MODELS_MAX = 10;
export const PROJECT_ALLOWED_MODELS_MAX = 50;
export const PROJECT_ALLOWED_INTEGRATIONS_MAX = 50;

const projectNameSchema = z.string().trim().min(1).max(PROJECT_NAME_MAX);

const projectDescriptionSchema = z.string().trim().max(PROJECT_DESCRIPTION_MAX);

const projectInstructionsSchema = z
.string()
.max(PROJECT_INSTRUCTIONS_MAX_CHARS);

const teamIdSchema = z.string().min(1);
const sharedWithTeamIdsSchema = z
.array(teamIdSchema)
.max(PROJECT_SHARED_TEAMS_MAX);

const agentSlugSchema = z
.string()
.min(1)
.max(120)
.regex(/^[a-z0-9][a-z0-9_-]*$/);

const modelRefSchema = z.string().min(1).refine(isValidModelRef, {
message: 'Invalid model ref (expected "[provider:]model-id")',
});

const integrationSlugSchema = z
.string()
.min(1)
.max(120)
.regex(/^[a-z0-9][a-z0-9_-]*$/);

export const createProjectInputSchema = z.object({
organizationId: z.string().min(1),
name: projectNameSchema,
description: projectDescriptionSchema.optional(),
icon: projectIconSchema.optional(),
color: projectColorSchema.optional(),
teamId: teamIdSchema.optional(),
sharedWithTeamIds: sharedWithTeamIdsSchema.optional(),
});
export type CreateProjectInput = z.infer<typeof createProjectInputSchema>;

export const updateProjectIdentitySchema = z
.object({
name: projectNameSchema,
description: projectDescriptionSchema,
icon: projectIconSchema.nullable(),
color: projectColorSchema.nullable(),
})
.partial();
export type UpdateProjectIdentityInput = z.infer<
typeof updateProjectIdentitySchema
>;

export const updateProjectInstructionsSchema = z.object({
instructions: projectInstructionsSchema,
});
export type UpdateProjectInstructionsInput = z.infer<
typeof updateProjectInstructionsSchema
>;

export const updateProjectSharingSchema = z.object({
teamId: teamIdSchema.nullable().optional(),
sharedWithTeamIds: sharedWithTeamIdsSchema.optional(),
});
export type UpdateProjectSharingInput = z.infer<
typeof updateProjectSharingSchema
>;

export const updateProjectKnowledgeModeSchema = z.object({
knowledgeMode: projectKnowledgeModeSchema,
});
export type UpdateProjectKnowledgeModeInput = z.infer<
typeof updateProjectKnowledgeModeSchema
>;

export const updateProjectAgentSettingsSchema = z.object({
agentMode: projectModeSchema,
recommendedAgentSlugs: z
.array(agentSlugSchema)
.max(PROJECT_RECOMMENDED_AGENTS_MAX)
.optional(),
allowedAgentSlugs: z
.array(agentSlugSchema)
.max(PROJECT_ALLOWED_AGENTS_MAX)
.optional(),
});
export type UpdateProjectAgentSettingsInput = z.infer<
typeof updateProjectAgentSettingsSchema
>;

export const updateProjectModelSettingsSchema = z.object({
modelMode: projectModeSchema,
recommendedModels: z
.array(modelRefSchema)
.max(PROJECT_RECOMMENDED_MODELS_MAX)
.optional(),
allowedModels: z
.array(modelRefSchema)
.max(PROJECT_ALLOWED_MODELS_MAX)
.optional(),
});
export type UpdateProjectModelSettingsInput = z.infer<
typeof updateProjectModelSettingsSchema
>;

export const updateProjectIntegrationsSchema = z.object({
integrationsMode: projectIntegrationsModeSchema,
allowedIntegrationSlugs: z
.array(integrationSlugSchema)
.max(PROJECT_ALLOWED_INTEGRATIONS_MAX)
.optional(),
});
export type UpdateProjectIntegrationsInput = z.infer<
typeof updateProjectIntegrationsSchema
>;

export const deleteProjectInputSchema = z.object({
mode: z.enum(['detach', 'cascade']),
confirmPhrase: z.string().optional(),
});
export type DeleteProjectInput = z.infer<typeof deleteProjectInputSchema>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Trim or internalize currently unused exports in this module.

Knip reports several exported constants/schemas/types here as unused. Either consume them now, or make non-public ones internal until needed to keep the module surface intentional.

As per coding guidelines, "Export only what other modules use; private helpers stay private."

🧰 Tools
🪛 GitHub Actions: Lint / 3_Knip.txt

[warning] 89-89: knip: Unused exports detected: projectIntegrationsModeSchema


[warning] 103-103: knip: Unused exports detected: PROJECT_INSTRUCTIONS_TOKEN_BUDGET


[warning] 106-106: knip: Unused exports detected: PROJECT_ALLOWED_AGENTS_MAX


[warning] 107-107: knip: Unused exports detected: PROJECT_RECOMMENDED_MODELS_MAX


[warning] 108-108: knip: Unused exports detected: PROJECT_ALLOWED_MODELS_MAX


[warning] 109-109: knip: Unused exports detected: PROJECT_ALLOWED_INTEGRATIONS_MAX


[warning] 178-178: knip: Unused exports detected: updateProjectKnowledgeModeSchema


[warning] 215-215: knip: Unused exports detected: updateProjectIntegrationsSchema


[warning] 90-90: knip: Unused exported types detected: ProjectIntegrationsMode


[warning] 149-149: knip: Unused exported types detected: CreateProjectInput


[warning] 159-159: knip: Unused exported types detected: UpdateProjectIdentityInput


[warning] 166-166: knip: Unused exported types detected: UpdateProjectInstructionsInput


[warning] 174-174: knip: Unused exported types detected: UpdateProjectSharingInput


[warning] 181-181: knip: Unused exported types detected: UpdateProjectKnowledgeModeInput


[warning] 196-196: knip: Unused exported types detected: UpdateProjectAgentSettingsInput


[warning] 211-211: knip: Unused exported types detected: UpdateProjectModelSettingsInput


[warning] 222-222: knip: Unused exported types detected: UpdateProjectIntegrationsInput


[warning] 230-230: knip: Unused exported types detected: DeleteProjectInput

🪛 GitHub Actions: Lint / Knip

[warning] 89-89: knip: Unused exports (projectIntegrationsModeSchema).


[warning] 103-103: knip: Unused exports (PROJECT_INSTRUCTIONS_TOKEN_BUDGET).


[warning] 106-106: knip: Unused exports (PROJECT_ALLOWED_AGENTS_MAX).


[warning] 107-107: knip: Unused exports (PROJECT_RECOMMENDED_MODELS_MAX).


[warning] 108-108: knip: Unused exports (PROJECT_ALLOWED_MODELS_MAX).


[warning] 109-109: knip: Unused exports (PROJECT_ALLOWED_INTEGRATIONS_MAX).


[warning] 178-178: knip: Unused exports (updateProjectKnowledgeModeSchema).


[warning] 215-215: knip: Unused exports (updateProjectIntegrationsSchema).


[warning] 90-90: knip: Unused exported types (ProjectIntegrationsMode).


[warning] 149-149: knip: Unused exported types (CreateProjectInput).


[warning] 159-159: knip: Unused exported types (UpdateProjectIdentityInput).


[warning] 166-166: knip: Unused exported types (UpdateProjectInstructionsInput).


[warning] 174-174: knip: Unused exported types (UpdateProjectSharingInput).


[warning] 181-181: knip: Unused exported types (UpdateProjectKnowledgeModeInput).


[warning] 196-196: knip: Unused exported types (UpdateProjectAgentSettingsInput).


[warning] 211-211: knip: Unused exported types (UpdateProjectModelSettingsInput).


[warning] 222-222: knip: Unused exported types (UpdateProjectIntegrationsInput).


[warning] 230-230: knip: Unused exported types (DeleteProjectInput).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/lib/shared/schemas/projects.ts` around lines 89 - 230,
Several constants and schema/type exports (e.g., PROJECT_NAME_MAX,
PROJECT_DESCRIPTION_MAX, PROJECT_INSTRUCTIONS_MAX_CHARS,
PROJECT_INSTRUCTIONS_TOKEN_BUDGET, PROJECT_SHARED_TEAMS_MAX,
PROJECT_RECOMMENDED_AGENTS_MAX, PROJECT_ALLOWED_AGENTS_MAX,
PROJECT_RECOMMENDED_MODELS_MAX, PROJECT_ALLOWED_MODELS_MAX,
PROJECT_ALLOWED_INTEGRATIONS_MAX and any schema/type declarations not referenced
externally) are publicly exported but reported unused; make them internal by
removing the export modifier or moving them out of the public module (or delete
if truly unnecessary) and only export the symbols actually consumed by other
modules (leave public exports like createProjectInputSchema/updateProject*Schema
etc. that are used); after changes, run the linter/knip and tests to confirm no
external usage breaks.

Comment thread services/platform/lib/shared/schemas/projects.ts Outdated
Comment on lines +257 to +467
"projects": {
"title": "Projects",
"navigation": {
"list": "All projects",
"overview": "Overview",
"files": "Files",
"instructions": "Instructions",
"threads": "Chats",
"agents": "Agents & Models",
"settings": "Settings"
},
"list": {
"title": "Projects",
"subtitle": "Shared workspaces for files, instructions, and chats.",
"createButton": "New project",
"emptyTitle": "No projects yet",
"emptyDescription": "Create a project to share files, instructions, and chats with your team.",
"emptyCta": "Create your first project",
"searchPlaceholder": "Search projects…",
"showArchived": "Show archived",
"columnName": "Name",
"columnSharing": "Sharing",
"columnFiles": "Files",
"columnChats": "Chats",
"columnActivity": "Last activity",
"sharingOrgWide": "Org-wide",
"sharingTeamPrefix": "Team:",
"sharingMultipleTeams": "{count} teams"
},
"create": {
"title": "Create project",
"nameLabel": "Project name",
"namePlaceholder": "e.g. Q2 Sales Hiring",
"descriptionLabel": "Description",
"descriptionPlaceholder": "Optional — what's this project for?",
"teamLabel": "Owning team",
"teamPlaceholder": "Select a team (optional, defaults to org-wide)",
"iconLabel": "Icon",
"colorLabel": "Color",
"submit": "Create project",
"submitting": "Creating…",
"successToast": "Project created",
"errorToast": "Couldn't create project"
},
"overview": {
"statsHeading": "Stats",
"statsFiles": "{count, plural, one {# file} other {# files}}",
"statsChats": "{count, plural, one {# chat} other {# chats}}",
"statsShared": "{count, plural, one {# shared} other {# shared}}",
"statsIndexed": "{count, plural, one {# RAG-indexed} other {# RAG-indexed}}",
"newChatCta": "New chat in this project",
"instructionsExcerpt": "Project knowledge",
"instructionsExcerptEmpty": "No project instructions yet.",
"instructionsExcerptEdit": "Edit instructions →",
"sharingHeading": "Sharing",
"recentChats": "Recent chats in this project",
"recentChatsEmpty": "No chats yet. Start one to see it here.",
"recentChatsViewAll": "View all",
"lastActivity": "Last activity {when}",
"lastActivityNever": "No activity yet",
"getStartedHeading": "Get started",
"getStartedFilesTitle": "Add files",
"getStartedFilesBody": "Upload reference material; it's available to every chat.",
"getStartedInstructionsTitle": "Write instructions",
"getStartedInstructionsBody": "Set context that applies to every chat in this project.",
"getStartedShareTitle": "Invite team",
"getStartedShareBody": "Share with other teams or make it org-wide."
},
"instructions": {
"label": "Instructions",
"placeholder": "Write context that should apply to every chat in this project…",
"tokenCountSuffix": "characters used",
"tokenCapWarning": "Approaching the {cap}-character limit",
"tokenCapError": "Exceeds the {cap}-character limit",
"savedIndicator": "Saved",
"savingIndicator": "Saving…",
"saveError": "Couldn't save instructions"
},
"files": {
"title": "Files",
"addButton": "Add file",
"emptyTitle": "No files yet",
"emptyDescription": "Upload reference material to make it available to every chat in this project.",
"ragStatusQueued": "Queued",
"ragStatusRunning": "Indexing…",
"ragStatusCompleted": "Indexed",
"ragStatusFailed": "Failed",
"detachAction": "Remove from project",
"detachSuccess": "Document removed from project",
"detachError": "Couldn't remove document",
"attachSuccess": "Document added to project",
"attachError": "Couldn't add document"
},
"threads": {
"title": "Chats",
"yourChats": "Your chats",
"sharedWithProject": "Shared with project",
"emptyYours": "You haven't started any chats in this project yet.",
"emptyShared": "No shared chats yet.",
"shareToggle": "Share with project",
"shareToggleDisclosure": "Sharing this chat hides your personal memories and instructions from the responses other members will see.",
"shareSuccess": "Chat shared with project",
"unshareSuccess": "Chat hidden from project",
"shareError": "Couldn't update sharing"
},
"agents": {
"agentsHeading": "Agents",
"modelsHeading": "Models",
"integrationsHeading": "Integrations",
"modeAll": "All",
"modeAllDescription": "Members see every agent they have access to.",
"modeRecommended": "Recommended",
"modeRecommendedDescription": "Pin some to the top.",
"modeRestricted": "Restricted",
"modeRestrictedDescription": "Only allow listed items.",
"recommendedListLabel": "Recommended (drag to reorder; first is default)",
"allowedListLabel": "Allowed",
"addAgent": "Add agent",
"addModel": "Add model",
"emptyAdminTitle": "No agents available in this project",
"emptyAdminBody": "The current restrictions exclude every agent your team can access.",
"emptyAdminCta": "Open project settings",
"emptyMemberTitle": "No agents available in this project",
"emptyMemberBody": "Your project admin has restricted access. Contact them to request access to the agents you need.",
"emptyModelsAdminTitle": "No models available in this project",
"emptyModelsAdminBody": "The current restrictions exclude every model your team can access.",
"emptyModelsMemberTitle": "No models available in this project",
"emptyModelsMemberBody": "Your project admin has restricted model access. Contact them to request access.",
"saveSuccess": "Saved",
"saveError": "Couldn't save settings",
"lockoutWarning": "Some members may have no usable agents/models after these restrictions are applied. They'll see an empty-state in the composer.",
"recommendedBadge": "Recommended"
},
"settings": {
"identity": "Identity",
"name": "Name",
"description": "Description",
"iconColor": "Icon / Color",
"sharing": "Sharing",
"owningTeam": "Owning team",
"owningTeamHelp": "Members of this team get full access. Admins of the org always have access.",
"alsoSharedWith": "Also shared with",
"alsoSharedWithHelp": "Pick additional teams that should see this project.",
"visibility": "Visibility",
"visibilityTeam": "Team-only",
"visibilityShared": "Specific teams",
"visibilityOrgWide": "Org-wide",
"knowledge": "Knowledge",
"knowledgeMode": "Knowledge mode",
"knowledgeModeOff": "Off — files are not used",
"knowledgeModeTool": "Tool — agent decides when to search",
"knowledgeModeContext": "Context — auto-injected into every chat",
"knowledgeModeBoth": "Both",
"dangerZone": "Danger zone",
"archiveButton": "Archive project",
"archiveSuccess": "Project archived",
"archiveError": "Couldn't archive project",
"restoreButton": "Restore project",
"restoreSuccess": "Project restored",
"restoreError": "Couldn't restore project",
"deleteButton": "Delete project",
"deleteDialogTitle": "Delete project",
"deleteDialogDescription": "This project will be deleted. Decide what happens to its files and chats.",
"deleteModeDetach": "Detach files and chats — they become library docs and personal chats.",
"deleteModeCascade": "Also delete attached files and project-shared chats.",
"deleteCascadeCheckbox": "Also delete attached files and project-shared chats",
"deleteConfirmPhrase": "Type the project name to confirm",
"deleteSubmit": "Delete project",
"deleteSuccess": "Project deleted",
"deleteError": "Couldn't delete project",
"saveSuccess": "Saved",
"saveError": "Couldn't save settings"
},
"composer": {
"chipLabel": "Project: {name}",
"exitProject": "Exit project context",
"moveTo": "Move to project…",
"removeFromProject": "Remove from project",
"moveSuccess": "Chat moved",
"moveError": "Couldn't move chat"
},
"picker": {
"title": "Move to project",
"searchPlaceholder": "Search projects…",
"createNew": "Create new project…",
"removeFromProject": "Remove from project",
"empty": "No projects yet. Create one to get started."
},
"errors": {
"PROJECT_FORBIDDEN": "You don't have access to this project.",
"PROJECT_NOT_FOUND": "We couldn't find that project. It may have been deleted.",
"PROJECT_MISMATCH": "This chat is in a different project. Switch context to continue.",
"PROJECT_AGENT_NOT_ALLOWED": "{agentName} isn't available in this project. Pick another agent.",
"PROJECT_MODEL_NOT_ALLOWED": "{modelName} isn't available in this project. Pick another model.",
"PROJECT_NAME_INVALID": "Project name must be 1–80 characters.",
"PROJECT_DESCRIPTION_INVALID": "Description is too long.",
"PROJECT_INSTRUCTIONS_TOO_LONG": "Instructions exceed the {cap}-character limit. Trim before saving.",
"PROJECT_LEGAL_HOLD": "This project is under legal hold and cannot be deleted.",
"PROJECT_CONFIRM_PHRASE_MISMATCH": "Type the project name exactly to confirm.",
"PROJECT_SHARING_INVALID": "Sharing settings are invalid.",
"PROJECT_TEAM_INVALID": "Team not found in your organization.",
"DOCUMENT_SCOPE_CONFLICT": "This document is already in a team library. Detach it first.",
"DOCUMENT_NOT_FOUND": "Document not found.",
"RBAC_FORBIDDEN": "Editor or admin role required to make this change.",
"ROLE_FORBIDDEN": "Only admins can change sharing or delete the project.",
"THREAD_FORBIDDEN": "You can only modify your own chats.",
"THREAD_NOT_FOUND": "Chat not found.",
"THREAD_NOT_IN_PROJECT": "This chat isn't in a project.",
"ORG_FORBIDDEN": "That resource belongs to a different organization.",
"THREAD_NO_ORG": "This chat isn't linked to an organization yet."
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

de-CH project strings are still English placeholders.

Line 257 onward introduces the new projects namespace, but the copy is predominantly English. This breaks locale expectations for Swiss German users and should be localized before release.

As per coding guidelines, "Use sentence case in every translation; use informal form across all languages (du in German, tu in French, never Sie or vous)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/messages/de-CH.json` around lines 257 - 467, The new
"projects" namespace in de-CH (keys under "projects" including "title",
"navigation", "list", "create", "overview", "instructions", "files", "threads",
"agents", "settings", "composer", "picker", and "errors") contains English
placeholder copy; replace every English string with Swiss German translations
using sentence case and the informal form ("du"), ensuring error messages (e.g.,
"PROJECT_NOT_FOUND", "PROJECT_FORBIDDEN", etc.) and UI labels ("New project",
"Create project", "Add file", "New chat in this project", "Create your first
project") are localized consistently; run the translations through existing i18n
reviewers or translation pipeline and update the values in the de-CH JSON so the
locale no longer contains English placeholders.

Comment on lines +5213 to +5423
"projects": {
"title": "Projects",
"navigation": {
"list": "All projects",
"overview": "Overview",
"files": "Files",
"instructions": "Instructions",
"threads": "Chats",
"agents": "Agents & Models",
"settings": "Settings"
},
"list": {
"title": "Projects",
"subtitle": "Shared workspaces for files, instructions, and chats.",
"createButton": "New project",
"emptyTitle": "No projects yet",
"emptyDescription": "Create a project to share files, instructions, and chats with your team.",
"emptyCta": "Create your first project",
"searchPlaceholder": "Search projects…",
"showArchived": "Show archived",
"columnName": "Name",
"columnSharing": "Sharing",
"columnFiles": "Files",
"columnChats": "Chats",
"columnActivity": "Last activity",
"sharingOrgWide": "Org-wide",
"sharingTeamPrefix": "Team:",
"sharingMultipleTeams": "{count} teams"
},
"create": {
"title": "Create project",
"nameLabel": "Project name",
"namePlaceholder": "e.g. Q2 Sales Hiring",
"descriptionLabel": "Description",
"descriptionPlaceholder": "Optional — what's this project for?",
"teamLabel": "Owning team",
"teamPlaceholder": "Select a team (optional, defaults to org-wide)",
"iconLabel": "Icon",
"colorLabel": "Color",
"submit": "Create project",
"submitting": "Creating…",
"successToast": "Project created",
"errorToast": "Couldn't create project"
},
"overview": {
"statsHeading": "Stats",
"statsFiles": "{count, plural, one {# file} other {# files}}",
"statsChats": "{count, plural, one {# chat} other {# chats}}",
"statsShared": "{count, plural, one {# shared} other {# shared}}",
"statsIndexed": "{count, plural, one {# RAG-indexed} other {# RAG-indexed}}",
"newChatCta": "New chat in this project",
"instructionsExcerpt": "Project knowledge",
"instructionsExcerptEmpty": "No project instructions yet.",
"instructionsExcerptEdit": "Edit instructions →",
"sharingHeading": "Sharing",
"recentChats": "Recent chats in this project",
"recentChatsEmpty": "No chats yet. Start one to see it here.",
"recentChatsViewAll": "View all",
"lastActivity": "Last activity {when}",
"lastActivityNever": "No activity yet",
"getStartedHeading": "Get started",
"getStartedFilesTitle": "Add files",
"getStartedFilesBody": "Upload reference material; it's available to every chat.",
"getStartedInstructionsTitle": "Write instructions",
"getStartedInstructionsBody": "Set context that applies to every chat in this project.",
"getStartedShareTitle": "Invite team",
"getStartedShareBody": "Share with other teams or make it org-wide."
},
"instructions": {
"label": "Instructions",
"placeholder": "Write context that should apply to every chat in this project…",
"tokenCountSuffix": "characters used",
"tokenCapWarning": "Approaching the {cap}-character limit",
"tokenCapError": "Exceeds the {cap}-character limit",
"savedIndicator": "Saved",
"savingIndicator": "Saving…",
"saveError": "Couldn't save instructions"
},
"files": {
"title": "Files",
"addButton": "Add file",
"emptyTitle": "No files yet",
"emptyDescription": "Upload reference material to make it available to every chat in this project.",
"ragStatusQueued": "Queued",
"ragStatusRunning": "Indexing…",
"ragStatusCompleted": "Indexed",
"ragStatusFailed": "Failed",
"detachAction": "Remove from project",
"detachSuccess": "Document removed from project",
"detachError": "Couldn't remove document",
"attachSuccess": "Document added to project",
"attachError": "Couldn't add document"
},
"threads": {
"title": "Chats",
"yourChats": "Your chats",
"sharedWithProject": "Shared with project",
"emptyYours": "You haven't started any chats in this project yet.",
"emptyShared": "No shared chats yet.",
"shareToggle": "Share with project",
"shareToggleDisclosure": "Sharing this chat hides your personal memories and instructions from the responses other members will see.",
"shareSuccess": "Chat shared with project",
"unshareSuccess": "Chat hidden from project",
"shareError": "Couldn't update sharing"
},
"agents": {
"agentsHeading": "Agents",
"modelsHeading": "Models",
"integrationsHeading": "Integrations",
"modeAll": "All",
"modeAllDescription": "Members see every agent they have access to.",
"modeRecommended": "Recommended",
"modeRecommendedDescription": "Pin some to the top.",
"modeRestricted": "Restricted",
"modeRestrictedDescription": "Only allow listed items.",
"recommendedListLabel": "Recommended (drag to reorder; first is default)",
"allowedListLabel": "Allowed",
"addAgent": "Add agent",
"addModel": "Add model",
"emptyAdminTitle": "No agents available in this project",
"emptyAdminBody": "The current restrictions exclude every agent your team can access.",
"emptyAdminCta": "Open project settings",
"emptyMemberTitle": "No agents available in this project",
"emptyMemberBody": "Your project admin has restricted access. Contact them to request access to the agents you need.",
"emptyModelsAdminTitle": "No models available in this project",
"emptyModelsAdminBody": "The current restrictions exclude every model your team can access.",
"emptyModelsMemberTitle": "No models available in this project",
"emptyModelsMemberBody": "Your project admin has restricted model access. Contact them to request access.",
"saveSuccess": "Saved",
"saveError": "Couldn't save settings",
"lockoutWarning": "Some members may have no usable agents/models after these restrictions are applied. They'll see an empty-state in the composer.",
"recommendedBadge": "Recommended"
},
"settings": {
"identity": "Identity",
"name": "Name",
"description": "Description",
"iconColor": "Icon / Color",
"sharing": "Sharing",
"owningTeam": "Owning team",
"owningTeamHelp": "Members of this team get full access. Admins of the org always have access.",
"alsoSharedWith": "Also shared with",
"alsoSharedWithHelp": "Pick additional teams that should see this project.",
"visibility": "Visibility",
"visibilityTeam": "Team-only",
"visibilityShared": "Specific teams",
"visibilityOrgWide": "Org-wide",
"knowledge": "Knowledge",
"knowledgeMode": "Knowledge mode",
"knowledgeModeOff": "Off — files are not used",
"knowledgeModeTool": "Tool — agent decides when to search",
"knowledgeModeContext": "Context — auto-injected into every chat",
"knowledgeModeBoth": "Both",
"dangerZone": "Danger zone",
"archiveButton": "Archive project",
"archiveSuccess": "Project archived",
"archiveError": "Couldn't archive project",
"restoreButton": "Restore project",
"restoreSuccess": "Project restored",
"restoreError": "Couldn't restore project",
"deleteButton": "Delete project",
"deleteDialogTitle": "Delete project",
"deleteDialogDescription": "This project will be deleted. Decide what happens to its files and chats.",
"deleteModeDetach": "Detach files and chats — they become library docs and personal chats.",
"deleteModeCascade": "Also delete attached files and project-shared chats.",
"deleteCascadeCheckbox": "Also delete attached files and project-shared chats",
"deleteConfirmPhrase": "Type the project name to confirm",
"deleteSubmit": "Delete project",
"deleteSuccess": "Project deleted",
"deleteError": "Couldn't delete project",
"saveSuccess": "Saved",
"saveError": "Couldn't save settings"
},
"composer": {
"chipLabel": "Project: {name}",
"exitProject": "Exit project context",
"moveTo": "Move to project…",
"removeFromProject": "Remove from project",
"moveSuccess": "Chat moved",
"moveError": "Couldn't move chat"
},
"picker": {
"title": "Move to project",
"searchPlaceholder": "Search projects…",
"createNew": "Create new project…",
"removeFromProject": "Remove from project",
"empty": "No projects yet. Create one to get started."
},
"errors": {
"PROJECT_FORBIDDEN": "You don't have access to this project.",
"PROJECT_NOT_FOUND": "We couldn't find that project. It may have been deleted.",
"PROJECT_MISMATCH": "This chat is in a different project. Switch context to continue.",
"PROJECT_AGENT_NOT_ALLOWED": "{agentName} isn't available in this project. Pick another agent.",
"PROJECT_MODEL_NOT_ALLOWED": "{modelName} isn't available in this project. Pick another model.",
"PROJECT_NAME_INVALID": "Project name must be 1–80 characters.",
"PROJECT_DESCRIPTION_INVALID": "Description is too long.",
"PROJECT_INSTRUCTIONS_TOO_LONG": "Instructions exceed the {cap}-character limit. Trim before saving.",
"PROJECT_LEGAL_HOLD": "This project is under legal hold and cannot be deleted.",
"PROJECT_CONFIRM_PHRASE_MISMATCH": "Type the project name exactly to confirm.",
"PROJECT_SHARING_INVALID": "Sharing settings are invalid.",
"PROJECT_TEAM_INVALID": "Team not found in your organization.",
"DOCUMENT_SCOPE_CONFLICT": "This document is already in a team library. Detach it first.",
"DOCUMENT_NOT_FOUND": "Document not found.",
"RBAC_FORBIDDEN": "Editor or admin role required to make this change.",
"ROLE_FORBIDDEN": "Only admins can change sharing or delete the project.",
"THREAD_FORBIDDEN": "You can only modify your own chats.",
"THREAD_NOT_FOUND": "Chat not found.",
"THREAD_NOT_IN_PROJECT": "This chat isn't in a project.",
"ORG_FORBIDDEN": "That resource belongs to a different organization.",
"THREAD_NO_ORG": "This chat isn't linked to an organization yet."
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Localize the new projects namespace for German (de.json) before merge.

This entire block is currently in English, so German users will see mixed-language UI and errors. Please translate these keys to German (informal register), matching the locale file’s conventions.

As per coding guidelines: services/platform/messages/*.json: “Use sentence case in every translation; use informal form across all languages (du in German, …)”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/messages/de.json` around lines 5213 - 5423, Translate the
entire "projects" JSON namespace into German using informal tone ("du"),
sentence case, and match existing locale conventions; update all keys under
projects (e.g., navigation, list, create, overview, instructions, files,
threads, agents, settings, composer, picker, errors) so UI strings and error
messages are full German translations and preserve interpolation tokens like
{count}, {cap}, {name}, {agentName}, {modelName}, {when} exactly as-is.

Comment on lines +5213 to +5423
"projects": {
"title": "Projects",
"navigation": {
"list": "All projects",
"overview": "Overview",
"files": "Files",
"instructions": "Instructions",
"threads": "Chats",
"agents": "Agents & Models",
"settings": "Settings"
},
"list": {
"title": "Projects",
"subtitle": "Shared workspaces for files, instructions, and chats.",
"createButton": "New project",
"emptyTitle": "No projects yet",
"emptyDescription": "Create a project to share files, instructions, and chats with your team.",
"emptyCta": "Create your first project",
"searchPlaceholder": "Search projects…",
"showArchived": "Show archived",
"columnName": "Name",
"columnSharing": "Sharing",
"columnFiles": "Files",
"columnChats": "Chats",
"columnActivity": "Last activity",
"sharingOrgWide": "Org-wide",
"sharingTeamPrefix": "Team:",
"sharingMultipleTeams": "{count} teams"
},
"create": {
"title": "Create project",
"nameLabel": "Project name",
"namePlaceholder": "e.g. Q2 Sales Hiring",
"descriptionLabel": "Description",
"descriptionPlaceholder": "Optional — what's this project for?",
"teamLabel": "Owning team",
"teamPlaceholder": "Select a team (optional, defaults to org-wide)",
"iconLabel": "Icon",
"colorLabel": "Color",
"submit": "Create project",
"submitting": "Creating…",
"successToast": "Project created",
"errorToast": "Couldn't create project"
},
"overview": {
"statsHeading": "Stats",
"statsFiles": "{count, plural, one {# file} other {# files}}",
"statsChats": "{count, plural, one {# chat} other {# chats}}",
"statsShared": "{count, plural, one {# shared} other {# shared}}",
"statsIndexed": "{count, plural, one {# RAG-indexed} other {# RAG-indexed}}",
"newChatCta": "New chat in this project",
"instructionsExcerpt": "Project knowledge",
"instructionsExcerptEmpty": "No project instructions yet.",
"instructionsExcerptEdit": "Edit instructions →",
"sharingHeading": "Sharing",
"recentChats": "Recent chats in this project",
"recentChatsEmpty": "No chats yet. Start one to see it here.",
"recentChatsViewAll": "View all",
"lastActivity": "Last activity {when}",
"lastActivityNever": "No activity yet",
"getStartedHeading": "Get started",
"getStartedFilesTitle": "Add files",
"getStartedFilesBody": "Upload reference material; it's available to every chat.",
"getStartedInstructionsTitle": "Write instructions",
"getStartedInstructionsBody": "Set context that applies to every chat in this project.",
"getStartedShareTitle": "Invite team",
"getStartedShareBody": "Share with other teams or make it org-wide."
},
"instructions": {
"label": "Instructions",
"placeholder": "Write context that should apply to every chat in this project…",
"tokenCountSuffix": "characters used",
"tokenCapWarning": "Approaching the {cap}-character limit",
"tokenCapError": "Exceeds the {cap}-character limit",
"savedIndicator": "Saved",
"savingIndicator": "Saving…",
"saveError": "Couldn't save instructions"
},
"files": {
"title": "Files",
"addButton": "Add file",
"emptyTitle": "No files yet",
"emptyDescription": "Upload reference material to make it available to every chat in this project.",
"ragStatusQueued": "Queued",
"ragStatusRunning": "Indexing…",
"ragStatusCompleted": "Indexed",
"ragStatusFailed": "Failed",
"detachAction": "Remove from project",
"detachSuccess": "Document removed from project",
"detachError": "Couldn't remove document",
"attachSuccess": "Document added to project",
"attachError": "Couldn't add document"
},
"threads": {
"title": "Chats",
"yourChats": "Your chats",
"sharedWithProject": "Shared with project",
"emptyYours": "You haven't started any chats in this project yet.",
"emptyShared": "No shared chats yet.",
"shareToggle": "Share with project",
"shareToggleDisclosure": "Sharing this chat hides your personal memories and instructions from the responses other members will see.",
"shareSuccess": "Chat shared with project",
"unshareSuccess": "Chat hidden from project",
"shareError": "Couldn't update sharing"
},
"agents": {
"agentsHeading": "Agents",
"modelsHeading": "Models",
"integrationsHeading": "Integrations",
"modeAll": "All",
"modeAllDescription": "Members see every agent they have access to.",
"modeRecommended": "Recommended",
"modeRecommendedDescription": "Pin some to the top.",
"modeRestricted": "Restricted",
"modeRestrictedDescription": "Only allow listed items.",
"recommendedListLabel": "Recommended (drag to reorder; first is default)",
"allowedListLabel": "Allowed",
"addAgent": "Add agent",
"addModel": "Add model",
"emptyAdminTitle": "No agents available in this project",
"emptyAdminBody": "The current restrictions exclude every agent your team can access.",
"emptyAdminCta": "Open project settings",
"emptyMemberTitle": "No agents available in this project",
"emptyMemberBody": "Your project admin has restricted access. Contact them to request access to the agents you need.",
"emptyModelsAdminTitle": "No models available in this project",
"emptyModelsAdminBody": "The current restrictions exclude every model your team can access.",
"emptyModelsMemberTitle": "No models available in this project",
"emptyModelsMemberBody": "Your project admin has restricted model access. Contact them to request access.",
"saveSuccess": "Saved",
"saveError": "Couldn't save settings",
"lockoutWarning": "Some members may have no usable agents/models after these restrictions are applied. They'll see an empty-state in the composer.",
"recommendedBadge": "Recommended"
},
"settings": {
"identity": "Identity",
"name": "Name",
"description": "Description",
"iconColor": "Icon / Color",
"sharing": "Sharing",
"owningTeam": "Owning team",
"owningTeamHelp": "Members of this team get full access. Admins of the org always have access.",
"alsoSharedWith": "Also shared with",
"alsoSharedWithHelp": "Pick additional teams that should see this project.",
"visibility": "Visibility",
"visibilityTeam": "Team-only",
"visibilityShared": "Specific teams",
"visibilityOrgWide": "Org-wide",
"knowledge": "Knowledge",
"knowledgeMode": "Knowledge mode",
"knowledgeModeOff": "Off — files are not used",
"knowledgeModeTool": "Tool — agent decides when to search",
"knowledgeModeContext": "Context — auto-injected into every chat",
"knowledgeModeBoth": "Both",
"dangerZone": "Danger zone",
"archiveButton": "Archive project",
"archiveSuccess": "Project archived",
"archiveError": "Couldn't archive project",
"restoreButton": "Restore project",
"restoreSuccess": "Project restored",
"restoreError": "Couldn't restore project",
"deleteButton": "Delete project",
"deleteDialogTitle": "Delete project",
"deleteDialogDescription": "This project will be deleted. Decide what happens to its files and chats.",
"deleteModeDetach": "Detach files and chats — they become library docs and personal chats.",
"deleteModeCascade": "Also delete attached files and project-shared chats.",
"deleteCascadeCheckbox": "Also delete attached files and project-shared chats",
"deleteConfirmPhrase": "Type the project name to confirm",
"deleteSubmit": "Delete project",
"deleteSuccess": "Project deleted",
"deleteError": "Couldn't delete project",
"saveSuccess": "Saved",
"saveError": "Couldn't save settings"
},
"composer": {
"chipLabel": "Project: {name}",
"exitProject": "Exit project context",
"moveTo": "Move to project…",
"removeFromProject": "Remove from project",
"moveSuccess": "Chat moved",
"moveError": "Couldn't move chat"
},
"picker": {
"title": "Move to project",
"searchPlaceholder": "Search projects…",
"createNew": "Create new project…",
"removeFromProject": "Remove from project",
"empty": "No projects yet. Create one to get started."
},
"errors": {
"PROJECT_FORBIDDEN": "You don't have access to this project.",
"PROJECT_NOT_FOUND": "We couldn't find that project. It may have been deleted.",
"PROJECT_MISMATCH": "This chat is in a different project. Switch context to continue.",
"PROJECT_AGENT_NOT_ALLOWED": "{agentName} isn't available in this project. Pick another agent.",
"PROJECT_MODEL_NOT_ALLOWED": "{modelName} isn't available in this project. Pick another model.",
"PROJECT_NAME_INVALID": "Project name must be 1–80 characters.",
"PROJECT_DESCRIPTION_INVALID": "Description is too long.",
"PROJECT_INSTRUCTIONS_TOO_LONG": "Instructions exceed the {cap}-character limit. Trim before saving.",
"PROJECT_LEGAL_HOLD": "This project is under legal hold and cannot be deleted.",
"PROJECT_CONFIRM_PHRASE_MISMATCH": "Type the project name exactly to confirm.",
"PROJECT_SHARING_INVALID": "Sharing settings are invalid.",
"PROJECT_TEAM_INVALID": "Team not found in your organization.",
"DOCUMENT_SCOPE_CONFLICT": "This document is already in a team library. Detach it first.",
"DOCUMENT_NOT_FOUND": "Document not found.",
"RBAC_FORBIDDEN": "Editor or admin role required to make this change.",
"ROLE_FORBIDDEN": "Only admins can change sharing or delete the project.",
"THREAD_FORBIDDEN": "You can only modify your own chats.",
"THREAD_NOT_FOUND": "Chat not found.",
"THREAD_NOT_IN_PROJECT": "This chat isn't in a project.",
"ORG_FORBIDDEN": "That resource belongs to a different organization.",
"THREAD_NO_ORG": "This chat isn't linked to an organization yet."
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Localize the new projects namespace to French in fr.json.

The new projects block is almost entirely English ("Create project", "All projects", "Couldn't save settings", etc.), which breaks French locale UX and violates locale consistency for this file.

As per coding guidelines, services/platform/messages/*.json must use proper translations (sentence case, informal form) for each language file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/messages/fr.json` around lines 5213 - 5423, The projects
namespace is still in English; translate every string under "projects" into
French (preserve placeholders like {count}, {cap}, {when}, and tokens such as
"Projects", "create.title", "list.navigation.list",
"overview.getStartedFilesBody", "instructions.placeholder",
"files.ragStatusRunning", "threads.shareToggleDisclosure",
"agents.modeRestrictedDescription", "settings.saveError", "composer.chipLabel",
"picker.createNew", and all keys under "errors") using sentence case and the
informal tone for French locale; ensure pluralization and interpolation formats
remain unchanged and that strings like "New project", "Create project",
"Couldn't save settings", "No projects yet" are replaced with proper French
equivalents throughout the entire "projects" block.

@yannickmonney yannickmonney merged commit 760708c into main May 25, 2026
29 checks passed
@yannickmonney yannickmonney deleted the feat/platform-projects branch May 25, 2026 11:01
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