diff --git a/.github/workflows/companion-build.yml b/.github/workflows/companion-build.yml
index be48f2d6..e731c6cc 100644
--- a/.github/workflows/companion-build.yml
+++ b/.github/workflows/companion-build.yml
@@ -22,7 +22,28 @@ jobs:
target: aarch64-apple-darwin
runs-on: ${{ matrix.platform }}
steps:
+ # pnpm-workspace.yaml globs '../packages/@nself/*', i.e. the
+ # nself-org/packages repo as a SIBLING directory. desktop/package.json
+ # depends on five of them as workspace:*, and tauri.conf.json's
+ # beforeBuildCommand runs `pnpm build` before cargo builds the shell.
+ # nclaw goes in a subdir so the sibling lands beside it, matching the
+ # layout the workspace file expects (same pattern as desktop-e2e.yml).
- uses: actions/checkout@v7
+ with:
+ path: nclaw
+ - uses: actions/checkout@v7
+ with:
+ repository: nself-org/packages
+ path: packages
+
+ - uses: actions/setup-node@v7
+ with: { node-version: '20' }
+ - uses: pnpm/action-setup@v6
+ with: { version: '9' }
+
+ - name: Install frontend dependencies
+ working-directory: nclaw
+ run: pnpm install --frozen-lockfile
- uses: dtolnay/rust-toolchain@stable
@@ -40,7 +61,7 @@ jobs:
patchelf
- name: Build Tauri app
- working-directory: desktop/src-tauri
+ working-directory: nclaw/desktop/src-tauri
run: cargo tauri build
- name: Upload Linux artifacts
@@ -49,8 +70,8 @@ jobs:
with:
name: companion-${{ matrix.target }}
path: |
- desktop/src-tauri/target/release/bundle/appimage/*.AppImage
- desktop/src-tauri/target/release/bundle/deb/*.deb
+ nclaw/desktop/src-tauri/target/release/bundle/appimage/*.AppImage
+ nclaw/desktop/src-tauri/target/release/bundle/deb/*.deb
- name: Upload Windows artifacts
if: matrix.platform == 'windows-latest'
@@ -58,8 +79,8 @@ jobs:
with:
name: companion-${{ matrix.target }}
path: |
- desktop/src-tauri/target/release/bundle/msi/*.msi
- desktop/src-tauri/target/release/bundle/nsis/*.exe
+ nclaw/desktop/src-tauri/target/release/bundle/msi/*.msi
+ nclaw/desktop/src-tauri/target/release/bundle/nsis/*.exe
- name: Upload macOS artifacts
if: matrix.platform == 'macos-latest'
@@ -67,4 +88,4 @@ jobs:
with:
name: companion-${{ matrix.target }}
path: |
- desktop/src-tauri/target/release/bundle/dmg/*.dmg
+ nclaw/desktop/src-tauri/target/release/bundle/dmg/*.dmg
diff --git a/desktop/package.json b/desktop/package.json
index 7bcced7f..190dabea 100644
--- a/desktop/package.json
+++ b/desktop/package.json
@@ -19,6 +19,7 @@
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.0.0",
"@ai-sdk/react": "^3.0.0",
+ "@fontsource/inter": "^5.0.0",
"@formatjs/intl-localematcher": "^0.5.0",
"@sentry/react": "^8.0.0",
"@nself/auth-core": "workspace:*",
@@ -49,6 +50,7 @@
"devDependencies": {
"@formatjs/cli": "^6.0.0",
"@playwright/test": "^1.48",
+ "@tailwindcss/postcss": "^4.0.0",
"@tauri-apps/cli": "^2.3.0",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.0",
@@ -57,8 +59,11 @@
"@types/react-syntax-highlighter": "^15.0.0",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.2.6",
+ "autoprefixer": "^10.4.0",
"jsdom": "^24.0.0",
"playwright": "^1.48",
+ "postcss": "^8.4.0",
+ "tailwindcss": "^4.0.0",
"typescript": "^5.4.5",
"vite": "^6.4.2",
"vitest": "^3.2.6"
diff --git a/desktop/postcss.config.js b/desktop/postcss.config.js
index 2b75bd8a..d9234117 100644
--- a/desktop/postcss.config.js
+++ b/desktop/postcss.config.js
@@ -1,6 +1,6 @@
export default {
plugins: {
- tailwindcss: {},
+ '@tailwindcss/postcss': {},
autoprefixer: {}
}
}
diff --git a/desktop/src/App.tsx b/desktop/src/App.tsx
index 1809482f..37ce3d26 100644
--- a/desktop/src/App.tsx
+++ b/desktop/src/App.tsx
@@ -2,7 +2,8 @@
* Purpose: ɳClaw desktop root component. Wraps the app with shared providers and
* renders the main shell. Auth state drives the top-level view.
* Inputs: Auth context from NselfAuthProvider (via useAuth hook).
- * Outputs: Renders i18n-wrapped shell. Unauthenticated state shows a sign-in prompt.
+ * Outputs: Renders i18n-wrapped shell. Authenticated state renders the chat UI;
+ * unauthenticated state shows a sign-in prompt.
* Constraints:
* - NselfI18nProvider wraps all children so useNselfTranslation() works everywhere.
* - Do NOT lift graphql queries here — let page components own their queries.
@@ -15,6 +16,7 @@ import * as Sentry from '@sentry/react';
import { NselfI18nProvider, isRTL, useNselfTranslation, useTranslation } from '@nself/i18n';
import { useAuth } from '@nself/auth-core';
import { initObservability } from '@nself/observability';
+import { ChatContainer } from './components/chat/ChatContainer';
// Initialize Sentry error reporting (runs at module load, before first render)
// Vite exposes build-time env via import.meta.env, not the Node-only `process`
@@ -59,6 +61,28 @@ function Shell(): React.ReactElement {
const { t } = useNselfTranslation();
useDocumentDir();
+ // Authenticated is the only state with content below the title bar — render
+ // it full-height so ChatContainer's own flex layout can fill the window.
+ if (status === 'authenticated') {
+ return (
+
+
+ {t('desktop.nclaw.title')}
+
+
+
+
+
+ );
+ }
+
return (
{
- const body = await req.json() as { messages: ChatMessage[] };
+export async function chatTransport(
+ input: RequestInfo | URL,
+ init?: RequestInit,
+): Promise {
+ const bodyText = input instanceof Request ? await input.text() : ((init?.body as string) ?? '{}');
+ const body = JSON.parse(bodyText) as { messages: ChatMessage[] };
const reply = await invoke('stream_chat', { messages: body.messages });
const encoder = new TextEncoder();
+ const messageId = crypto.randomUUID();
+ const chunk = (part: Record) =>
+ encoder.encode(`data: ${JSON.stringify(part)}\n\n`);
+
const stream = new ReadableStream({
start(controller) {
- controller.enqueue(
- encoder.encode(`data: ${JSON.stringify({ content: reply })}\n\n`)
- );
+ controller.enqueue(chunk({ type: 'text-start', id: messageId }));
+ controller.enqueue(chunk({ type: 'text-delta', id: messageId, delta: reply }));
+ controller.enqueue(chunk({ type: 'text-end', id: messageId }));
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
controller.close();
},
});
return new Response(stream, {
- headers: { 'Content-Type': 'text/event-stream' },
+ headers: {
+ 'Content-Type': 'text/event-stream',
+ 'x-vercel-ai-ui-message-stream': 'v1',
+ },
});
}
diff --git a/desktop/src/main.tsx b/desktop/src/main.tsx
index 0ae67e8e..483db16a 100644
--- a/desktop/src/main.tsx
+++ b/desktop/src/main.tsx
@@ -4,6 +4,12 @@
* Inputs: VITE_NSELF_SENTRY_DSN, VITE_APP_ENV, VITE_APP_VERSION from build env.
* Outputs: React app mounted on #root, Sentry + OTel registered, i18next initialised.
* Constraints:
+ * - styles/tokens.css and styles/globals.css (which pulls in Tailwind via
+ * @import 'tailwindcss') MUST be imported here — Vite only emits CSS that's reachable
+ * from the module graph. Without this import every Tailwind utility class
+ * in the tree is inert (elements keep className but get no rules), which
+ * silently collapses flex layouts (e.g. ChatList's scroll container) to
+ * zero height.
* - initObservability() MUST be called before ReactDOM.createRoot() so Sentry catches
* any errors thrown during React hydration.
* - initializeI18next() runs synchronously at module level — must complete before render.
@@ -24,6 +30,8 @@ import { Provider as UrqlProvider } from 'urql';
import App from './App';
import { authStrategy } from './lib/auth';
import { graphqlClient } from './lib/graphql';
+import './styles/tokens.css';
+import './styles/globals.css';
// ─── i18n init (module level — before first render) ──────────────────────────
initializeI18next();
diff --git a/desktop/src/styles/globals.css b/desktop/src/styles/globals.css
index 080f01d2..6e5e66fd 100644
--- a/desktop/src/styles/globals.css
+++ b/desktop/src/styles/globals.css
@@ -1,8 +1,10 @@
@import '@fontsource/inter';
+@import 'tailwindcss';
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+/* Tailwind v4 is CSS-first, but tailwind.config.js still carries this app's
+ custom colors (bg-surface, accent) and darkMode setting — @config keeps
+ that file authoritative instead of duplicating theme values here. */
+@config '../../tailwind.config.js';
body {
@apply bg-surface text-slate-100 font-sans antialiased;
diff --git a/desktop/tests/e2e/chat.spec.ts b/desktop/tests/e2e/chat.spec.ts
index 50b94b21..a5499b3f 100644
--- a/desktop/tests/e2e/chat.spec.ts
+++ b/desktop/tests/e2e/chat.spec.ts
@@ -1,15 +1,46 @@
import { test, expect } from '@playwright/test';
+import { mockAuthenticatedSession } from './fixtures/auth';
+
+// Both tests require an authenticated session: App.tsx only mounts
+// ChatContainer (the composer + message list) once useAuth() resolves to
+// 'authenticated' (see desktop/src/App.tsx Shell()). A fresh, unauthenticated
+// page has no chat UI at all, so every test here seeds a session first.
test('user can send a message and see a stub reply', async ({ page }) => {
+ await mockAuthenticatedSession(page);
await page.goto('/');
- const input = page.getByPlaceholder(/Type a message/i);
+
+ // Real placeholder string is @nself/i18n's desktop.nclaw.messagePlaceholder
+ // ("Message ɳClaw…"), wired in InputArea.tsx via useNselfTranslation().
+ const input = page.getByPlaceholder(/Message ɳClaw/i);
await input.waitFor({ timeout: 5000 });
await input.fill('Hello');
await page.keyboard.press('Enter');
+
+ // stream_chat's real Rust command still returns NotImplemented pending
+ // S15.T17 (desktop/src-tauri/src/commands/chat.rs); the mocked Tauri
+ // bridge in fixtures/auth.ts stands in for it with the same placeholder
+ // text so this test exercises the real send -> render pipeline (useChat,
+ // ChatContainer, ChatList, MessageBubble) without depending on a native
+ // Tauri host, which Playwright cannot drive.
await expect(page.locator('text=(stub response)')).toBeVisible({ timeout: 10000 });
});
test('markdown renders in messages', async ({ page }) => {
+ await mockAuthenticatedSession(page);
await page.goto('/');
- await expect(page.locator('article, [role="article"], .markdown, .prose').first()).toBeAttached({ timeout: 10000 });
+
+ const input = page.getByPlaceholder(/Message ɳClaw/i);
+ await input.waitFor({ timeout: 5000 });
+ await input.fill('**bold text**');
+ await page.keyboard.press('Enter');
+
+ // MessageBubble renders message content through react-markdown
+ // (remark-gfm), which turns **bold text** into a real element.
+ // This fails again if MessageBubble stops rendering markdown (e.g. reverts
+ // to a plain text node), unlike a generic container-class selector that
+ // could match unrelated markup.
+ await expect(page.locator('strong', { hasText: 'bold text' })).toBeAttached({
+ timeout: 10000,
+ });
});
diff --git a/desktop/tests/e2e/fixtures/auth.ts b/desktop/tests/e2e/fixtures/auth.ts
new file mode 100644
index 00000000..e252b6ce
--- /dev/null
+++ b/desktop/tests/e2e/fixtures/auth.ts
@@ -0,0 +1,97 @@
+/**
+ * Purpose: Seed an authenticated session for nclaw desktop E2E tests.
+ * Inputs: Playwright Page.
+ * Outputs: Injects a TokenPair into localStorage (the same keys
+ * NativeAuthStrategy/SecureStoreInterface read from — see
+ * desktop/src/lib/auth.ts and @nself/auth-core/src/native.helpers.ts
+ * SECURE_STORE_KEYS) before the page's own scripts run, and stubs
+ * window.__TAURI_INTERNALS__ so invoke() resolves without a real
+ * Tauri binary (Playwright drives a plain Chromium window, which
+ * has no Tauri IPC bridge at all).
+ * Constraints:
+ * - Must run via page.addInitScript, before React mounts and before
+ * NativeAuthStrategy.init() reads SecureStore, or the app renders
+ * 'loading' -> 'unauthenticated' and the chat UI never appears.
+ * - expiresAt is set far in the future so the proactive refresh loop
+ * (DEFAULT_REFRESH_BUFFER_MS before expiry) never fires mid-test and
+ * tries to hit a real auth server that doesn't exist in CI.
+ * - The JWT signature is not verified client-side (decodeUserFromJwt only
+ * reads the payload), so any syntactically valid three-part token works.
+ */
+
+import type { Page } from '@playwright/test';
+
+// Mirrors @nself/auth-core SECURE_STORE_KEYS (native.helpers.ts) — desktop's
+// localSecureStore (desktop/src/lib/auth.ts) is a thin localStorage wrapper
+// around the same keys.
+const SECURE_STORE_KEYS = {
+ ACCESS_TOKEN: '@nself/auth-core/accessToken',
+ REFRESH_TOKEN: '@nself/auth-core/refreshToken',
+ EXPIRES_AT: '@nself/auth-core/expiresAt',
+} as const;
+
+/**
+ * Seed a valid, far-from-expiry TokenPair and stub the Tauri IPC bridge so
+ * the app boots into the 'authenticated' state and can call stream_chat
+ * without a native Tauri host. Call before page.goto('/').
+ */
+export async function mockAuthenticatedSession(page: Page): Promise {
+ await page.addInitScript(
+ ({ accessKey, refreshKey, expiresKey }) => {
+ function base64url(obj: Record): string {
+ return btoa(JSON.stringify(obj))
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ .replace(/=+$/, '');
+ }
+
+ const header = base64url({ alg: 'HS256', typ: 'JWT' });
+ const payload = base64url({
+ sub: 'e2e-test-user-id',
+ email: 'e2e@example.test',
+ display_name: 'E2E Test User',
+ 'https://hasura.io/jwt/claims': {
+ 'x-hasura-allowed-roles': ['user'],
+ 'x-hasura-default-role': 'user',
+ },
+ });
+ const fakeJwt = `${header}.${payload}.e2e-test-signature`;
+
+ window.localStorage.setItem(accessKey, fakeJwt);
+ window.localStorage.setItem(refreshKey, 'e2e-test-refresh-token');
+ // 24h out — well past DEFAULT_REFRESH_BUFFER_MS (60s), so the refresh
+ // loop schedules for tomorrow and never fires during the test run.
+ window.localStorage.setItem(expiresKey, String(Date.now() + 24 * 60 * 60 * 1000));
+
+ // Stub the Tauri IPC bridge. Real command implementations are Rust
+ // (desktop/src-tauri/src/commands/*.rs); Playwright drives a plain
+ // browser window with no native host, so invoke() would otherwise
+ // throw "window.__TAURI_INTERNALS__ is undefined" before React can
+ // even render the composer.
+ const w = window as unknown as Record;
+ w.__TAURI_INTERNALS__ = {
+ transformCallback: (callback: (value: unknown) => void): number => {
+ const id = Math.floor(Math.random() * 1e9);
+ (w as Record)[`_tauriCb_${id}`] = callback;
+ return id;
+ },
+ invoke: async (cmd: string): Promise => {
+ if (cmd === 'stream_chat') {
+ // stream_chat's real Rust implementation is a stub returning
+ // NotImplemented (see desktop/src-tauri/src/commands/chat.rs,
+ // awaiting S15.T17). Mirror that shape here rather than a
+ // hand-picked success value, so this fixture does not claim
+ // more of the backend works than actually does.
+ return '(stub response)';
+ }
+ return null;
+ },
+ };
+ },
+ {
+ accessKey: SECURE_STORE_KEYS.ACCESS_TOKEN,
+ refreshKey: SECURE_STORE_KEYS.REFRESH_TOKEN,
+ expiresKey: SECURE_STORE_KEYS.EXPIRES_AT,
+ },
+ );
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9e8561e9..cd42604c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -499,6 +499,9 @@ importers:
'@dnd-kit/utilities':
specifier: ^3.0.0
version: 3.2.2(react@19.2.7)
+ '@fontsource/inter':
+ specifier: ^5.0.0
+ version: 5.3.0
'@formatjs/intl-localematcher':
specifier: ^0.5.0
version: 0.5.10
@@ -584,6 +587,9 @@ importers:
'@playwright/test':
specifier: ^1.48
version: 1.61.0
+ '@tailwindcss/postcss':
+ specifier: ^4.0.0
+ version: 4.3.1
'@tauri-apps/cli':
specifier: ^2.3.0
version: 2.11.2
@@ -608,12 +614,21 @@ importers:
'@vitest/coverage-v8':
specifier: ^3.2.6
version: 3.2.6(vitest@3.2.6)
+ autoprefixer:
+ specifier: ^10.4.0
+ version: 10.5.4(postcss@8.5.26)
jsdom:
specifier: ^24.0.0
version: 24.1.3
playwright:
specifier: ^1.48
version: 1.61.0
+ postcss:
+ specifier: '>=8.5.18'
+ version: 8.5.26
+ tailwindcss:
+ specifier: ^4.0.0
+ version: 4.3.1
typescript:
specifier: ^5.4.5
version: 5.8.3
@@ -1825,6 +1840,9 @@ packages:
'@fastify/busboy@3.2.0':
resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==}
+ '@fontsource/inter@5.3.0':
+ resolution: {integrity: sha512-RofMylZmjlJEfELXeNHFWBRcSs75rGU/6bV2S2jfnvv/3rPXPGe0LgUJTklcHZ9lM4OZmAVFhcJPnACfb91A3g==}
+
'@formatjs/cli-native-darwin-arm64@1.1.5':
resolution: {integrity: sha512-zGt7OenQakyh8a+eAgCx/zUInbQXYT0UNzeOaYlnaKmU9yr78wsVAGGhPc85gchnQ7gIXSWOKhwIzvfSMFGU7w==}
cpu: [arm64]
@@ -4579,6 +4597,13 @@ packages:
resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ autoprefixer@10.5.4:
+ resolution: {integrity: sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: '>=8.5.18'
+
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
@@ -4668,6 +4693,11 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ baseline-browser-mapping@2.11.19:
+ resolution: {integrity: sha512-Grytf1xOxOEMTGRwx6rLGKkTabd4vMg3VrKdj/7joCmV0qgh4QwMMO6xh34YEXQqirAuUdgQGa5orJQQ+69RBw==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
better-opn@3.0.2:
resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==}
engines: {node: '>=12.0.0'}
@@ -4716,6 +4746,11 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
+ browserslist@4.28.8:
+ resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
bser@2.1.1:
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
@@ -4782,6 +4817,9 @@ packages:
caniuse-lite@1.0.30001799:
resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==}
+ caniuse-lite@1.0.30001810:
+ resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==}
+
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@@ -5313,6 +5351,9 @@ packages:
electron-to-chromium@1.5.374:
resolution: {integrity: sha512-HCF5i7izveksHSGqa7mhDh6tr3Uz9Dar2RAjwuh69bw3QGPVObjQIgLwQWeO/Rxp9/r0KdboKy9RbpQDl97fjg==}
+ electron-to-chromium@1.5.415:
+ resolution: {integrity: sha512-958V+Kbhtgz+SxXeEVKBjrlKRBIDAYvUJfwhjxMZ5S6ut9jAl7l9ZKBkBrvjyjZE36PabLUo2L8kEeV5O4vgJg==}
+
emittery@0.13.1:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
engines: {node: '>=12'}
@@ -5837,6 +5878,9 @@ packages:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
+ fraction.js@5.3.4:
+ resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
+
framer-motion@11.18.2:
resolution: {integrity: sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==}
peerDependencies:
@@ -7522,6 +7566,10 @@ packages:
resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==}
engines: {node: '>=18'}
+ node-releases@2.0.54:
+ resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==}
+ engines: {node: '>=18'}
+
normalize-path@2.1.1:
resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==}
engines: {node: '>=0.10.0'}
@@ -9093,6 +9141,12 @@ packages:
peerDependencies:
browserslist: '>= 4.21.0'
+ update-browserslist-db@1.3.1:
+ resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -10862,6 +10916,8 @@ snapshots:
'@fastify/busboy@3.2.0': {}
+ '@fontsource/inter@5.3.0': {}
+
'@formatjs/cli-native-darwin-arm64@1.1.5':
optional: true
@@ -13967,6 +14023,15 @@ snapshots:
auto-bind@5.0.1: {}
+ autoprefixer@10.5.4(postcss@8.5.26):
+ dependencies:
+ browserslist: 4.28.8
+ caniuse-lite: 1.0.30001810
+ fraction.js: 5.3.4
+ picocolors: 1.1.1
+ postcss: 8.5.26
+ postcss-value-parser: 4.2.0
+
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.1.0
@@ -14107,6 +14172,8 @@ snapshots:
baseline-browser-mapping@2.10.37: {}
+ baseline-browser-mapping@2.11.19: {}
+
better-opn@3.0.2:
dependencies:
open: 8.4.2
@@ -14158,6 +14225,14 @@ snapshots:
node-releases: 2.0.47
update-browserslist-db: 1.2.3(browserslist@4.28.2)
+ browserslist@4.28.8:
+ dependencies:
+ baseline-browser-mapping: 2.11.19
+ caniuse-lite: 1.0.30001810
+ electron-to-chromium: 1.5.415
+ node-releases: 2.0.54
+ update-browserslist-db: 1.3.1(browserslist@4.28.8)
+
bser@2.1.1:
dependencies:
node-int64: 0.4.0
@@ -14215,6 +14290,8 @@ snapshots:
caniuse-lite@1.0.30001799: {}
+ caniuse-lite@1.0.30001810: {}
+
ccount@2.0.1: {}
chai@5.3.3:
@@ -14723,6 +14800,8 @@ snapshots:
electron-to-chromium@1.5.374: {}
+ electron-to-chromium@1.5.415: {}
+
emittery@0.13.1: {}
emoji-regex@10.6.0: {}
@@ -15434,6 +15513,8 @@ snapshots:
dependencies:
fetch-blob: 3.2.0
+ fraction.js@5.3.4: {}
+
framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
motion-dom: 11.18.1
@@ -17937,6 +18018,8 @@ snapshots:
node-releases@2.0.47: {}
+ node-releases@2.0.54: {}
+
normalize-path@2.1.1:
dependencies:
remove-trailing-separator: 1.1.0
@@ -19705,6 +19788,12 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
+ update-browserslist-db@1.3.1(browserslist@4.28.8):
+ dependencies:
+ browserslist: 4.28.8
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1