fix(ai): hide Translate / Find related / Suggest reply menu items when the AI flag is off (LC-686) #652

Merged
longjacksonle merged 1 commit from fix/LC-686-ai-menu-gate into main 2026-08-09 04:52:27 +02:00

Symptom

With the LC-679 AI feature flag off (its production default), the message action menu still showed Translate, Find related, and Suggest reply. Clicking one fails: the route 403s (correct) and the UI shows two confusing toasts - "AI request failed. Please try again." (live.js) and "Could not save. Please try again." (settings.js binds a global htmx:responseError handler). Catch me up and the composer writing-assistant are gated server-side and correctly vanish when the flag is off, so the menu was inconsistent.

Root cause (CSS specificity, not a backend failure)

These three items are CSS-gated: they always render with a lc-llm-only / lc-embeddings-only class, hidden by default and revealed only under the [data-lc-llm] / [data-lc-embeddings] page-root attribute (which now folds in flag + role).

The hide rule lost a specificity tie:

  • .lc-llm-only { display: none } / .lc-embeddings-only { display: none } - (0,1,0)
  • .lc-menu-item { display: flex }, defined later in the same file - also (0,1,0)

Every item carries both classes (class="lc-menu-item lc-llm-only"), so the later rule won the source-order tie and the item was always shown. The reveal rule [data-lc-llm] .lc-menu-item.lc-llm-only (0,3,0) only ever turned items on; nothing turned them off. Same cascade trap as LC-678.

Fix

Scope the two hide rules to .lc-menu-item.lc-llm-only / .lc-menu-item.lc-embeddings-only (0,2,0) - beats .lc-menu-item, while the reveal rule (0,3,0) still wins. Both classes are used only as lc-menu-item lc-<x>-only across the templates, and the sibling .lc-read-aloud gate already uses this higher-specificity shape.

Result: with the flag off the three items disappear exactly like Catch me up and the writing-assistant; reveal is unchanged for flag-on + configured + privileged viewers, including live-swapped WS message fragments (the rule is ancestor-based). The routes keep their 403 as server-side defense in depth (covered by the LC-679 routes_ai_gate tests).

Scope

One file, server/assets/main.css (+12/-4), served directly (no build step). No behavior change when the flag is on.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PBAyCesyqJkZ5JgixXf9Lf

## Symptom With the LC-679 AI feature flag **off** (its production default), the message action menu still showed **Translate**, **Find related**, and **Suggest reply**. Clicking one fails: the route 403s (correct) and the UI shows two confusing toasts - "AI request failed. Please try again." (live.js) and "Could not save. Please try again." (settings.js binds a global `htmx:responseError` handler). Catch me up and the composer writing-assistant are gated server-side and correctly vanish when the flag is off, so the menu was inconsistent. ## Root cause (CSS specificity, not a backend failure) These three items are CSS-gated: they always render with a `lc-llm-only` / `lc-embeddings-only` class, hidden by default and revealed only under the `[data-lc-llm]` / `[data-lc-embeddings]` page-root attribute (which now folds in flag + role). The hide rule lost a specificity tie: - `.lc-llm-only { display: none }` / `.lc-embeddings-only { display: none }` - **(0,1,0)** - `.lc-menu-item { display: flex }`, defined **later** in the same file - also **(0,1,0)** Every item carries both classes (`class="lc-menu-item lc-llm-only"`), so the later rule won the source-order tie and the item was always shown. The reveal rule `[data-lc-llm] .lc-menu-item.lc-llm-only` (0,3,0) only ever turned items **on**; nothing turned them **off**. Same cascade trap as LC-678. ## Fix Scope the two hide rules to `.lc-menu-item.lc-llm-only` / `.lc-menu-item.lc-embeddings-only` **(0,2,0)** - beats `.lc-menu-item`, while the reveal rule (0,3,0) still wins. Both classes are used **only** as `lc-menu-item lc-<x>-only` across the templates, and the sibling `.lc-read-aloud` gate already uses this higher-specificity shape. Result: with the flag off the three items disappear exactly like Catch me up and the writing-assistant; reveal is unchanged for flag-on + configured + privileged viewers, including live-swapped WS message fragments (the rule is ancestor-based). The routes keep their 403 as server-side defense in depth (covered by the LC-679 `routes_ai_gate` tests). ## Scope One file, `server/assets/main.css` (+12/-4), served directly (no build step). No behavior change when the flag is on. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01PBAyCesyqJkZ5JgixXf9Lf
fix(ai): hide Translate / Find related / Suggest reply menu items when the AI flag is off (LC-686)
All checks were successful
check-secrets / Kingfisher (push) Successful in 5s
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 4s
Check / clippy + fmt + tests (pull_request) Successful in 4m30s
Create release / Create release from merged PR (pull_request) Has been skipped
885571b6c9
With the LC-679 AI feature flag off (its production default), the message action menu still showed Translate, Find related, and Suggest reply. Clicking one 403s (the routes gate correctly) and the UI surfaced two confusing toasts ("AI request failed" from live.js and, via settings.js's global htmx:responseError handler, "Could not save"). Catch me up and the composer writing-assistant are gated server-side and correctly vanish when the flag is off, so the menu was inconsistent.

Root cause is a CSS specificity tie, not a backend failure. These three items are CSS-gated: they always render with a lc-llm-only / lc-embeddings-only class and are meant to be hidden by default, revealed only under the [data-lc-llm] / [data-lc-embeddings] page-root attribute (which now folds in the flag + role). But the hide rule `.lc-llm-only { display: none }` is (0,1,0) and `.lc-menu-item { display: flex }`, defined later in the same file, is also (0,1,0); every item carries both classes, so the later rule won the source-order tie and the item was always shown. The reveal rule only ever turned items ON; nothing turned them OFF. Same cascade trap as LC-678.

Fix: scope the two hide rules as `.lc-menu-item.lc-llm-only` / `.lc-menu-item.lc-embeddings-only` (0,2,0), which beats `.lc-menu-item` while the reveal rule (0,3,0) still wins. Both classes are used only as `lc-menu-item lc-<x>-only` across the templates, and the sibling `.lc-read-aloud` gate already uses this higher-specificity shape. Now the three items disappear when the flag is off, exactly like Catch me up and the writing-assistant, and reveal unchanged for flag-on + configured + privileged viewers (including live-swapped WS message fragments). The routes keep their 403 as server-side defense in depth (LC-679 routes_ai_gate tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAyCesyqJkZ5JgixXf9Lf
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-08-09 04:48:57 +02:00
longjacksonle deleted branch fix/LC-686-ai-menu-gate 2026-08-09 04:52:27 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!652
No description provided.