Skip to content

fix(chat): lifecycle-guaranteed root token injection (production regression)#375

Merged
blove merged 1 commit into
mainfrom
claude/fix-chat-root-tokens-injection
May 16, 2026
Merged

fix(chat): lifecycle-guaranteed root token injection (production regression)#375
blove merged 1 commit into
mainfrom
claude/fix-chat-root-tokens-injection

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 16, 2026

Summary

Fixes the chrome-less production demo: sidenav too narrow, no border on the input, suggestion chips without pill background, etc. Root cause is `ensureChatRootStyles()` being tree-shaken out of the production bundle.

Root cause (verified live + via manual style injection)

`ensureChatRootStyles()` is the function that injects `<style id="ngaf-chat-root-tokens">` into ``. That style element defines:

  • All `--ngaf-chat-*` root tokens (bg, surface, text, separator, radius, sidenav width, font-family, …)
  • The data-ngaf-chat-theme attribute → token mappings (light/dark switching)
  • The edge-claim primitive defaults
  • `prefers-reduced-motion` reset

Every host-encapsulated chat lib style references these via `var()`. Without them, sidenav has no width, input has no border, chips have no pill background, suggestions look like plain text on the page bg.

The function is wired as a module-evaluation side effect. The published artifact's `sideEffects` glob is `["/chat-tokens.ts", "/*.css"]` — the chat-tokens.ts glob matches the source tree (workspace TS paths in this repo) but matches nothing in the bundled `dist/libs/chat/fesm2022/ngaf-chat.mjs` artifact. Production bundlers see no side-effect-marked files in the published package, treat the entire bundle as side-effect-free, and drop the call.

Manual proof: injecting the root tokens by hand via browser console at https://demo.cacheplane.ai restored every chrome element instantly.

This doesn't reproduce locally because dev mode skips tree-shaking and workspace TS paths match the source glob — which is exactly why we missed it in code review.

Fix

  1. Lifecycle-guaranteed call. Invoke `ensureChatRootStyles()` from the constructors of every top-level chat composition: `ChatComponent`, `ChatPopupComponent`, `ChatSidebarComponent`, `ChatDebugComponent`. The function is idempotent (early-returns if the style element already exists). Constructors are reachable from user code, so bundlers can't tree-shake the call.

  2. Defense-in-depth on the package manifest. Extend `libs/chat/package.json` `sideEffects` to also include `./fesm2022/ngaf-chat.mjs` for any consumer bundler that DOES respect the field.

Test plan

  • All 744 chat lib tests pass
  • `npx nx build chat` green
  • api-docs unchanged (constructor-internal change)
  • CI green
  • Post-merge: production demo at demo.cacheplane.ai renders with proper chrome after v0.0.35 ships

Out of scope (separate follow-up)

  • Suggestion-list reduction (top 3 + overflow dropdown)
  • Top-level theme switcher (currently only in chat-debug palette)

🤖 Generated with Claude Code

…ession)

`ensureChatRootStyles()` injects the chat lib's root CSS custom
properties — `--ngaf-chat-bg`, `--ngaf-chat-surface`,
`--ngaf-chat-radius-input`, `--ngaf-chat-sidenav-width-expanded`,
`--ngaf-chat-font-family`, the edge-claim primitive, the
data-ngaf-chat-theme attribute mappings, and reduced-motion overrides.
Every host-encapsulated chat lib style references these via `var()`.

It was wired as a module-evaluation side effect (auto-call at the bottom
of chat-tokens.ts). The published artifact's `sideEffects` glob was
`["**/chat-tokens.ts", "**/*.css"]` — the chat-tokens.ts glob matches
the *source* tree (workspace TS paths in this repo) but matches nothing
in the bundled `dist/libs/chat/fesm2022/ngaf-chat.mjs` artifact. When
a consumer's bundler does aggressive production tree-shaking it sees
no side-effect-marked files in the published package and treats the
entire bundle as side-effect-free. Result: the module-eval call is
dropped, the style element is never injected, and every chat surface
renders without chrome — sidenav has no width, input has no border,
chips have no pill background, suggestions look like plain text on
the page bg.

This shipped in production at https://demo.cacheplane.ai but doesn't
reproduce locally (dev mode skips tree-shaking; workspace TS paths
match the source glob). Verified live: manual injection of the root
tokens via browser console restored every chrome element instantly.

Fix:
  - Call `ensureChatRootStyles()` from the constructors of every
    top-level chat composition (`ChatComponent`, `ChatPopupComponent`,
    `ChatSidebarComponent`, `ChatDebugComponent`). The function is
    idempotent (early-returns if the style element already exists).
    Constructors are reachable from user code, so bundlers can't
    tree-shake the call.
  - Extend `libs/chat/package.json` `sideEffects` to also match
    `./fesm2022/ngaf-chat.mjs` — defense-in-depth for any consumer
    bundler that DOES respect the field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 16, 2026 7:39pm

Request Review

@blove blove merged commit 72de161 into main May 16, 2026
16 checks passed
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