# @acedatacloud/core

Shared frontend core for AceDataCloud's three browser apps:

- [`AuthFrontend`](https://github.com/AceDataCloud/AuthFrontend) — `auth.acedata.cloud`
- [`Nexior`](https://github.com/AceDataCloud/Nexior) — `studio.acedata.cloud` (consumer / chat)
- [`PlatformFrontend`](https://github.com/AceDataCloud/PlatformFrontend) — `platform.acedata.cloud` (developer / open API)

These apps had independently re-implemented the same plumbing (axios interceptors,
chunk-load recovery, telemetry, fingerprint, locale resolution) and drifted on the
details. This package lifts the **correctness-sensitive duplication** into one place so
the apps stop diverging on the parts where divergence causes real auth / observability bugs.

## Distribution — published npm package

Consumed via a normal `package.json` dependency (no submodule, no `@common` alias):

```bash
npm install @acedatacloud/core
```

The package is built with plain `tsc` to ESM (no Vite library mode) so any consumer's
bundler (Vite 8 / 7 / 2) transpiles it cleanly. CI **auto-publishes on merge to `main`**,
version-gated: a merge publishes only when `package.json`'s version is not already on npm.
To ship a release, bump the version in `package.json` in your PR.

### Subpath entry points

Import only what you need — peer-dependent modules are subpath-only so they tree-shake:

```ts
import { createChunkLoadErrorHandler, makeGetLocale, isWechat } from '@acedatacloud/core/core';
import { createHttpClient } from '@acedatacloud/core/http';           // peer: axios
import { createTelemetry } from '@acedatacloud/core/telemetry';       // peer: aegis-web-sdk (optional)
import { createFingerprintResolver } from '@acedatacloud/core/fingerprint';
import type { IUser, IConnection } from '@acedatacloud/core/types';
import type { ITranslationEnableRequest, ITranslationEnableResponse } from '@acedatacloud/core/types';
import { CURRENCY_LABEL_MAPPING, LOCALE_CURRENCY_MAPPING } from '@acedatacloud/core/constants';
import { debug, getBaseUrlAuth } from '@acedatacloud/core/utils';
import { replaceBrandText } from '@acedatacloud/core/brand-spacing';
import { extractX402PaymentError, resolveX402PaymentError } from '@acedatacloud/core/x402';
```

The root entry (`@acedatacloud/core`) re-exports the dependency-free surface
(`core`, `types`, `constants`, `utils`).

### Shared components

Import the shared stylesheet once in the application entry, then import components from the dedicated subpath:

```ts
import '@acedatacloud/core/styles.css';
import { Pagination } from '@acedatacloud/core/components';
```

`Pagination` preserves the existing Nexior and PlatformFrontend props and `change` event contract. Its round controls use the shared `--adc-radius-round` token in `styles.css`.

### Design tokens

`styles.css` also defines the shared visual baseline without globally styling application elements:

- theme-aware color, text, surface, and border aliases;
- the `4 / 8 / 12 / 16 / 20 / full` radius scale with `small / control / card / dialog` semantics;
- a 4px spacing grid;
- shared font-family, size, weight, line-height, and letter-spacing tokens.
- 32/40/48px control density and a 40px icon target;
- focus outline/ring, elevation, motion, and stacking-layer tokens;
- reduced-motion duration overrides.

Consumers opt in by mapping their existing root variables and component styles to `--adc-*`. Runtime accent colors and light/dark surfaces remain owned by each application's Element Plus theme.

### Core control adapter

Import the control adapter after vendor and application styles — **including Tailwind** — so its variant-safe Element Plus contract wins without `!important`:

```ts
import '@acedatacloud/core/styles.css';
import './assets/scss/style.scss';
import './assets/css/tailwind.css';
import '@acedatacloud/core/controls.css';
```

Keeping `controls.css` last is load-bearing, not cosmetic. It carries an `svg { display: inline-block }` reset that undoes Tailwind preflight's `svg { display: block }` (which otherwise breaks `text-align: center` on icons). Both selectors have the same specificity, so the reset only wins by coming later — import Tailwind after `controls.css` and it silently stops working.

The adapter standardizes Button, Input, Select, Textarea, Card, Dialog, and MessageBox while preserving Element's link/text/circle sizes, error states, input-group seams, and card shadow modes. Applications can retain product-specific card styling through `--adc-control-card-border`, `--adc-control-card-shadow`, `--adc-control-card-shadow-hover`, and `--adc-control-dialog-shadow`; because dialogs and message boxes are teleported, set overlay hooks on `:root` or `body`.

### Semantic icons

`@acedatacloud/core/icons` defines one Lucide glyph for each product semantic. Brand logos remain official SVG/brand assets and are not mapped to Lucide. `@acedatacloud/core/icons/components` statically re-exports the complete reviewed semantic set; the original single-icon subpaths remain available for compatibility.

Consumers that use icon subpaths must exact-pin the tested peer in the same adoption PR:

```bash
npm install --save-exact @lucide/vue@1.23.0
```

The manifest is a review source, not a dynamic runtime resolver. Consumer code uses named imports, for example `import { AddIcon, SearchIcon } from '@acedatacloud/core/icons/components'`; bundlers tree-shake unused named re-exports. Do not use `import * as Icons`, Lucide `DynamicIcon`, or arbitrary icon-name strings in production bundles. Legacy call sites are migrated by product meaning, never by globally replacing a Font Awesome glyph.

## Layout

```
src/
├── core/            # Pure TS, zero framework deps
│   ├── chunkLoadError.ts   createChunkLoadErrorHandler({ storagePrefix })
│   ├── surface.ts          isWechat(), isMobile()
│   └── locale.ts           makeGetLocale({ supportedLocales, defaultLocale })
├── http/            # peer: axios
│   └── createHttpClient.ts createHttpClient(config) — request/response interceptor contract
├── telemetry/       # peer: aegis-web-sdk (optional, dynamically imported)
│   └── createTelemetry.ts  createTelemetry({ projectId, hostUrl }) — Tencent RUM glue
├── fingerprint/     # FingerprintJS loader injected (no SDK dep — v4/v5 both work)
│   └── createFingerprintResolver.ts
├── types/           # Pure TypeScript interfaces (user, connection, skill, oauthApp)
├── brand-spacing/   # Unicode-aware boundaries; consumers own source/replacement policy
├── constants/       # base URLs plus shared locale/currency mappings
├── utils/           # getBaseUrl* helpers plus the shared console.debug alias
└── index.ts         # root re-export of the dependency-free surface
```

### Design notes

- **Factories, not singletons.** Everything that differed across apps (timeout, header
  casing, where the token/userId/fingerprint come from, what a 401 does, RUM ext-slot
  mapping, FingerprintJS version) is injected via config. The package never imports a store,
  a router, or `vuex`.
- **What is intentionally *not* here:** Vuex stores, app-specific routing/auth helpers,
  brand identity/policy, and business workflows. `brand-spacing` is a dependency-free text-boundary helper; each consumer owns its source variants and replacement brand. Element Plus components are shared only when the
  existing app implementations and public contracts are the same.

## Develop

```bash
npm install
npm run typecheck   # tsc -p tsconfig.json (no emit)
npm test            # vitest (jsdom)
npm run build       # tsc -p tsconfig.build.json -> dist/
```

## License

Private — AceDataCloud internal.
