Skip to content

feat(i18n): build-time AI translation + @fuzefront/i18n language selector - #72

Merged
izzywdev merged 14 commits into
masterfrom
feature/i18n
Jun 22, 2026
Merged

feat(i18n): build-time AI translation + @fuzefront/i18n language selector#72
izzywdev merged 14 commits into
masterfrom
feature/i18n

Conversation

@izzywdev

Copy link
Copy Markdown
Owner

Summary

Build-time AI translation (Option C — no runtime service/DB/spinner) + a shared @fuzefront/i18n React runtime. English is the only maintained language; a curated ~10-language set is AI-translated at build time via the in-cluster LiteLLM gateway, and a CI job opens a bot PR with the generated locale files. Git is the store.

Packages

@fuzefront/i18n (packages/i18n) — frontend runtime

  • Adopts i18next + react-i18next (no bespoke runtime).
  • <I18nProvider> (init i18next, load bundled locale JSON, restore saved language, wire direction manager), <LanguageSelector>, useT(), setLanguage(), useDir() + applyDocumentDirection/attachDirectionManager, and a typed language registry ({code,name,nativeName,dir}).
  • Design-system-first <LanguageSelector>: styled entirely from "fuse seam" token CSS variables (no hard-coded color/spacing/type — there is a vitest assertion that enforces this), full label/htmlFor a11y, native language names, and RTL via CSS logical properties so it mirrors automatically.
  • Centralized RTL/LTR: direction derived from the active language and applied to <html dir/lang>. Documented that design-system components should use logical properties to mirror.
  • Dual es/cjs/d.ts build (rollup, mirrors the SDK). publishConfig → GitHub Packages, @fuzefront scope, access: restricted, repository field set. Added to lerna publish config.

@fuzefront/i18n-translate (packages/i18n-translate) — build-time CLI

  • bin CLI (fuzefront-i18n-translate): reads locales/en/*.json + i18n.languages.json, diffs vs existing locales/<lng>/*.json, and translates only missing/changed keys via an OpenAI-compatible /chat/completions endpoint (endpoint/key/model from env; LiteLLM gateway).
  • Preserves ICU placeholders + {{interpolation}} tokens, enforces a do-not-translate glossary ("FuzeFront", "fuse seam", …), and uses a per-key source hash for idempotency (byte-identical re-runs, never re-spends on the LLM). LLM is mocked in all tests — zero live network.

Source + config

  • locales/en/common.json (10 keys incl. an ICU plural apps.count and an interpolation greeting.welcome).
  • i18n.languages.json: en, es, fr, de, pt, ru, zh, ja, hi (LTR) + ar, he (RTL).

CI

  • .github/workflows/i18n-translate.yml: triggers only on locales/en/** / i18n.languages.json changes (+ workflow_dispatch), runs the CLI against LiteLLM (endpoint/key/model as secrets), and opens a bot PR with regenerated locales. Never translates on every build. Passes actionlint.

Verification (local)

  • @fuzefront/i18n: tsc --noEmit exit 0; vitest run src18 passed (incl. he/ar RTL flip, a11y label association, hidden-label, and token-only styling assertions); rollup -c produced dist/index.js + index.esm.js + index.d.ts.
  • @fuzefront/i18n-translate: see PR checks / comments for the final counts.
  • actionlint on the workflow → exit 0.

Decisions / deferred (flagged per instructions)

  • Root workspaces not modified. Following the established repo pattern (frontend, sdk, services are NOT in root workspaces to avoid the os=linux/native-binary install gotcha at root npm ci); these packages are wired through lerna for publishing instead. Per-package lockfiles and local .npmrc os-clear workarounds are git-ignored so the Linux CI install is unaffected.
  • Design-system gap: design-system/ is not yet a resolvable @fuzefront/design-system npm package (no package.json/exports). The selector therefore consumes the design-system tokens directly and mirrors the DS Select markup rather than importing the component. When the DS is published, swap the inner control for the imported <Select>. Also: the DS Select.jsx uses physical right/padding properties — it should adopt logical properties to mirror in RTL like this selector does.
  • Identity track convergence: the identity track is starting its own i18n provider separately. @fuzefront/i18n is the intended shared package; this PR does not touch the identity branch — convergence is a later step.

Notes

  • Did not edit the service build matrix in release.yml (orchestrator-owned).

🤖 Generated with Claude Code

AppHub Developer and others added 4 commits June 22, 2026 10:01
…ctor

Add two new private @fuzefront/* packages plus build-time config and CI:

- packages/i18n (@fuzefront/i18n): i18next + react-i18next runtime.
  I18nProvider, design-system-token-only LanguageSelector (a11y + native
  names), useT, setLanguage, centralized RTL/LTR direction manager, and a
  typed language registry. Dual es/cjs/d.ts build (rollup). 18 vitest pass
  incl. he/ar RTL flip + token-only assertions.
- packages/i18n-translate (@fuzefront/i18n-translate): build-time CLI that
  diffs en/* vs existing locales and AI-translates only missing/changed keys
  via an OpenAI-compatible (LiteLLM) endpoint. Per-key source-hash idempotency,
  ICU + {{interpolation}} preservation, do-not-translate glossary. LLM mocked
  in tests (no live network).
- locales/en/common.json (incl. ICU plural + interpolation) and
  i18n.languages.json (en/es/fr/de/pt/ru/zh/ja/hi LTR + ar/he RTL).
- .github/workflows/i18n-translate.yml: translate on locale/list change,
  open a bot PR with regenerated locales (never per-build).
- Wire both packages into lerna publish config; publishConfig -> GitHub
  Packages, access restricted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nused dep

The package shipped ESM (`type: module`) but TypeScript emitted extensionless
relative imports, which Node's pure-ESM resolver rejects at runtime
(ERR_MODULE_NOT_FOUND on `./runner`). Vitest masked it (bundler resolution).
Switch the build to CommonJS (module/commonjs + moduleResolution/node) so
`node dist/cli.js` works; the CLI `--help` and env-validation paths now run.
Remove the unused `fast-glob` dependency (directory scan uses node:fs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fe DS Select

- Mount <I18nProvider> at the app root (main.tsx), bundling the repo-root
  locales/ tree via an @locales Vite alias + import.meta.glob.
- Replace the hard-coded top-bar language dropdown with the DS-token
  <LanguageSelector> from @fuzefront/i18n; direction manager owns <html dir>.
- Module-Federation: share @fuzefront/i18n + i18next + react-i18next as
  singletons so remotes join the host's single i18n instance.
- Design-system gap: make DS Select (Select.jsx + forms.card.html) use CSS
  logical properties (padding-inline*, inset-inline-end, text-align:start) so
  it mirrors under dir=rtl.
- Migrate a representative slice (TopBar, SidePanel) from the legacy
  LanguageContext to useT() flat-dotted keys; seed en/he/ar common.json.
- Legacy LanguageProvider no longer writes <html dir/lang> (manager owns it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/registry wiring

The frontend Docker build context is only ./frontend, so the bundled locale
JSON must live inside it. Relocate locales/<lng>/common.json to
frontend/locales/<lng>/common.json (single source of truth), and point the
i18n-translate workflow's --locales-dir + trigger paths there.

- resources.ts globs ../../locales (frontend/locales) eagerly.
- vite/vitest resolve @fuzefront/i18n from local monorepo source when present
  (host dev/test), else from node_modules (Docker/CI) — guarded by existsSync.
- frontend/.npmrc + Dockerfile GITHUB_TOKEN build-arg so @fuzefront/i18n
  resolves from GitHub Packages inside the image build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

CI fix — branch claude-auto-fix-ci-feature/i18n-27935514222

I investigated the failing CI run and pushed a fix to the branch above. Here's what I found:

Root cause

frontend/package.json lists "@fuzefront/i18n": "^1.0.0" as a registry dependency. The .npmrc files route all @fuzefront/* packages to npm.pkg.github.com (GitHub Packages). The package hasn't been published there yet — that only happens via packages-publish.yml on merge to master. Additionally, frontend/package-lock.json had no entry at all for @fuzefront/i18n, so npm ci tried to resolve the range ^1.0.0 from the registry and received a 404:

npm error code E404
npm error 404  '@fuzefront/i18n@^1.0.0' is not in this registry.

Fix (2 files changed)

Changed the dependency in frontend/package.json to a local file: reference:

-"@fuzefront/i18n": "^1.0.0",
+"@fuzefront/i18n": "file:../packages/i18n",

npm ci now creates a symlink (node_modules/@fuzefront/i18n → ../../packages/i18n) without any registry lookup. This is consistent with how every other tool in the repo already handles the package:

Tool Resolution
vite.config.ts aliases @fuzefront/i18npackages/i18n/src/index.ts
vitest.config.ts same alias
tsconfig.json paths @fuzefront/i18n../packages/i18n/src/index.ts

frontend/package-lock.json was regenerated with "resolved": "../packages/i18n", "link": true — no registry URLs.

Next steps

Please open a PR from claude-auto-fix-ci-feature/i18n-27935514222 targeting feature/i18n (the bot doesn't have permission to create PRs). Once merged, the feature/i18nmaster merge will publish @fuzefront/i18n to GitHub Packages via packages-publish.yml, at which point the file: reference can optionally be changed back to a version range if desired.

@github-actions

Copy link
Copy Markdown
Contributor

CI Fix — branch claude-auto-fix-ci-feature/i18n-27935584531

Root cause

frontend/package-lock.json was committed before @fuzefront/i18n, i18next, and react-i18next were added to frontend/package.json. When CI ran npm ci --include=dev in the frontend/ directory, npm detected the mismatch and tried to resolve @fuzefront/i18n@^1.0.0 from GitHub Packages — where it doesn't exist (the packages-publish workflow is gated on repo ownership being fuzefront, not izzywdev).

npm error 404 Not Found - GET https://npm.pkg.github.com/@fuzefront%2fi18n
npm error 404  '@fuzefront/i18n@^1.0.0' is not in this registry.

This broke both the Playwright sign-in flow (e2e.yml) and Lint & Test / Build Applications (ci.yml) jobs.

Fix (already pushed to claude-auto-fix-ci-feature/i18n-27935584531)

Registered packages/i18n and frontend as npm workspaces in the root package.json. npm workspace resolution now matches @fuzefront/i18n 1.0.0 from the local packages/i18n directory, so GitHub Packages is never consulted. This aligns with how Vite and TypeScript already resolve the package:

  • vite.config.ts aliases @fuzefront/i18n → packages/i18n/src/index.ts when local source exists
  • tsconfig.json has "paths": { "@fuzefront/i18n": ["../packages/i18n/src/index.ts"] }

Files changed:

  • package.json — added packages/i18n and frontend to workspaces
  • package-lock.json — regenerated; @fuzefront/i18n resolves as a workspace link ("link": true); i18next and react-i18next now properly included
  • frontend/package-lock.json — deleted (workspace members use the root lock file)
  • packages/i18n/package.json — moved @rollup/rollup-win32-x64-msvc from devDependenciesoptionalDependencies (was EBADPLATFORM on Linux)

To merge the fix into this PR, you can either:

  1. Cherry-pick the commit 1ccc186 from claude-auto-fix-ci-feature/i18n-27935584531 into feature/i18n
  2. Merge the fix branch into feature/i18n: git merge claude-auto-fix-ci-feature/i18n-27935584531

The fix branch is ready: https://github.com/izzywdev/FuzeFront/tree/claude-auto-fix-ci-feature/i18n-27935584531

…eproduction)

Close the design-system gap from the prior wiring pass:

- Make design-system/ a resolvable @fuzefront/design-system package
  (package.json with private GitHub Packages publishConfig + repository,
  index.d.ts type surface auto-generated by build.mjs alongside index.js).
- @fuzefront/i18n now depends on @fuzefront/design-system and the
  LanguageSelector renders the DS <Select> primitive instead of reproducing
  the control — surface, spacing, type, focus-ring and the tokenized chevron
  all come from the design system (zero hard-coded values).
- DS Select already styles with CSS logical properties (RTL mirror); add the
  missing 'children' prop to its .d.ts (the impl accepts <option> children).
- Resolve the DS from local monorepo source in the i18n vitest (alias +
  react/react-dom dedupe) and tsc (paths) so the no-build unit tests render
  the real DS Select; published installs resolve it from GitHub Packages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

CI Fix: Playwright sign-in flow

Branch with fix: claude-auto-fix-ci-feature/i18n-27937459216

Root cause (3 layered failures)

1. Stale package-lock.json (primary failure)
frontend/package.json was updated to add @fuzefront/i18n, i18next, and react-i18next as dependencies, but frontend/package-lock.json was never regenerated. The CI step runs npm ci which strictly requires the lockfile to be in sync — any package in package.json absent from the lockfile causes npm ci to exit immediately before the build runs.

2. vite-plugin-federation ENOTDIR crash (secondary)
The federation plugin's buildStart hook tries to read <shared-package>/package.json to detect the version. The Vite alias maps @fuzefront/i18npackages/i18n/src/index.ts (a source file). Appending /package.json to a file path produces an ENOTDIR crash. Fixed by pinning version: '1.0.0' in the shared config entry.

3. Missing @fuzefront/design-system alias + resolve.dedupe (tertiary)
LanguageSelector.tsx (in the aliased i18n source) imports @fuzefront/design-system — also a private package with no Vite alias in the frontend. The aliased source also has no node_modules, so i18next/react-i18next can't be resolved from it. Both are fixed by adding aliases and extending resolve.dedupe.

Changes in the fix branch

File Change
frontend/package.json Remove @fuzefront/i18n — resolved via Vite alias + tsconfig path; no npm dep needed
frontend/package-lock.json Regenerated; i18next and react-i18next now tracked from public npm
frontend/vite.config.ts Add @fuzefront/design-system alias; pin version for federation shared config; extend resolve.dedupe
frontend/tsconfig.json Add @fuzefront/design-system path alias for TypeScript

Verified: npm ci --include=dev and npm run build both complete successfully on the fix branch.

To merge, please create a PR from claude-auto-fix-ci-feature/i18n-27937459216 into feature/i18n.

… DS tokens)

The LanguageSelector now renders the DS <Select>, so the host frontend must
resolve @fuzefront/design-system too:

- vite + vitest: alias @fuzefront/design-system (and its subpaths) to local
  monorepo source when present, with react/i18next dedupe so the out-of-tree
  DS/i18n source resolves the host's singletons; falls back to node_modules
  inside the ./frontend Docker context.
- Module Federation: share @fuzefront/design-system as a singleton so every
  federated micro-frontend renders the same DS components/tokens.
- tsconfig: resolve @fuzefront/design-system (+ transitive react types) for the
  host type-check.
- main.tsx: import @fuzefront/design-system/styles.css so the DS token scales
  (space/type/radii/motion) the <Select> needs are present; before index.css
  so the host color theme still wins.
- package.json: add @fuzefront/design-system dependency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

CI fix branch ready

Branch claude-auto-fix-ci-feature/i18n-27937401163 contains all fixes for the CI failures on this PR. It targets feature/i18n and passes all checks locally (28/28 tests, type-check clean, npm ci clean).

Root causes fixed:

Failure Fix
npm E404 for @fuzefront/i18n and @fuzefront/design-system Switched to file: references in frontend/package.json; regenerated lockfile
TS can't resolve react/i18next/react-i18next from packages/i18n/src/ Added design-system and packages/i18n to root workspaces so deps hoist to root node_modules
Missing @fuzefront/design-system in frontend/tsconfig.json paths Added path mapping
@rollup/rollup-win32-x64-msvc EBADPLATFORM on Linux Moved to optionalDependencies
TS6133 unused React import Removed (jsx: react-jsx doesn't need React in scope)
Duplicate-React hook crash in tests Added resolve.dedupe for react/react-dom/react-i18next/i18next in vitest.config.ts
Wrong test assertion for theme.switchToDark Fixed expected value to "Switch to dark mode"

A maintainer with PR-creation permissions can open the PR from claude-auto-fix-ci-feature/i18n-27937401163feature/i18n.

@github-actions

Copy link
Copy Markdown
Contributor

CI fix pushed — branch claude-auto-fix-ci-feature/i18n-27939210136

Root cause: frontend/package.json lists @fuzefront/design-system@^1.0.0 and @fuzefront/i18n@^1.0.0 as npm registry dependencies. The .npmrc routes all @fuzefront/* scoped packages to npm.pkg.github.com (GitHub Packages). Neither package has been published there, so npm ci in the Lint & Test job hits a 404. The Notify Team job fails as a downstream consequence.

Fix: Changed both deps to file: references pointing at their local monorepo directories:

"@fuzefront/design-system": "file:../design-system",
"@fuzefront/i18n": "file:../packages/i18n",

vite.config.ts and tsconfig.json already alias both packages to their local source (added in earlier commits on this branch), so builds, type-checking, and tests all continue to resolve from the monorepo. The file: refs just make npm ci succeed without contacting the registry.

frontend/package-lock.json was regenerated to record the file: links (npm install --package-lock-only).

Branch: claude-auto-fix-ci-feature/i18n-27939210136 — PR creation was blocked by repository permissions, so please open the PR manually or merge the branch into feature/i18n.

@github-actions

Copy link
Copy Markdown
Contributor

CI Fix: Playwright sign-in flow

I've investigated the failing Playwright sign-in flow job on this PR and pushed a fix on branch claude-auto-fix-ci-feature/i18n-27939273259.

Root Cause

Two issues were masking each other — the first blocked npm ci, and the second would have blocked vite build:

  1. npm ci 404: frontend/package.json declared @fuzefront/design-system: "^1.0.0" as a registry dependency. GitHub Packages returned 404 because this package hasn't been published yet (the packages-publish.yml workflow is gated on a fuzefront org ownership transfer, so it never runs on izzywdev).

  2. vite build ENOTDIR: The existing Vite alias for @fuzefront/i18n pointed directly to packages/i18n/src/index.ts (a file). vite-plugin-federation tries to resolve @fuzefront/i18n/package.json by appending /package.json to the alias path — treating the .ts file as a directory — causing ENOTDIR.

Fix (branch claude-auto-fix-ci-feature/i18n-27939273259)

File Change
frontend/package.json + package-lock.json Changed @fuzefront/design-system and @fuzefront/i18n from registry semver ranges (^1.0.0) to file: local paths so npm ci resolves them from the checked-out monorepo
frontend/vite.config.ts Changed both aliases from a source file to the package root directory; added conditions: ['source'] to resolve.conditions so Vite still loads src/index.ts via the new export condition
packages/i18n/package.json Added "source": "./src/index.ts" export condition (Vite picks this) + "./package.json": "./package.json" so vite-plugin-federation can read package metadata

Verified locally

  • npm ci --include=dev in frontend/ ✓ (no registry lookup for @fuzefront/*)
  • npm run build ✓ (209 modules, federation shared chunks for both packages emitted)

Please merge claude-auto-fix-ci-feature/i18n-27939273259feature/i18n to unblock this PR.

AppHub Developer and others added 2 commits June 22, 2026 11:54
…ct import

The host tsconfig maps @fuzefront/i18n to its raw source, which resolves its
runtime/type deps (i18next, react-i18next, react/jsx-runtime) relative to its
own out-of-tree location with no node_modules. Map those to the host's
installed copies (matching the existing react/react-dom mapping) so the host
type-check resolves the i18n + design-system wiring. Also drop the unused
`import React` from the i18n integration test (react-jsx runtime; tripped
noUnusedLocals).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oDark)

The English Probe renders t('theme.switchToDark') -> 'Switch to dark mode'
but the assertion expected 'Switch to light mode' (the switchToLight string).
Align the assertion with the rendered key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

CI fix: @fuzefront local packages → file: references

Root cause: The root .npmrc routes all @fuzefront/* packages to npm.pkg.github.com, but @fuzefront/design-system and @fuzefront/i18n live only in this monorepo (design-system/ and packages/i18n/) and have never been published there. npm ci in the frontend directory hit a hard 404:

npm error 404 Not Found - GET https://npm.pkg.github.com/@fuzefront%2fdesign-system
npm error 404  '@fuzefront/design-system@^1.0.0' is not in this registry.

Fix (branch claude-auto-fix-ci-feature/i18n-27941144846):

  • frontend/package.json: @fuzefront/design-systemfile:../design-system, @fuzefront/i18nfile:../packages/i18n
  • packages/i18n/package.json: @fuzefront/design-systemfile:../../design-system
  • frontend/package-lock.json: regenerated with the file: entries

No behaviour changes. tsconfig.json already resolves both packages from local source via paths, and vite.config.ts already does the same via existsSync aliases. The only change is that npm ci now resolves them from the monorepo tree instead of hitting the GitHub Packages registry.

Note: this bot cannot create PRs — please merge branch claude-auto-fix-ci-feature/i18n-27941144846 into feature/i18n manually.

@github-actions

Copy link
Copy Markdown
Contributor

CI fix pushed — branch claude-auto-fix-ci-feature/i18n-27941378075

Root cause: PR #72 added @fuzefront/design-system and @fuzefront/i18n to frontend/package.json as regular dependencies resolved from GitHub Packages (https://npm.pkg.github.com). Neither package has been published there, so npm ci in the Playwright sign-in flow job failed immediately:

npm error 404 Not Found - GET https://npm.pkg.github.com/@fuzefront%2fdesign-system
npm error  '@fuzefront/design-system@^1.0.0' is not in this registry.

This caused the "Build & preview frontend" step to exit 1, skipping the Playwright tests entirely.

Fix: Remove both entries from frontend/package.json dependencies. They're already fully handled without npm:

  • vite.config.ts aliases both to ../packages/i18n/src/index.ts / ../design-system/index.js when local source is present (always the case in full-repo CI checkout)
  • vitest.config.ts has identical aliases for unit tests
  • tsconfig.json has matching paths for type-checking

The package-lock.json never had entries for these packages, so removing them restores consistency between the two files.

The fix is on branch claude-auto-fix-ci-feature/i18n-27941378075 — commit bcf4cb8. Merge it into feature/i18n to unblock CI.

@github-actions

Copy link
Copy Markdown
Contributor

Root cause

PR #72 added @fuzefront/design-system and @fuzefront/i18n as dependencies in frontend/package.json, configured to resolve from the GitHub Packages registry (npm.pkg.github.com). Neither package has been published to that registry yet, so the CI "Build & preview frontend" step was failing immediately:

npm error code E404
npm error 404 Not Found - GET https://npm.pkg.github.com/@fuzefront%2fdesign-system
npm error 404  '@fuzefront/design-system@^1.0.0' is not in this registry.

This caused npm ci to abort before the Vite build or Playwright tests ever ran.

Fix

Three targeted changes:

  1. frontend/package.json — switch both deps to file: references so npm resolves them from the local monorepo tree rather than the registry:

    • @fuzefront/design-systemfile:../design-system
    • @fuzefront/i18nfile:../packages/i18n
  2. packages/i18n/package.json — same treatment for i18n's own dependency on design-system (file:../../design-system), so npm doesn't try to fetch it from the registry when installing the file-referenced i18n package.

  3. frontend/vite.config.ts — add explicit version: '1.0.0' to the @fuzefront/i18n and @fuzefront/design-system entries in the Module Federation shared config. Without this, vite-plugin-federation tries to determine the version by reading package.json via path.join(aliasTarget, 'package.json') where aliasTarget is the Vite alias target (a source file, not a directory). That produces an ENOTDIR error at build start.

  4. frontend/package-lock.json — regenerated to reflect the file: resolutions.

The Vite build and tsc --noEmit type-check both pass locally after these changes.

Fixes #72

The design system is now a publishable @fuzefront/design-system package
(consumed by @fuzefront/i18n's LanguageSelector). Add it to the lerna packages
list so the GitHub Packages publish workflow versions + publishes it (its
build script regenerates index.js/index.d.ts during the topological build).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

CI fix — branch claude-auto-fix-ci-feature/i18n-27941300128

Root cause of the 404 in Lint & Test:

frontend/package.json listed @fuzefront/design-system@^1.0.0 and @fuzefront/i18n@^1.0.0 as dependencies. Neither package has been published to npm.pkg.github.com, so npm ci in the frontend/ directory hit a 404:

npm error 404 Not Found - GET https://npm.pkg.github.com/@fuzefront%2fdesign-system
npm error 404  '@fuzefront/design-system@^1.0.0' is not in this registry.

The frontend/package-lock.json also had zero entries for these packages (they were added to package.json after the lock file was last committed), causing npm ci to detect the mismatch and attempt to fetch the missing packages from the registry.

Fix (commit a7d995a):

Removed both packages from frontend/package.json dependencies. They don't need to be installed as npm packages because all three resolution paths already handle them from local monorepo source:

  • TypeScript (tsconfig.json paths): @fuzefront/i18n../packages/i18n/src/index.ts, @fuzefront/design-system../design-system/index.d.ts
  • Vite build (vite.config.ts aliases): same local source (guarded by existsSync — both files are present in CI)
  • Vitest tests (vitest.config.ts aliases): same local source

The existing frontend/package-lock.json already had no entries for these packages, so it's consistent with the updated package.json without any lock file changes.

The Notify Team failure is a downstream effect of Lint & Test failing and will resolve automatically.

To land this fix, please create a PR from claude-auto-fix-ci-feature/i18n-27941300128feature/i18n (the bot lacks createPullRequest permission).

@github-actions

Copy link
Copy Markdown
Contributor

CI fix pushed to claude-auto-fix-ci-feature/i18n-27941817370

Root cause

The Lint & Test job failed with a 404 when npm ci tried to install @fuzefront/design-system@^1.0.0 (and would have also failed on @fuzefront/i18n@^1.0.0) from https://npm.pkg.github.com. Neither package has been published to GitHub Packages under the fuzefront scope, so the install step cannot resolve them.

Both packages already live in the monorepo (design-system/ and packages/i18n/) and are already resolved from local source during all Vite operations (build, test, type-check) via Vite aliases and TypeScript paths. The npm ci install step had no such override and went straight to the registry.

Fix (branch: claude-auto-fix-ci-feature/i18n-27941817370, commit e6a4227)

Changed three registry version ranges to file: paths:

File Dependency Before After
frontend/package.json @fuzefront/design-system ^1.0.0 file:../design-system
frontend/package.json @fuzefront/i18n ^1.0.0 file:../packages/i18n
packages/i18n/package.json @fuzefront/design-system ^1.0.0 file:../../design-system

frontend/package-lock.json was regenerated with npm install. All 28 frontend unit tests pass locally.

Note: GitHub Actions does not have permission to open a pull request from this bot account. Please merge or cherry-pick the fix from claude-auto-fix-ci-feature/i18n-27941817370 into feature/i18n.

@izzywdev
izzywdev marked this pull request as ready for review June 22, 2026 09:14
@github-actions

Copy link
Copy Markdown
Contributor

CI fix pushed — branch claude-auto-fix-ci-feature/i18n-27941900997

Root cause

The Build & preview frontend step in the e2e job failed with:

npm error code E404
npm error 404 Not Found - GET https://npm.pkg.github.com/@fuzefront%2fdesign-system
npm error 404  '@fuzefront/design-system@^1.0.0' is not in this registry.

@fuzefront/design-system and @fuzefront/i18n were added to frontend/package.json pointing at GitHub Packages, but neither package has been published there yet. The frontend/package-lock.json was also stale — these packages (along with i18next and react-i18next) were absent from its root entry. npm ci tried to install all missing deps from their registries; @fuzefront/design-system returned 404 and the step failed.

Fix (commit d98782d on claude-auto-fix-ci-feature/i18n-27941900997)

  • Removed @fuzefront/design-system and @fuzefront/i18n from frontend/package.json dependencies. Both packages are already resolved without being npm-installed:
    • vite.config.ts and vitest.config.ts alias them to local monorepo source via existsSync when the full repo is checked out (always true in the e2e workflow)
    • tsconfig.json maps them to local source via paths for type-checking
  • Regenerated frontend/package-lock.json so its root entry is consistent and i18next + react-i18next are properly locked

To apply: merge branch claude-auto-fix-ci-feature/i18n-27941900997 into feature/i18n (a direct PR couldn't be opened due to Actions permissions).

izzywdev added a commit that referenced this pull request Jun 22, 2026
…-engineer (#85)

Fixes the cross-branch design-system duplication that stranded features (identity
#65, i18n #72, billing #81 each independently re-edited design-system/ → merge
conflicts → nothing converges).

- frontend-engineer is now the SOLE owner of design-system/: derive components
  from the user story → add missing primitives to the DS FIRST (landed as a
  foundation; one PR when features run in parallel) → then build the feature UI.
- All other agents: never edit design-system/ (consume only) — added to NOT-scope.
- New frontend-test-engineer: INDEPENDENT UI verification via Playwright, pre-prod
  (ephemeral stack, gates merge) AND post-prod (smoke vs live app). Split out from
  test-engineer, which is now scoped to API/contract/integration/event tests.
- README roles + sequence updated (DS foundation step; fe-test after fe-engineer).

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	.gitignore
#	design-system/index.d.ts
#	design-system/package.json
#	frontend/tsconfig.json
#	frontend/vite.config.ts
#	frontend/vitest.config.ts
#	lerna.json
@github-actions

Copy link
Copy Markdown
Contributor

CI Fix — branch claude-auto-fix-ci-feature/i18n-27960381781

Root cause

packages/i18n/package.json had @rollup/rollup-win32-x64-msvc listed as a direct devDependency. This is a Windows-only native Rollup binary that was accidentally committed (likely added while developing on Windows).

Because it was a direct dependency rather than an optional transitive one, the root package-lock.json recorded it without "optional": true. On Linux CI, npm ci then tried to install it and immediately failed:

npm error code EBADPLATFORM
npm error notsup Unsupported platform for @rollup/rollup-win32-x64-msvc@4.62.2:
  wanted {"os":"win32","cpu":"x64"} (current: {"os":"linux","cpu":"x64"})

This caused both the Identity UI + Security (unit) and Lint & Test (18.x) jobs to fail, which in turn caused Notify Team to fail.

Fix (already pushed)

Branch: claude-auto-fix-ci-feature/i18n-27960381781 targeting feature/i18n

  • Removed "@rollup/rollup-win32-x64-msvc": "^4.62.2" from packages/i18n/package.json devDependencies.
  • Ran npm install --package-lock-only to regenerate package-lock.json. The entry is now only present as an optional transitive dep of rollup itself ("optional": true), so npm ci on Linux skips it.

Please merge claude-auto-fix-ci-feature/i18n-27960381781 into feature/i18n to unblock CI. (GitHub Actions token cannot create PRs in this repo — someone with write access needs to open it manually or merge the branch directly.)

@github-actions

Copy link
Copy Markdown
Contributor

CI fix pushed — branch claude-auto-fix-ci-feature/i18n-27960403382

Root cause of the "Playwright sign-in flow" failure:

packages/i18n/package.json listed @rollup/rollup-win32-x64-msvc in devDependencies instead of optionalDependencies. This is a Windows-only native binary. On the ubuntu-latest CI runner, the root npm ci --include=dev step failed immediately with:

npm error code EBADPLATFORM
npm error notsup Unsupported platform for @rollup/rollup-win32-x64-msvc@4.62.2:
npm error notsup   wanted {"os":"win32","cpu":"x64"} (current: {"os":"linux","cpu":"x64"})

This killed the job before any build or Playwright step could run.

Fix applied in branch claude-auto-fix-ci-feature/i18n-27960403382:

  • Moved @rollup/rollup-win32-x64-msvc from devDependenciesoptionalDependencies in packages/i18n/package.json
  • Updated package-lock.json to add "optional": true to the package entry

npm now skips it silently on unsupported platforms. Confirmed npm ci --include=dev passes after the change.

Please merge claude-auto-fix-ci-feature/i18n-27960403382feature/i18n to fix CI on this PR.

packages/i18n declared @rollup/rollup-win32-x64-msvc in devDependencies (a Windows
npm-install artifact). Once packages/i18n became a root workspace, root `npm ci` on
the Linux CI runner hit EBADPLATFORM on that non-optional win32 binary. rollup
auto-selects its platform binary, so the pin is removed; lockfiles regenerated in a
linux container so optional native deps resolve correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

CI fix — branch claude-auto-fix-ci-feature/i18n-27961819314

Root cause

The Vite alias for @fuzefront/design-system in frontend/vite.config.ts was pointing to the entry file (../design-system/index.js) instead of the package directory:

// before (broken)
const designSystemSrc = fileURLToPath(
  new URL('../design-system/index.js', import.meta.url)
)

When Rollup resolves a subpath import like @fuzefront/design-system/styles.css, it replaces the aliased prefix with the value and appends the subpath — giving:

.../design-system/index.js/styles.css

index.js is a file, not a directory, so this fails with ENOTDIR, crashing the Vite build before the Playwright tests even start.

Fix (one line, already pushed)

// after (fixed)
const designSystemSrc = fileURLToPath(
  new URL('../design-system', import.meta.url)   // directory, not index.js
)

With a directory alias, Vite resolves:

  • @fuzefront/design-system → directory → follows exports/mainindex.js
  • @fuzefront/design-system/styles.cssdesign-system/styles.css

Build verified locally: 237 modules transformed, ✓ built in 2.74s.

The fix is on branch claude-auto-fix-ci-feature/i18n-27961819314 — merge or cherry-pick b54502e into feature/i18n to unblock the CI on this PR.

@github-actions

Copy link
Copy Markdown
Contributor

CI Fix: fix(i18n): pin i18next/react-i18next to frontend node_modules in vitest

Branch: claude-auto-fix-ci-feature/i18n-27961844841

Root cause

When @fuzefront/i18n is aliased to source (packages/i18n/src/index.ts) in frontend/vitest.config.ts, Vite resolves transitive imports (react-i18next, i18next) by walking up from the aliased file's physical location (packages/i18n/src/), not from the project root.

After root workspace npm ci, npm installs separate copies of react-i18next and i18next inside packages/i18n/node_modules/. Vite finds those for the aliased package, while the frontend uses its copies from frontend/node_modules/. The result is two isolated module singletons — the i18next event emitter used to signal initialization is a different object than the one I18nextProvider listens to, so I18nProvider's async init callback (setI18n) never fires. The rendered body stays <div /> (the null fallback) and findByRole('combobox', { name: 'Language' }) times out.

Fix

Added explicit resolve.alias entries for i18next and react-i18next in frontend/vitest.config.ts, pinning both to frontend/node_modules/. This ensures a single shared instance for all code in the test run, matching production behavior.

All 28 frontend tests now pass locally, including all 4 i18n integration tests.

The fix is in branch claude-auto-fix-ci-feature/i18n-27961844841 (one commit: f1e52dc). Note: GitHub Actions cannot create PRs — please open one manually from that branch targeting feature/i18n if you'd like CI to run against it.

izzywdev added a commit that referenced this pull request Jun 22, 2026
… frontend (draft) (#81)

* fix(e2e): repair Playwright sign-in flow (#71)

* fix(e2e): seed provisioned personal org so the sign-in shell renders

The authenticated shell renders behind WorkspaceProvisioningGate, which
only mounts the app layout once the user has a personal org. The e2e seeds
a bare admin user and relied on async login self-heal provisioning to create
that org within the test window; it never appeared, so the gate stayed on the
'Creating your workspace…' card and the .app-layout/.top-bar/.main-content
(and .app-grid-button) the specs assert never rendered.

Seed the personal org + active owner membership directly (mirroring
ensurePersonalOrg) so the gate opens immediately and the test is deterministic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(api): stop 500 on GET /organizations from double-parsing jsonb

settings/metadata are jsonb columns; the pg driver returns them already
parsed as objects, so JSON.parse(org.settings) throws ('[object Object]' is
not valid JSON) and the route 500s as soon as any org row is returned. That
500 also breaks WorkspaceProvisioningGate: its getOrganizations() poll rejects,
the gate flips to its error state, and the authenticated shell never mounts.

Add parseJsonColumn() that passes objects through and only JSON.parse()s
strings (sqlite/json-column paths), falling back to {} on invalid input.
Apply it to all four settings/metadata reads in the organizations routes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* style(e2e): use _ for unused seq loop var (actionlint SC2034 clean)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(api): GET /organizations hid all active orgs (boolean default vs string compare)

The is_active query param defaults to the boolean `true` when not sent, but
the filter compared it with `is_active === 'true'` — true === 'true' is false,
so with no param the route filtered WHERE is_active = false and returned ZERO
active orgs. The frontend WorkspaceProvisioningGate calls GET /organizations
with no params, so it never saw the user's (active) personal org and stayed
stuck on the 'Creating your workspace…' card — the actual reason the sign-in
e2e never reached the app shell.

Coerce both shapes: boolean true and string 'true' mean active.

Verified against Postgres: old filter returns 0 rows / no personal org;
new filter returns the personal org.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(prod-cd): app.fuzefront.com on Contabo k3s — overlay, app config, kafka topics, observability, CI gate (#69)

* wip(prodcd): watchdog salvage checkpoint — unpushed agent work [skip ci]

* feat(prod-cd): kafka topic pre-create Job (Phase D)

Idempotent Helm post-install/post-upgrade hook Job that creates the
identity/notify/billing prefixed topics with explicit partitions +
retention, gated behind kafkaTopics.enabled (on in values-prod). Topic
set reconciled from @fuzefront/shared TOPICS plus planned billing/chat
events.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(prod-cd): observability + helm-validate + prod-smoke (Phases E, G, H part)

- E: backend /metrics via prom-client (defensive require); prometheus.io
  scrape annotations on backend/security/applications pods; FuzeFront Grafana
  dashboard + Prometheus alert rules shipped as labeled ConfigMaps.
- G: helm-validate.yml — helm lint + kubeconform (strict, k8s 1.29) of the
  chart vs values-local/prod on PRs touching deploy/helm/**.
- H: prod-smoke.yml — poll app.fuzefront.com/api/health for 200 after a
  release: tag-bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(prod-cd): Contabo runbook + BUILDING_ON_FUZEFRONT guide (Phase H)

- CONTABO_DEPLOYMENT.md → operational runbook: release flow, rollback via
  git revert of the tag-bump commit, prune:false data safety, 2nd-node join,
  sealed-secret rotation, kafka topics, observability.
- BUILDING_ON_FUZEFRONT.md: downstream products on FuzeFront — Module-Federation
  app registration, @fuzefront/* packages, Authentik OIDC SSO, Permit scopes,
  the API, fuse-seam design language.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ci): add prom-client to lockfile so npm ci passes (#69 metrics dep)

* fix(frontend): remove unused fireEvent import (TS6133) blocking Lint & Test

* ci(claude): add @claude handler + companion auto-PR workflow (issue->PR autonomy)

Mirrors FuzeInfra's claude.yml; claude-auto-pr opens a draft PR from pushed
claude/** branches (claude-code-action only pushes+links, doesn't open PRs).
Requires repo secret ANTHROPIC_API_KEY.

---------

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ci): backend image build needs repo-root context (Dockerfile COPYs shared/ + backend/) (#73)

Co-authored-by: AppHub Developer <developer@apphub.dev>

* feat(billing-contract): OpenAPI 3.1 spec from real routes + spectral + generated client types [skip ci]

* feat(infra): declarative node-request + dispatch-to-FuzeInfra reconcile loop (#76)

* feat(infra): declarative node-request + dispatch-to-FuzeInfra loop

FuzeFront declares infra needs (deploy/terraform node-request, references a
FuzeInfra-owned contabo-k3s-node module) + Argo apps; CI path-watch fires a
repository_dispatch to FuzeInfra (sole credential holder) to reconcile. FuzeFront
holds no Contabo/cluster creds — only a scoped FUZEINFRA_DISPATCH_TOKEN. Decoupled
IaC-as-a-service via git; gating = whitelist auto-apply on the FuzeInfra side.

* fix(lint): remove unused react-hooks/exhaustive-deps disable in WorkspaceProvisioningGate (master lint was red)

---------

Co-authored-by: AppHub Developer <developer@apphub.dev>

* wip(billing-ui): scaffold @fuzefront/billing-ui package (tsup dual build, vitest, private publishConfig) [skip ci]

* wip(billing-ui): i18n layer, status helpers, token-only stylesheet; add --scrim DS token [skip ci]

* wip(billing-ui): primitives, accessible Modal, PlanCard, PlanPicker [skip ci]

* wip(billing-ui): CheckoutModal (Stripe Payment Element), SubscriptionManager, UsagePanel, PaymentMethodPanel, barrel [skip ci]

* wip(billing-ui): vitest unit + a11y + RTL tests (plans, checkout w/ mocked Stripe, subscription, panels, modal, status) [skip ci]

* fix(billing-ui): named React event/type imports (no React namespace under jsx-runtime); ignore .npm-cache [skip ci]

* test(billing-ui): scope plan-card assertions by region/selector (28→29 green) [skip ci]

* build(billing-ui): wire @fuzefront/billing-ui into lerna publish pipeline + README

* feat(agents): single-responsibility domain agents + contract-designer gate + honest-done contract (#82)

* feat(agents): add single-responsibility domain agents + scope/done contract

Adds five domain-scoped agent definitions (.claude/agents/) plus a README,
each with an exclusive scope, explicit NOT-scope (named for the orchestrator),
and a MANDATORY honest-"done" contract:

  SCOPE DONE (verified): <commands/results>
  OUT OF SCOPE — NOT DONE: <named unbuilt sibling layers>

- backend-engineer  — API/services/DB/migrations/events + own unit tests
- frontend-engineer — design-system-first UI npm package vs the contract
- test-engineer     — INDEPENDENT acceptance/contract/e2e tests vs the spec
- devops-engineer   — Helm/Argo/CI/infra-request/sealed-secrets
- docs-maintainer   — consumer/integration docs only

No agent ever declares the *feature* done/green — only its slice. A feature is
complete only when every slice's PR is green and merged (orchestrator's call).
Fixes the failure mode where one feature-agent reported DONE/GREEN while the UI
and tests were unbuilt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(agents): add contract-designer — the detailed-design phase before fan-out

No prior agent owned *creating* the contract; backend/frontend/test all only
consume it. contract-designer runs FIRST and alone: user story → frozen
OpenAPI/Swagger + Kafka Zod event schemas + generated @fuzefront/<svc>-client,
PR'd as the gate the parallel fan-out depends on. Designs the interface; does
not implement behind it. README + sequence updated to make it the sequential
gate before backend/frontend/test/devops/docs fan out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(agents): equip each domain agent with its best-fit skills

Wire the strongest available skills into each agent's How section:
- contract-designer: + writing-plans, well-architected
- backend-engineer:  + test-driven-development, systematic-debugging,
                       security-review, verification-before-completion
- frontend-engineer: + a11y-debugging, web-perf, verification-before-completion
- test-engineer:     + test-driven-development, systematic-debugging,
                       a11y-debugging, verification-before-completion
- devops-engineer:   + observability, well-architected, verification-before-completion
- docs-maintainer:   + writing-rules, verification-before-completion

verification-before-completion is wired into every implementer so the
honest-"done" report is backed by an actual verification pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* feat(fuzeone): family onboarding toolkit — "set me up as a FuzeOne member" (#83)

* feat(fuzeone): toolkit skeleton — manifest, dependency-free sync.mjs, CLAUDE block, .npmrc, caller workflows [skip ci]

WIP: reusable hub workflows + README + fuzefront-expert onboarding flow next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(fuzeone): finish toolkit — real workflow bodies, generic helm-validate, README, shims

- Caller workflows mirror the hub's actual workflows: claude/claude-auto-pr/auto-merge/
  infra-dispatch self-contained; claude-ci-autofix + telegram call the izzywdev/AITools
  reusable workflows (the real hybrid — central fixes propagate).
- helm-validate generalized to discover any chart under deploy/helm/.
- Dropped deliverable-verify (no implementation exists yet).
- README (FuzeOne layering + onboarding), cross-platform bin shims.
- Verified: dry-run, conditional gating (has-helm/has-infra), var substitution,
  CLAUDE.md region merge, idempotent re-run, --check drift exit code.

Depends on #82 (.claude/agents/*) merging — sync reads the canonical agents from the hub.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* feat(agents): frontend-engineer owns design-system; add frontend-test-engineer (#85)

Fixes the cross-branch design-system duplication that stranded features (identity
#65, i18n #72, billing #81 each independently re-edited design-system/ → merge
conflicts → nothing converges).

- frontend-engineer is now the SOLE owner of design-system/: derive components
  from the user story → add missing primitives to the DS FIRST (landed as a
  foundation; one PR when features run in parallel) → then build the feature UI.
- All other agents: never edit design-system/ (consume only) — added to NOT-scope.
- New frontend-test-engineer: INDEPENDENT UI verification via Playwright, pre-prod
  (ephemeral stack, gates merge) AND post-prod (smoke vs live app). Split out from
  test-engineer, which is now scoped to API/contract/integration/event tests.
- README roles + sequence updated (DS foundation step; fe-test after fe-engineer).

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* feat(chat): chat-service (RAG) backend + @fuzefront/chat-client (#68)

* feat(chat): chat-service helm template, litellm argo app, permit Docs/Chat resources

Unit 1 of the AI Chat (RAG) feature — deployment + authz foundation.

A. Helm: add chat-service Deployment+Service template gated by chatService.enabled
   (default false). Port 3006 (3005 taken by provisioningService). Env includes
   LITELLM_URL, CHROMA_URL, BACKEND_URL, PERMIT_PDP_URL, KAFKA_BROKERS, DB_* and
   JWT_SECRET from chart Secret. Conditional ANTHROPIC_API_KEY / OPENAI_API_KEY /
   LITELLM_MASTER_KEY from Secret when set. Add chatService: block to values.yaml and
   three new empty-placeholder secret keys.

B. Argo: add deploy/argocd/applications/litellm.yaml pointing at FuzeInfra/helm/litellm
   (companion FuzeInfra PR creates that chart). app-of-apps needs no change (directory
   sweep). No separate Argo app for chat-service (umbrella fuzefront chart handles it).

C. Docs: docs/ai-chat/fuzeinfra-companion-spec.md — precise spec for the FuzeInfra
   companion PR: full LiteLLM Helm chart templates + model config, ChromaDB enablement
   (flip chromadb.enabled + template spec if missing), and fuzeinfra-ai-keys Secret spec.

D. Permit: add Docs (action: read) and Chat (actions: stream, manage) resources to both
   backend/src and backend/security/src schema.ts files. Grant all three roles Docs:read
   and Chat:stream; restrict Chat:manage to admin only. Extend permit-schema.test.ts with
   8 tests total (3 new role-grant assertions + updated resource list + idempotency paths).

Helm: lint passes, template renders correctly (gated off by default, renders on enable).
Tests: 8/8 permit-schema tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(chat): clarify FuzeInfra submodule bump in companion spec; restore viewer manage guard

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(shared): add billing.llm.usage kafka topic + zod schema

- Add BILLING_LLM_USAGE to TOPICS const in shared/src/kafka/types.ts
- Create billingLlmUsageSchemaV1 with uuid/int/datetime validators; no version in payload (lives on FuzeEvent envelope)
- Export BillingLlmUsagePayloadV1 inferred type
- Wire export through schemas/index.ts
- Add 5-case describe block in email-service/tests/schemas.test.ts; update TOPICS count assertion

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat-client): @fuzefront/chat-client SSE+HTTP client for chat-service

Implements Unit 3 of the AI Chat RAG feature plan (tasks T1.1, T1.2, T1.3).

- New package packages/chat-client (@fuzefront/chat-client v1.0.0, MIT)
- src/types.ts: ChatStreamRequest, ChatStreamEvent union (7 variants), RagSource,
  Conversation, ConversationMessage, ConversationWithMessages
- src/streaming.ts: parseSSEStream() generator — accepts ReadableStream<Uint8Array>
  or AsyncIterable<string>; uses eventsource-parser v1.x; yields typed ChatStreamEvent;
  stops at {type:'done'}; skips malformed JSON lines
- src/client.ts: ChatServiceClient class — streamChat (SSE), confirmTool, listConversations,
  getConversation, submitFeedback; yields {type:'error'} on streamChat errors, throws on others
- src/index.ts: barrel re-export
- Registered in lerna.json packages array and root package.json workspaces array
- 22/22 tests pass (streaming.test.ts 8, client.test.ts 14); tsc --noEmit clean

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(chat-client): untrack built dist/ (CI/publish builds it)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat-service): scaffold service - config, health, jwt auth, rate-limit, chat db migrations

- New services/chat-service package (@fuzefront/chat-service 1.0.0, private:true)
- Express app with GET /health (unauthenticated), graceful SIGTERM/SIGINT shutdown
- config.ts reads all env vars set by Helm chat-service.yaml template; REDIS_URL falls
  back to fuzeinfra default (no Helm mismatch that requires template edits)
- Stateless JWT auth middleware: jwt.verify -> req.userId + req.orgId; no DB lookup;
  no console.log noise (§10d)
- Rate-limit middleware: express-rate-limit 7.x + rate-limit-redis 4.x; three factory
  fns (stream 20/min, confirm 60/min, global 100/min §10f); Redis injectable for tests;
  degrades to in-memory if Redis unavailable (lazyConnect, no startup crash)
- DB knexfile mirrors backend/knexfile.ts; 001_create_chat_tables migration with exact
  SQL from plan §6e (4 tables: chat_conversations, chat_messages, chat_audit_log,
  chat_feedback); idempotent up/down
- 4 test suites, 11 passing, 2 skipped (live-DB migration, no Postgres in env)
- Dockerfile mirrors email-service (multi-stage node:18-alpine, user chatservice, port 3006)
- services/chat-service added to lerna.json packages (not root workspaces — matches
  email-service pattern)
- tsc --noEmit: clean

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* wip(chat): watchdog salvage checkpoint — unpushed agent work [skip ci]

* feat(chat-service): RAG retrieval, agent loop, chat routes, billing emitter

Implements the read-only RAG path + streaming chat backend on top of the
scaffold (Plan F / AI chat RAG):

- llm/litellm: OpenAI-compat LiteLLM client (chat completions + embeddings,
  streaming SSE chunk parse); adopts the gateway, no provider SDK.
- rag/{chunker,embedder,chroma,indexer,retriever}: deterministic chunking,
  ChromaDB REST client, content-hash idempotent indexer, top-k retriever.
- rag/index-docs: CLI entrypoint for the chat-doc-indexer Job.
- db/repositories/{conversations,messages,feedback}: scoped by JWT userId,
  never request body (§10d).
- agent/prompt: injection-resistant system prompt, <doc>-wrapped context,
  input sanitization (§10a/§10b).
- agent/{permit,confirmation,tools}: fail-closed PDP client, owner-scoped
  confirmation state machine, read-only search_docs tool (mutating tools
  deferred).
- agent/loop: retrieve -> rag_sources -> text_delta... -> done, usage report.
- billing/emitter: emits billing.llm.usage to Kafka, non-blocking on failure.
- routes/chat: POST /chat/stream (SSE), conversations, feedback,
  confirm/:id; behind auth + per-route limiters; persists + bills.
- app/index: composition root wiring all of the above.
- helm: chat-doc-indexer Job template; chatService.resources limits +
  docIndexer/embeddingModel values. Dockerfile copies docs corpus.
- shared/dist: regenerate kafka .d.ts (billing.llm.usage) + barrel export.

Wire format = chat-client's SSE event union (text_delta/rag_sources/done/
error), a deliberate deviation from plan §6f (AI-SDK data stream).

Verification: tsc --noEmit clean; jest 85 tests (83 pass, 2 skipped live-DB).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(chat): extract deploy wiring to chat-devops slice

#68 is now the chat-service backend + @fuzefront/chat-client only. The Helm
templates (chat-service, doc-indexer), LiteLLM Argo app, and chat secret/values
moved to the chat-devops PR (devops slice), which merges after this backend lands.
Also merges origin/master so this branch no longer reverts the prod-CD work
(observability, kafka-topics-job, node-request) it was behind on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(deploy): chat-service + doc-indexer Helm templates, LiteLLM Argo app, chat secret/values (#84)

Devops slice extracted from #68 (the chat feature was bundling deploy wiring).
Owned/reviewed as the devops slice; merges after the chat-service backend (#68).
Chart renders coherently (chat-service template + values + secret keys together).

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* ci: enforce in-repo packages resolve from source (prevent PR #65 404 class) (#86)

Adds scripts/check-workspace-deps.mjs + a Workspace deps CI gate that fails when
a consumer references an in-repo package by a registry spec (e.g. "^0.1.0")
without it resolving as a local workspace — exactly the PR #65 break where
frontend listed "@fuzefront/identity-ui": "^0.1.0" for an unbuilt in-repo package
and `npm ci` 404'd against the registry.

The check is dependency-free (no install), passes on master, and fails the
#65-class violation with an actionable fix message. Also exposed as
`npm run check:workspace-deps`. Pairs with fuzefront-ui-package skill rule #7.

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* WIP: identity-management UI + API tokens (@fuzefront/identity-ui) (#65)

* feat(security): add organization members CRUD endpoints

Implements GET/POST/PUT/DELETE for /api/organizations/:id/members in the
security service. GET returns a bare member array with nested user objects
(firstName/lastName camelCase) to match the existing frontend contract in
MembersManagement and OrganizationPage. POST creates a pending invitation row
(same path as /:id/invitations). PUT/DELETE guard owner memberships with 403.
Permit role assignment is non-blocking on all mutating routes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): filter members list to active + assert user.id in test

Add .where('organization_memberships.status', 'active') to the GET
/:id/members list query so only active members are returned. Add
assertion on member.user.id in the GET happy-path test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(security): add api_tokens migration (010)

Creates the api_tokens table with SHA-256-hashed opaque tokens, polymorphic
owner_id (no FK), created_by FK with ON DELETE SET NULL, scopes jsonb, and
expiry/revocation timestamps. Enum creation guarded by DO $$ ... EXCEPTION block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(security): implement API token service with TDD

Add backend/security/src/services/api-token.ts with full token lifecycle:
generateToken (ff_live_ format, base62 prefix, base64url body), hashToken,
extractParts, createToken, verifyToken (timingSafeEqual, VerifyResult discriminated
union), revokeToken, listTokensForOwner, getTokenById, updateLastUsed, and
mapScopesToPermitRole (minimal-role algorithm from permitSchema). 48 unit tests
cover all pure functions and mocked-DB operations including security invariants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): parse api-token scopes on read; tighten ApiTokenRow type; add base62 test

- Add parseScopes() helper (handles pg string or already-array mock)
  and call it on all four read paths: createToken return, verifyToken
  valid branch, listTokensForOwner map, getTokenById return — so
  callers always receive scopes as string[] not a JSON string.
- Split ApiTokenRow into internal ApiTokenDbRow (includes token_hash)
  and exported ApiTokenRow = Omit<ApiTokenDbRow,'token_hash'|'scopes'>
  & { scopes: string[] } so callers cannot believe token_hash is present.
- Export encodeBase62 and add describe('encodeBase62') with 6 tests
  including pinned known-vector (0xdeadbeef -> '44pZgF') to catch
  silent algorithm regressions.
- Add scopes round-trip tests for createToken and verifyToken with mock
  DB returning scopes as a JSON string (real pg behaviour).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(security): API-token auth middleware + req.apiToken typing + rate limiting

- Add authenticateFlexible middleware that branches on ff_live_ bearer tokens
  vs JWTs (JWT path delegates to core's authenticateToken unchanged)
- PAT path: loads user row from DB, builds same User shape as core JWT middleware
- Service-token path: synthetic svc_token:<id> principal with roles ['service']
- Add tokenAuthRateLimiter (express-rate-limit 7.2.0, skipSuccessfulRequests:true,
  10 failed attempts per IP per 60s → 429)
- Extend Express.Request with apiToken?: { id, scopes, ownerType, ownerId }
- 11 tests covering all paths incl. rate-limit 11th-request 429

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(security): API token routes, Permit sync helpers, scope enforcement

- Add syncServiceTokenToPermit / removeServiceTokenFromPermit to user-sync.ts
- Create routes/api-tokens.ts: POST/GET/DELETE /api/tokens, GET /:orgId/tokens
  via orgTokensRouter, and the requireTokenScope middleware export
- Mount /api/tokens + /api/organizations (org-tokens sub-route) in index.ts
  with tokenAuthRateLimiter
- 28 tests covering all brief cases (create, 403, 400, ownership, org-admin,
  list, revoke, Permit-sync, requireTokenScope)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): deterministic token-route test + mount order + surface permit-sync false

I-1: Replace setTimeout(20) timing hacks in api-tokens.routes.test.ts with
     deterministic microtask flushes (triple Promise.resolve()) for both the
     org-token create and org-token revoke fire-and-forget assertions.

I-2: In index.ts, mount orgTokensRouter BEFORE organizationsRoutes so the
     specific /:orgId/tokens path cannot be shadowed by future wildcards.

M-2: Change syncServiceTokenToPermit and removeServiceTokenFromPermit
     fire-and-forget calls from .catch-only to .then(ok=>warn-if-false).catch
     so false-return failures are surfaced in logs, not silently dropped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(design-system): add Modal, DataTable, Textarea, FileDropZone + tokens

- New tokens: --modal-max-w (560px), --modal-max-w-lg (720px) in spacing.css;
  --drop-active (rgba indigo 0.18) in colors.css (both themes)
- Modal: accessible dialog shell in new overlay/ category — focus trap,
  Escape close, backdrop close, fuse-seam top bar, role/aria-modal/labelledby
- DataTable: semantic table shell in new data/ category — headless-friendly
  (consumer renders <tbody>); sort carets + aria-sort; 5-row skeleton with
  --bg-quaternary pulse; emptyState slot
- Textarea: mirrors Input.jsx exactly but renders <textarea> with vertical resize
- FileDropZone: drag-drop target in forms/; keyboard-activatable (Enter/Space);
  --drop-active dragover fill; visible --accent-soft focus ring
- Regenerated _ds_manifest.json and index.js: 18→22 components, 144→147 tokens

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* wip checkpoint: @fuzefront/design-system package + identity-ui scaffold start; SDD ledger [skip ci]

* wip(identity): watchdog salvage — token/members/invite UI components (unverified) [skip ci]

* feat(frontend): mount IdentityPage in OrganizationPage members tab + MF shared scope

Wire @fuzefront/identity-ui into the host shell: replace MembersManagement
with <IdentityPage>, add the package to frontend deps and Module Federation
shared scope. Frontend build/type-check verified in CI (Windows-local Vite
build is the documented os=linux gotcha).

[skip ci]

* fix(identity-ui): regenerate lockfile for new workspaces + tsc jest-dom types + CI coverage

- package-lock.json was stale: it predated the `packages/identity-ui` and
  `design-system` workspace members being added to the root `workspaces`, so
  root `npm ci` failed ("Missing ... from lock file"). Regenerated cross-platform
  (lockfileVersion 3) so it includes both new workspaces, their deps (vitest,
  @tanstack/react-table, react-hook-form, papaparse, zod) and both linux-x64 and
  win32-x64 native binaries — `npm ci` now works on Linux CI and Windows.
- identity-ui test setup: import `@testing-library/jest-dom/vitest` (not the bare
  entrypoint) so jest-dom augments vitest's `Assertion` interface — `tsc --noEmit`
  (the `type-check` script, which includes `src/**/*.test.tsx`) now recognises
  `toBeInTheDocument`.
- ci.yml: add an `identity-ui-and-security` job (Linux) that runs the
  @fuzefront/identity-ui type-check + vitest + library build (asserting es/cjs/d.ts
  artifacts), plus the security-service API-token jest suite (DB-mocked, no Postgres).
  This is the canonical clean-Linux verification for PR #65.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(identity-ui,design-system): label/control association + EmptyState title + member test fixture [skip ci]

* fix(identity-ui): assert exact 'Name is required' validation, not ambiguous /required|name/

The /required|name/i query matched both the 'Token name' label and the error,
failing the empty-name unit test with 'found multiple elements'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(frontend): resolve @fuzefront/identity-ui + design-system from source [skip ci]

Remove unpublished @fuzefront/identity-ui from frontend deps (was 404ing npm ci);
alias both @fuzefront UI packages to source in vite/vitest/tsconfig.
Keep identity-ui in Module-Federation shared list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(identity-ui): remove unused imports; consume built d.ts in frontend type-check

Two real type errors remained after the source-resolution fix:
1. TS6133 unused React imports (5 files) + unused IconButton (TokenList) —
   noUnusedLocals + react-jsx automatic runtime. Removed.
2. TS2322 csstype CSSProperties clash in TokenList: the frontend's tsc was
   compiling identity-ui SOURCE, so identity-ui's React types (root @types/react)
   clashed with the frontend's own @types/react/csstype copy (frontend is not a
   root workspace, so it gets its own).

Fix (2) mirrors @fuzefront/design-system: frontend/tsconfig.json now resolves
@fuzefront/identity-ui to its built dist/index.d.ts instead of src, so tsc
consumes validated types (skipLibCheck) rather than recompiling source under a
duplicate csstype. vite/vitest still resolve from source via their aliases for
bundling and tests. CI builds identity-ui before the frontend type-check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(build): declare socket.io-client in shared; drop @fuzefront UI from federation shared

- @fuzefront/shared/src/hooks/useSocketBus.ts imports socket.io-client but it was
  never declared (latent since initial commit; #65 CI now builds shared and TS2307'd).
  Add socket.io-client ^4.7.5 (matches backend socket.io server) + regen lockfile.
- frontend vite federation `shared` listed @fuzefront/identity-ui + design-system,
  which are aliased to source FILES — the plugin read `<file>/package.json` → ENOTDIR
  and failed `vite build`. They are host-bundled; only react/react-dom stay shared.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(billing-ui): regenerate lockfiles on linux (avoid win32 EBADPLATFORM on CI)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(billing-ui): register billing-client + packages/billing-ui in root workspaces

The agent's workspace registration was left unstaged; the merge commit omitted it,
so CI's workspace-deps gate saw @fuzefront/billing-client (a peerDep ^1.0.0) as an
unregistered in-repo package. Register both so it resolves from source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(billing): renumber backend/security migration 010->011 (collision with identity 010_api_tokens)

#65 (on master) added backend/security migration 010_create_api_tokens_table; billing's
010_add_billing_to_entities collided. Renumbered to 011 to restore a unique ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: AppHub Developer <developer@apphub.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AppHub Developer and others added 2 commits June 22, 2026 18:32
…stance

frontend/src/i18n/integration.test.tsx crashed with 'Cannot read properties of
null (reading useMemo)' — @fuzefront/i18n is resolved from source and pulls
react-i18next with a nested react copy (packages/i18n/node_modules/react), giving
two React instances → invalid hook call. Restore the resolve.dedupe array (lost
when the configs were taken from master during the merge). Full frontend suite:
28/28 green (verified in a linux container).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

CI Failure Root Cause: pg_isready health-check targets a non-existent database

Job: Playwright sign-in flow
Symptom: 12× FATAL: database "fuzeinfra" does not exist at 10-second intervals, then the Postgres service container is marked unhealthy and the job fails before any step runs.

Root cause

The e2e.yml Postgres service is configured as:

env:
  POSTGRES_USER: fuzeinfra
  POSTGRES_DB: fuzefront_platform   # ← only this DB is created
options: >-
  --health-cmd "pg_isready -U fuzeinfra"   # ← no -d flag

When pg_isready is called without -d, libpq defaults to a database named the same as the user — i.e., fuzeinfra. That database was never created (only fuzefront_platform was). In PostgreSQL 15 this produces PQPING_REJECT, so pg_isready exits with code 2 on every attempt. After 5 retries the container is marked unhealthy and GitHub Actions kills the job.

Fix

Add -d postgres to the health-check command (the postgres maintenance database is always present):

-          --health-cmd "pg_isready -U fuzeinfra"
+          --health-cmd "pg_isready -U fuzeinfra -d postgres"

This is a one-line change in .github/workflows/e2e.yml line 26.

Why I couldn't push

The GitHub App token used by this bot does not have the workflows scope required to modify files under .github/workflows/. The fix is committed locally on branch claude-auto-fix-ci-feature/i18n-27964791344 but cannot be pushed. A maintainer with a PAT that has the workflows permission can apply this patch:

git diff HEAD~1 HEAD   # shows the one-line change

or simply edit e2e.yml line 26 to add -d postgres as shown above.

@github-actions

Copy link
Copy Markdown
Contributor

CI failure investigation (automated fix branch: claude-auto-fix-ci-feature/i18n-27964802822)

Root cause: The Vite alias for @fuzefront/design-system pointed to the file design-system/index.js instead of the package directory. When frontend/src/main.tsx imports @fuzefront/design-system/styles.css, Vite does a plain prefix-replacement:

@fuzefront/design-system → /…/design-system/index.js
@fuzefront/design-system/styles.css → /…/design-system/index.js/styles.css  ← ENOTDIR

index.js is a file, not a directory, so the path is invalid and the build fails.

Fix applied in frontend/vite.config.ts (1-line change):

-  new URL('../design-system/index.js', import.meta.url)
+  new URL('../design-system', import.meta.url)

Pointing the alias at the directory lets Vite:

  • Resolve the bare @fuzefront/design-system import via package.json exports["."]index.js
  • Resolve @fuzefront/design-system/styles.css via prefix replacement → /…/design-system/styles.css

The Notify Team job failure is purely downstream of Build Applications failing — it will pass once the build is green.

The fix is on branch claude-auto-fix-ci-feature/i18n-27964802822 — please open a PR from that branch targeting feature/i18n (GitHub Actions token lacks PR-creation permission).

…les.css

main.tsx imports '@fuzefront/design-system/styles.css'; the exact alias maps the
package to the index.js FILE, so the subpath became index.js/styles.css → ENOTDIR
and 'vite build' (Build Applications) failed. Add a '@fuzefront/design-system/'
-> directory alias before the exact one. Verified: vite build green in a linux
container (237 modules).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@izzywdev
izzywdev merged commit 8368905 into master Jun 22, 2026
13 of 17 checks passed
@izzywdev
izzywdev deleted the feature/i18n branch July 27, 2026 18:27
@izzywdev
izzywdev restored the feature/i18n branch July 29, 2026 05:10
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