@sybilion/uilib 1.0.21 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ # Private npm registry (local + CI)
2
+
3
+ This document describes a typical setup using **Verdaccio** as a lightweight private registry. Alternatives include **npm Enterprise**, **GitHub Packages**, **GitLab Package Registry**, **Azure Artifacts**, and **JFrog Artifactory**; the **auth and CI patterns** below apply to any registry that supports npm’s token-based login.
4
+
5
+ ## 1. Run a local private registry (Verdaccio)
6
+
7
+ Install and start (no project required):
8
+
9
+ ```bash
10
+ yarn global add verdaccio
11
+ verdaccio
12
+ ```
13
+
14
+ Default URL: `http://localhost:4873`. Open that URL in a browser to confirm the UI.
15
+
16
+ - **Persistence**: config and storage live under `~/.config/verdaccio` by default (see [Verdaccio docs](https://verdaccio.org/docs/configuration) for `storage`, `auth`, and uplinks).
17
+ - **Use the public registry for non-private packages**: Verdaccio usually **proxies** `https://registry.npmjs.org` for packages you did not publish locally, so `yarn install` can stay one command.
18
+
19
+ ## 2. Publish a package to the private registry
20
+
21
+ One-off login (creates/updates auth in `~/.npmrc`):
22
+
23
+ ```bash
24
+ npm adduser --registry http://localhost:4873
25
+ ```
26
+
27
+ In the package you want to publish (`uilib` or another repo), set the registry for that scope or package. Examples.
28
+
29
+ **Scoped package** (`@sybilion/uilib`):
30
+
31
+ ```ini
32
+ # .npmrc next to package.json (or in user ~/.npmrc)
33
+ @sybilion:registry=http://localhost:4873/
34
+ ```
35
+
36
+ **Unscoped package**: either publish only to Verdaccio for that session:
37
+
38
+ ```bash
39
+ npm publish --registry http://localhost:4873
40
+ ```
41
+
42
+ or set `publishConfig.registry` in `package.json` for that package.
43
+
44
+ Then:
45
+
46
+ ```bash
47
+ yarn npm publish
48
+ # or: npm publish
49
+ ```
50
+
51
+ ## 3. Install from the private registry on a developer machine
52
+
53
+ In the **consumer** repo, point installs at the registry. Scoped example:
54
+
55
+ ```ini
56
+ @sybilion:registry=http://localhost:4873/
57
+ ```
58
+
59
+ If the registry requires auth to **read** (you enabled it in Verdaccio), add a token line as in section 4.
60
+
61
+ For **local-only** workflows, some teams use **Verdaccio in Docker** or a **fixed hostname** (e.g. `registry.dev.sybilion.internal`) so URLs match between laptops and CI.
62
+
63
+ ## 4. Authentication tokens (needed for CI and locked-down reads)
64
+
65
+ Registries use **bearer tokens**. After `npm login`, your user `.npmrc` often contains something like:
66
+
67
+ ```ini
68
+ //localhost:4873/:_authToken="…"
69
+ ```
70
+
71
+ For CI you should **not** commit tokens. Instead:
72
+
73
+ 1. Create a **read-only** user/token for install jobs and a **separate** publish token if needed.
74
+ 2. Store the token in the CI secret store (see below).
75
+ 3. At job start, write `.npmrc` or set env vars the tooling understands.
76
+
77
+ **npm / Yarn (classic)** often respect:
78
+
79
+ ```bash
80
+ export NODE_AUTH_TOKEN="…"
81
+ ```
82
+
83
+ with an `.npmrc` in the repo or generated in CI:
84
+
85
+ ```ini
86
+ @sybilion:registry=https://your-registry.example/
87
+ //your-registry.example/:_authToken=${NODE_AUTH_TOKEN}
88
+ always-auth=true
89
+ ```
90
+
91
+ Exact host and path must match your registry URL. Verdaccio and cloud registries each document their token format.
92
+
93
+ ## 5. How organization CI works with a private registry
94
+
95
+ High-level flow:
96
+
97
+ 1. **Registry URL** is fixed for the org (self-hosted Verdaccio behind HTTPS, or GitHub/GitLab/etc.).
98
+ 2. **Secrets**: store `NODE_AUTH_TOKEN` (or registry-specific secret) in:
99
+ - **GitHub Actions**: repository or organization secrets
100
+ - **GitLab CI**: CI/CD variables (masked/protected as appropriate)
101
+ - **Buildkite / CircleCI / Jenkins**: equivalent secret stores
102
+ 3. **Install step** runs after the token is available: `yarn install` / `npm ci` resolves scoped packages from the private registry; the rest still comes from the public npm proxy or npmjs.org.
103
+ 4. **Publishing** (if the pipeline publishes packages) uses a job with a **publish** token, often only on protected branches or tags.
104
+
105
+ ### GitHub Actions (illustrative)
106
+
107
+ ```yaml
108
+ jobs:
109
+ build:
110
+ runs-on: ubuntu-latest
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+ - uses: actions/setup-node@v4
114
+ with:
115
+ node-version: '20'
116
+ registry-url: 'https://npm.pkg.github.com'
117
+ # or your Verdaccio HTTPS URL — match .npmrc scope
118
+ - run: yarn install --frozen-lockfile
119
+ env:
120
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
121
+ - run: yarn test
122
+ ```
123
+
124
+ Adjust `registry-url` and `.npmrc` to match **your** registry (GitHub Packages uses a specific host; self-hosted Verdaccio uses yours).
125
+
126
+ ### Caching
127
+
128
+ CI can cache `node_modules` or Yarn’s cache **after** auth works; failed auth usually shows `401`/`403` during `yarn install`.
129
+
130
+ ### Lockfiles
131
+
132
+ Commit **yarn.lock** / **package-lock.json** so CI installs deterministic versions. Private packages are referenced by version (or tarball URL) like public ones.
133
+
134
+ ## 6. Choosing local Verdaccio vs a hosted org registry
135
+
136
+ | Approach | Good for |
137
+ | ------------------------------------ | ------------------------------------------------------ |
138
+ | **Verdaccio on localhost** | Quick feedback, testing publish/install before pushing |
139
+ | **Verdaccio on a server / K8s** | Single org registry, full control, air-gapped options |
140
+ | **GitHub Packages / GitLab / Azure** | Tight integration with repos, SSO, less ops |
141
+
142
+ CI for consuming repos is the same idea everywhere: **registry URL + secret token + lockfile**.
143
+
144
+ ## 7. GitHub Packages
145
+
146
+ For GitHub-hosted npm packages (`npm.pkg.github.com`), scopes, tokens, Actions, and Docker secrets, see **[github-packages.md](./github-packages.md)**.
147
+
148
+ ## 8. References
149
+
150
+ - [Verdaccio](https://verdaccio.org/)
151
+ - [npm scope and `.npmrc`](https://docs.npmjs.com/cli/v10/using-npm/scope)
152
+ - [GitHub Packages npm](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry)
@@ -0,0 +1,21 @@
1
+ # Workspace mini-apps (Sybilion iframe)
2
+
3
+ The Sybilion client embeds mini-apps in an **iframe** and syncs theme with `postMessage`. Use **`MiniAppRoot`** so this document’s `<html>` gets **`light` / `dark`** (uilib uses `.dark { … }` for tokens).
4
+
5
+ 1. Import **`@sybilion/uilib/mini-app-global.css`** (slim tokens + font imports; ships in this package).
6
+ 2. Wrap the React root:
7
+
8
+ ```tsx
9
+ import { MiniAppRoot } from '@sybilion/uilib';
10
+ import '@sybilion/uilib/mini-app-global.css';
11
+
12
+ createRoot(document.getElementById('root')!).render(
13
+ <MiniAppRoot appId="my-mini-app">
14
+ <App />
15
+ </MiniAppRoot>,
16
+ );
17
+ ```
18
+
19
+ 3. Optional: **`useMiniAppShellTheme()`** for **`{ theme: { mode, isDarkMode } }`** under the provider (object leaves room for more shell fields later).
20
+
21
+ **Bridge:** `MiniAppRoot` handles `THEME_SYNC`, sends `READY` on mount/load, and checks `event.source === window.parent` plus `document.referrer` origin when present. If the referrer is missing (strict `Referrer-Policy`), `READY` may use `targetOrigin` `*` — document that for your host.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -29,9 +29,12 @@
29
29
  "import": "./src/index.ts",
30
30
  "default": "./src/index.ts"
31
31
  },
32
- "./src/*": "./src/*"
32
+ "./src/*": "./src/*",
33
+ "./mini-app-global.css": "./assets/mini-app-global.css"
33
34
  },
34
35
  "files": [
36
+ "assets",
37
+ "docs",
35
38
  "src",
36
39
  "dist",
37
40
  "package.json"
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './mini-app';
1
2
  export * from './contexts/chat-context';
2
3
  export * from './types/chat-api.types';
3
4
  export * from './components/ui/AnalysesSelector';
@@ -0,0 +1,115 @@
1
+ import type { ReactNode } from 'react';
2
+ import React, {
3
+ createContext,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from 'react';
11
+
12
+ import {
13
+ type ThemeSyncPayload,
14
+ applyThemeToDocument,
15
+ buildReadyMessage,
16
+ parseThemeSyncMessage,
17
+ resolveParentOriginFromReferrer,
18
+ } from './miniAppProtocol';
19
+
20
+ const defaultTheme: ThemeSyncPayload = {
21
+ mode: 'light',
22
+ isDarkMode: false,
23
+ };
24
+
25
+ export type MiniAppShellContextValue = {
26
+ theme: ThemeSyncPayload;
27
+ };
28
+
29
+ const MiniAppShellContext = createContext<MiniAppShellContextValue | null>(
30
+ null,
31
+ );
32
+
33
+ export function useMiniAppShellTheme(): MiniAppShellContextValue {
34
+ const v = useContext(MiniAppShellContext);
35
+ if (!v) {
36
+ throw new Error('useMiniAppShellTheme must be used within MiniAppRoot');
37
+ }
38
+ return v;
39
+ }
40
+
41
+ /**
42
+ * Accept only messages that appear to come from the real embedding parent:
43
+ * - `source` must be `window.parent` (same as shell’s iframe target).
44
+ * - When `document.referrer` is present, `origin` must match it (Sybilion page that loaded this iframe).
45
+ * If referrer was stripped (Referrer-Policy), we only rely on `source` matching `parent`.
46
+ */
47
+ function isTrustedParentMessage(event: MessageEvent): boolean {
48
+ // Ignore messages from other windows (e.g. popups, other iframes).
49
+ if (event.source !== window.parent) return false;
50
+ const fromReferrer = resolveParentOriginFromReferrer();
51
+ // Tighten to embedder origin when the browser exposes it.
52
+ if (fromReferrer && event.origin !== fromReferrer) return false;
53
+ return true;
54
+ }
55
+
56
+ export type MiniAppRootProps = {
57
+ children: ReactNode;
58
+ /** Included in READY payload when set. */
59
+ appId?: string;
60
+ onThemeChange?: (theme: ThemeSyncPayload) => void;
61
+ };
62
+
63
+ export function MiniAppRoot({
64
+ children,
65
+ appId,
66
+ onThemeChange,
67
+ }: MiniAppRootProps): React.ReactElement {
68
+ const [theme, setTheme] = useState<ThemeSyncPayload>(defaultTheme);
69
+ const onThemeChangeRef = useRef(onThemeChange);
70
+ onThemeChangeRef.current = onThemeChange;
71
+
72
+ const sendReady = useCallback(() => {
73
+ if (!window.parent || window.parent === window) return;
74
+ const payload = appId ? { appId } : {};
75
+ const msg = buildReadyMessage(payload);
76
+ const target = resolveParentOriginFromReferrer();
77
+
78
+ if (target) {
79
+ window.parent.postMessage(msg, target);
80
+ } else {
81
+ window.parent.postMessage(msg, '*');
82
+ }
83
+ }, [appId]);
84
+
85
+ useEffect(() => {
86
+ applyThemeToDocument(theme.mode);
87
+ }, [theme.mode]);
88
+
89
+ useEffect(() => {
90
+ const onMessage = (event: MessageEvent) => {
91
+ if (!isTrustedParentMessage(event)) return;
92
+ const parsed = parseThemeSyncMessage(event.data);
93
+ if (!parsed) return;
94
+ setTheme(parsed);
95
+ onThemeChangeRef.current?.(parsed);
96
+ };
97
+ window.addEventListener('message', onMessage);
98
+ return () => window.removeEventListener('message', onMessage);
99
+ }, []);
100
+
101
+ useEffect(() => {
102
+ sendReady();
103
+ if (document.readyState === 'complete') return;
104
+ window.addEventListener('load', sendReady);
105
+ return () => window.removeEventListener('load', sendReady);
106
+ }, [sendReady]);
107
+
108
+ const ctx = useMemo((): MiniAppShellContextValue => ({ theme }), [theme]);
109
+
110
+ return (
111
+ <MiniAppShellContext.Provider value={ctx}>
112
+ {children}
113
+ </MiniAppShellContext.Provider>
114
+ );
115
+ }
@@ -0,0 +1,21 @@
1
+ export {
2
+ applyThemeToDocument,
3
+ buildReadyMessage,
4
+ MINIAPP_CHANNEL,
5
+ MINIAPP_VERSION,
6
+ parseThemeSyncMessage,
7
+ resolveParentOriginFromReferrer,
8
+ } from './miniAppProtocol';
9
+ export type {
10
+ MiniAppMessageReady,
11
+ MiniAppMessageThemeSync,
12
+ ThemeSyncPayload,
13
+ } from './miniAppProtocol';
14
+ export {
15
+ MiniAppRoot,
16
+ useMiniAppShellTheme,
17
+ } from './MiniAppRoot';
18
+ export type {
19
+ MiniAppRootProps,
20
+ MiniAppShellContextValue,
21
+ } from './MiniAppRoot';
@@ -0,0 +1,79 @@
1
+ /**
2
+ * postMessage protocol for Sybilion workspace mini-apps (iframe child).
3
+ * Keep in sync with sybilion-client `src/workspace/miniAppBridge.ts` (channel, version, payloads).
4
+ */
5
+
6
+ export const MINIAPP_CHANNEL = 'sybilion.miniapp' as const;
7
+ export const MINIAPP_VERSION = 1 as const;
8
+
9
+ export type ThemeSyncPayload = {
10
+ mode: 'light' | 'dark';
11
+ isDarkMode: boolean;
12
+ };
13
+
14
+ export type MiniAppMessageReady = {
15
+ channel: typeof MINIAPP_CHANNEL;
16
+ version: typeof MINIAPP_VERSION;
17
+ type: 'READY';
18
+ payload: Record<string, unknown> | { appId?: string };
19
+ };
20
+
21
+ export type MiniAppMessageThemeSync = {
22
+ channel: typeof MINIAPP_CHANNEL;
23
+ version: typeof MINIAPP_VERSION;
24
+ type: 'THEME_SYNC';
25
+ payload: ThemeSyncPayload;
26
+ };
27
+
28
+ function isRecord(v: unknown): v is Record<string, unknown> {
29
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
30
+ }
31
+
32
+ /** Parse parent → child THEME_SYNC (shell uses `buildThemeSyncMessage`). */
33
+ export function parseThemeSyncMessage(data: unknown): ThemeSyncPayload | null {
34
+ if (!isRecord(data)) return null;
35
+ if (data.channel !== MINIAPP_CHANNEL) return null;
36
+ if (data.version !== MINIAPP_VERSION) return null;
37
+ if (data.type !== 'THEME_SYNC') return null;
38
+
39
+ const payload = data.payload;
40
+ if (!isRecord(payload)) return null;
41
+
42
+ const mode = payload.mode === 'dark' ? 'dark' : 'light';
43
+ const isDarkMode =
44
+ typeof payload.isDarkMode === 'boolean'
45
+ ? payload.isDarkMode
46
+ : mode === 'dark';
47
+
48
+ return { mode, isDarkMode };
49
+ }
50
+
51
+ /** Child → parent READY (optional `appId` for telemetry). */
52
+ export function buildReadyMessage(
53
+ payload: Record<string, unknown> = {},
54
+ ): MiniAppMessageReady {
55
+ return {
56
+ channel: MINIAPP_CHANNEL,
57
+ version: MINIAPP_VERSION,
58
+ type: 'READY',
59
+ payload,
60
+ };
61
+ }
62
+
63
+ export function resolveParentOriginFromReferrer(): string | null {
64
+ try {
65
+ if (typeof document !== 'undefined' && document.referrer) {
66
+ return new URL(document.referrer).origin;
67
+ }
68
+ } catch {
69
+ /* invalid referrer */
70
+ }
71
+ return null;
72
+ }
73
+
74
+ export function applyThemeToDocument(mode: 'light' | 'dark'): void {
75
+ if (typeof document === 'undefined') return;
76
+ const root = document.documentElement;
77
+ root.classList.remove('light', 'dark');
78
+ root.classList.add(mode);
79
+ }