feat: update tg integration
All checks were successful
Deploy to VPS (dist) / deploy (push) Successful in 1m35s

This commit is contained in:
Hewston Fox
2026-03-18 01:24:50 +02:00
parent 9f0ff8c4e5
commit 16b85048f9
5 changed files with 251 additions and 119 deletions

86
CLAUDE.local.md Normal file
View File

@@ -0,0 +1,86 @@
# Orchestrator Mode
For tasks that require code changes, act as an orchestrator. Break down requests, delegate to agents, coordinate results.
## Agents
- `researcher` (Opus) - investigation, architecture analysis, web lookups
- `developer` (Sonnet) - implementation, bug fixes, tests
- `reviewer` (Sonnet) - code review after implementation
## Workflow
1. Clarify which project(s) are affected and the expected behavior if not obvious from the request
2. Dispatch `researcher` when the task touches unfamiliar code or multiple systems interact
3. Dispatch `developer` with research findings and the target project name(s)
4. Dispatch `reviewer` after implementation
5. Summarize results to the user
Skip the researcher for tasks confined to a single file or component with obvious patterns.
## Clarification Loops
Agents cannot spawn other agents. When an agent reports questions or blockers during implementation:
1. Save the agent's `agentId` from its return value
2. Dispatch `researcher` with the open question
3. **Resume** the blocked agent using `resume: <agentId>` with the researcher's findings — this continues the agent with its full prior context preserved
## After Review
When the reviewer requests changes:
1. **Resume the developer** with the reviewer's blocking issues and ask it to summarize only what's relevant to those issues — changed files, design decisions, and approaches that were tried but didn't work
2. **Dispatch a fresh developer** with: the summary + the reviewer's issues list. The clean context lets it focus on fixes without carrying the full implementation history
When the reviewer approves — done, no further action needed.
## Rules
- Never implement code yourself for non-trivial tasks.
- Always dispatch `reviewer` after implementation.
- Always specify the target project(s) when dispatching any agent.
- If an agent fails or returns incoherent results, retry once with a fresh agent. If it fails again, report to the user.
- Keep the user informed at each stage.
## Project Context
**honey-fe** — Telegram games project frontend (single SPA, no sub-projects).
### Tech Stack
- React 19 + TypeScript 5.9 (strict mode, `erasableSyntaxOnly`)
- Vite 7 + SWC (via `@vitejs/plugin-react-swc`)
- TanStack Router (file-based, auto code-splitting) + React Query
- Tailwind CSS v4 (via `@tailwindcss/vite` plugin)
- i18next + react-i18next (HTTP backend, locales in `public/locales/`, EN + RU)
- arktype for runtime validation
- motion (Framer Motion) for animations
- pnpm package manager
### Path Aliases
- `@/*``src/*`
- `@components/*``src/components/*`
### Directory Layout
```
src/
components/ — shared UI (atoms/, form/, icons/, modal/, modals/, surface/)
routes/ — TanStack file-based routes (game, apiary, cashdesk, earnings, roulette, shop, tasks)
styles/ — global CSS + fonts
i18next.ts — i18n runtime setup
main.tsx — app entry point
public/
locales/ — translation JSON files (en.json, ru.json)
fonts/
```
### Key Commands
- `pnpm dev` — start dev server
- `pnpm build` — typecheck + production build
- `pnpm build:staging` — staging build
- `pnpm lint` / `pnpm lint:fix` — oxlint
- `pnpm fmt` / `pnpm fmt:check` — oxfmt

View File

@@ -21,7 +21,7 @@
"@tanstack/react-query-devtools": "^5.91.3",
"@tanstack/react-router": "^1.166.3",
"@tanstack/react-router-devtools": "^1.166.3",
"@telegram-apps/sdk-react": "^3.3.9",
"@tma.js/sdk-react": "^3.0.16",
"arktype": "^2.2.0",
"axios": "^1.13.6",
"clsx": "^2.1.1",

140
pnpm-lock.yaml generated
View File

@@ -29,9 +29,9 @@ importers:
'@tanstack/react-router-devtools':
specifier: ^1.166.3
version: 1.166.3(@tanstack/react-router@1.166.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.166.2)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@telegram-apps/sdk-react':
specifier: ^3.3.9
version: 3.3.9(@types/react@19.2.14)(react@19.2.4)(typescript@5.9.3)
'@tma.js/sdk-react':
specifier: ^3.0.16
version: 3.0.16(@types/react@19.2.14)(react@19.2.4)(typescript@5.9.3)
arktype:
specifier: ^2.2.0
version: 2.2.0
@@ -1192,15 +1192,11 @@ packages:
resolution: {integrity: sha512-42WoRePf8v690qG8yGRe/YOh+oHni9vUaUUfoqlS91U2scd3a5rkLtVsc6b7z60w3RogH0I00vdrC5AaeiZ18w==}
engines: {node: '>=20.19'}
'@telegram-apps/bridge@2.11.0':
resolution: {integrity: sha512-kBZjWRRp/lxKeQ8r8cDWUY9EjxUtyeh/9xTQjsjuGRsCR5XTO1cyVbvcvqzHn53csGx3aBs+fOR3Pk3b6w2JHg==}
deprecated: This package is not supported anymore. Use @tma.js/bridge instead
'@tma.js/bridge@2.2.3':
resolution: {integrity: sha512-R+FQTxaFbQVBgtegfCvOU4SH1TcXhgGvFotNzrtsaRmiuPlvsMNSbtslfsysg0Yv3d6svAPAWJ1PJjimAopfag==}
'@telegram-apps/navigation@1.0.14':
resolution: {integrity: sha512-bqNgF/J8Po7ZtsELm3E1a6aPr7awwxO3sIqD8J6u12urOlGoW5+1KxKKbkPa58mgXuQdsltd8apI+OVy0IYiUA==}
'@telegram-apps/sdk-react@3.3.9':
resolution: {integrity: sha512-85jF1ICT8sYCNzXu19SqJrfrS8XslsvV14KUcToiyL7H5ZMXHt0JQKM+QJgULjnLjEswweQ4/7Bd7mNULf/BIg==}
'@tma.js/sdk-react@3.0.16':
resolution: {integrity: sha512-/DhMV6jwLh2Yja/gGrZu4CTWt0enC1tbG6Bp0lfUUHK0x+9q90z+zCN0XuytNKk1gedZfmzpeTyR/4sroW2CLA==}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -1208,22 +1204,20 @@ packages:
'@types/react':
optional: true
'@telegram-apps/sdk@3.11.8':
resolution: {integrity: sha512-vlpkYzMJCV9Cadsn9Q+/hbHNR39j5o96N9z4CjZ6AFHkkxOeOqVRrq9zRBw0gmffROkF2bU0WQxhRj8KZ/bPhw==}
'@tma.js/sdk@3.1.7':
resolution: {integrity: sha512-yHYqr+Gwj2wptmkceYAZ+ZPOV1Tv3f/UpJTkpngUt7shZg2vJQnZINnHC93iqznLo/Vqr15TO8UGzOdBQ7rhNA==}
'@telegram-apps/signals@1.1.2':
resolution: {integrity: sha512-1P1kdCLX7MfETGPxH7f3UZKIsdE7Tz5S7QmN4Km1sbYQMakD5Bi1NecSMR7/wnHp50gWMI1JzENcMtCEmouhSg==}
'@tma.js/signals@1.0.1':
resolution: {integrity: sha512-i2HUuwGqL4BmM5KAklAUhMhlEgUOF+F4nMHHS/zxrmD0upHE/0CiXCEdQxVeeOGN6e2RrKlonA40sDtR6OUDCw==}
'@telegram-apps/toolkit@2.1.3':
resolution: {integrity: sha512-LPUBL7hxQTOr+Dowyk9a1O82nQS4ja4+OYiYWtvqq5nNUHC6Gbbus0zGWCbFcj9CLnIzaeb5HWOg5iSnhoRcyg==}
'@tma.js/toolkit@1.0.4':
resolution: {integrity: sha512-6CDlkgc+43WD2jy6jog3B50yt9t+/wRwrN25zOa4iSw4IzfqoFjaQcJzTupBYaRxz19C3Rrir7p0/qStS/Z8vw==}
'@telegram-apps/transformers@2.2.6':
resolution: {integrity: sha512-MMBRs3demeBT9Cd614KKZmak7eEyNdEbfu99a0SwEEJe2oIODjJLrUxrhUcAOc5wvTRfrKka27VXVgruauLhdg==}
deprecated: This package is not supported anymore. Use @tma.js/transfomers instead
'@tma.js/transformers@1.1.3':
resolution: {integrity: sha512-zRL+fdo/NBpGqCJfl8ItAvcJIFT5Zy6UOYJwQPmUV5/gTHgmSvmGlrP43PeVi9XZ7Ggf4xbO6LUa4ehxozhMTA==}
'@telegram-apps/types@2.0.3':
resolution: {integrity: sha512-pXh9BdnLZF3e2BGc4WL+RTRMAUpqKpaSP3Rs8rB4WyRwIoRSGWFKE4gtT/9m42LGixB8YVwdo/pJ+9XO765XEA==}
deprecated: This package is not supported anymore. Use @tma.js/types instead
'@tma.js/types@1.0.2':
resolution: {integrity: sha512-qs4mi+U1xZmMQBdMhWAo1X4YqUJ/ae0y28s+GNCpQq58bsJo0h8rvyVOB1RwPvXogIY9+yribbZe6z3AIJmsAQ==}
'@traversable/json@0.0.26':
resolution: {integrity: sha512-oXKX0eNxbbHGLjLV27nTuV0uyR6uSoWi0BF+FYJu4jXRcSsWqCHOqNjIb2x/0usKd70rnKLGyHxurlTBTpQVOw==}
@@ -1309,8 +1303,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
better-promises@0.4.1:
resolution: {integrity: sha512-cDW7eMvhvCoWzSih5o/OxsAgTUfP05yGMq77xNZUTmcZoNU9vEeFZJ+yJJi4lnocvxFrVFKsG0Yxt7ZnuYJEig==}
better-promises@1.0.0:
resolution: {integrity: sha512-gPgL2nRgeSbMIe3QpsYdKR3K0S9OZuphVuos60Eqsw8d/6GivOkyJ5D/zmnolJ6hzh7upnnflBUWUlQh4qpU2A==}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
@@ -1424,8 +1418,8 @@ packages:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
error-kid@0.0.7:
resolution: {integrity: sha512-8mFs7ZaDWlBFgjOy4lHLMCe2+KZfZXGx5GOgh2VQ/M1CCIvSWzbl2OMl+5fdZgrgoUfhTeF+NPhmqfEvUhN8yw==}
error-kid@1.0.2:
resolution: {integrity: sha512-Xvq0ZrY/azCbREWKt9E/3mXDF0MkuEVVvHnOKutUhtq2O8pyneEixQ4nSFkA19Xfn+/OVSg//iVG0dxIDj7DIA==}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
@@ -1486,6 +1480,9 @@ packages:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'}
fp-ts@2.16.11:
resolution: {integrity: sha512-LaI+KaX2NFkfn1ZGHoKCmcfv7yrZsC3b8NtWsTVQeHkq4F27vI5igUuO53sxqDEa2gNQMHFPmpojDw/1zmUK7w==}
framer-motion@12.35.1:
resolution: {integrity: sha512-rL8cLrjYZNShZqKV3U0Qj6Y5WDiZXYEM5giiTLfEqsIZxtspzMDCkKmrO5po76jWfvOg04+Vk+sfBvTD0iMmLw==}
peerDependencies:
@@ -2157,16 +2154,8 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
valibot@1.0.0:
resolution: {integrity: sha512-1Hc0ihzWxBar6NGeZv7fPLY0QuxFMyxwYR2sF1Blu7Wq7EnremwY2W02tit2ij2VJT8HcSkHAQqmFfl77f73Yw==}
peerDependencies:
typescript: '>=5'
peerDependenciesMeta:
typescript:
optional: true
valibot@1.0.0-beta.14:
resolution: {integrity: sha512-tLyV2rE5QL6U29MFy3xt4AqMrn+/HErcp2ZThASnQvPMwfSozjV1uBGKIGiegtZIGjinJqn0SlBdannf18wENA==}
valibot@1.3.0:
resolution: {integrity: sha512-SItIaOFnWYho/AcRU5gOtyfkTsuDTC3tRv+jy4/py8xERPnvHdM+ybD1iIqWTATVWG1nZetOfwZKq5upBjSqzw==}
peerDependencies:
typescript: '>=5'
peerDependenciesMeta:
@@ -3079,57 +3068,60 @@ snapshots:
'@tanstack/virtual-file-routes@1.161.4': {}
'@telegram-apps/bridge@2.11.0(typescript@5.9.3)':
'@tma.js/bridge@2.2.3(typescript@5.9.3)':
dependencies:
'@telegram-apps/signals': 1.1.2
'@telegram-apps/toolkit': 2.1.3
'@telegram-apps/transformers': 2.2.6(typescript@5.9.3)
'@telegram-apps/types': 2.0.3
better-promises: 0.4.1
error-kid: 0.0.7
'@tma.js/signals': 1.0.1
'@tma.js/toolkit': 1.0.4
'@tma.js/transformers': 1.1.3(typescript@5.9.3)
'@tma.js/types': 1.0.2
better-promises: 1.0.0
error-kid: 1.0.2
fp-ts: 2.16.11
mitt: 3.0.1
valibot: 1.0.0(typescript@5.9.3)
valibot: 1.3.0(typescript@5.9.3)
transitivePeerDependencies:
- typescript
'@telegram-apps/navigation@1.0.14': {}
'@telegram-apps/sdk-react@3.3.9(@types/react@19.2.14)(react@19.2.4)(typescript@5.9.3)':
'@tma.js/sdk-react@3.0.16(@types/react@19.2.14)(react@19.2.4)(typescript@5.9.3)':
dependencies:
'@telegram-apps/sdk': 3.11.8(typescript@5.9.3)
'@tma.js/sdk': 3.1.7(typescript@5.9.3)
react: 19.2.4
optionalDependencies:
'@types/react': 19.2.14
transitivePeerDependencies:
- typescript
'@telegram-apps/sdk@3.11.8(typescript@5.9.3)':
'@tma.js/sdk@3.1.7(typescript@5.9.3)':
dependencies:
'@telegram-apps/bridge': 2.11.0(typescript@5.9.3)
'@telegram-apps/navigation': 1.0.14
'@telegram-apps/signals': 1.1.2
'@telegram-apps/toolkit': 2.1.3
'@telegram-apps/transformers': 2.2.6(typescript@5.9.3)
'@telegram-apps/types': 2.0.3
better-promises: 0.4.1
error-kid: 0.0.7
valibot: 1.0.0(typescript@5.9.3)
'@tma.js/bridge': 2.2.3(typescript@5.9.3)
'@tma.js/signals': 1.0.1
'@tma.js/toolkit': 1.0.4
'@tma.js/transformers': 1.1.3(typescript@5.9.3)
'@tma.js/types': 1.0.2
better-promises: 1.0.0
error-kid: 1.0.2
fp-ts: 2.16.11
valibot: 1.3.0(typescript@5.9.3)
transitivePeerDependencies:
- typescript
'@telegram-apps/signals@1.1.2': {}
'@tma.js/signals@1.0.1': {}
'@telegram-apps/toolkit@2.1.3': {}
'@telegram-apps/transformers@2.2.6(typescript@5.9.3)':
'@tma.js/toolkit@1.0.4':
dependencies:
'@telegram-apps/toolkit': 2.1.3
'@telegram-apps/types': 2.0.3
valibot: 1.0.0-beta.14(typescript@5.9.3)
better-promises: 1.0.0
fp-ts: 2.16.11
'@tma.js/transformers@1.1.3(typescript@5.9.3)':
dependencies:
'@tma.js/toolkit': 1.0.4
'@tma.js/types': 1.0.2
fp-ts: 2.16.11
valibot: 1.3.0(typescript@5.9.3)
transitivePeerDependencies:
- typescript
'@telegram-apps/types@2.0.3': {}
'@tma.js/types@1.0.2': {}
'@traversable/json@0.0.26(@traversable/registry@0.0.25)':
dependencies:
@@ -3213,9 +3205,9 @@ snapshots:
baseline-browser-mapping@2.10.0: {}
better-promises@0.4.1:
better-promises@1.0.0:
dependencies:
error-kid: 0.0.7
error-kid: 1.0.2
binary-extensions@2.3.0: {}
@@ -3320,7 +3312,7 @@ snapshots:
environment@1.1.0: {}
error-kid@0.0.7: {}
error-kid@1.0.2: {}
es-define-property@1.0.1: {}
@@ -3390,6 +3382,8 @@ snapshots:
hasown: 2.0.2
mime-types: 2.1.35
fp-ts@2.16.11: {}
framer-motion@12.35.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
motion-dom: 12.35.1
@@ -4004,11 +3998,7 @@ snapshots:
dependencies:
react: 19.2.4
valibot@1.0.0(typescript@5.9.3):
optionalDependencies:
typescript: 5.9.3
valibot@1.0.0-beta.14(typescript@5.9.3):
valibot@1.3.0(typescript@5.9.3):
optionalDependencies:
typescript: 5.9.3

View File

@@ -1,22 +1,87 @@
import * as tg from "@telegram-apps/sdk-react";
import * as tg from "@tma.js/sdk-react";
export const STORAGE_KEYS = {
authToken: "authToken",
} as const;
export type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];
const MOCKED_START_PARAM = `debug+${Math.random().toString(36).substring(2, 15)}`;
tg.mockTelegramEnv({
launchParams: {
tgWebAppData: new URLSearchParams({
user: JSON.stringify({
id: 1,
first_name: "Pavel",
is_bot: false,
last_name: "Durov",
username: "durov",
language_code: "en",
is_premium: true,
photo_url:
"https://media4.giphy.com/media/v1.Y2lkPTZjMDliOTUyeXF1MzYyY2pwMjR2YWFhNDhqdXBsc216MWo2aW9pczNnNXM2ZmZmbCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/xUPGcHc4I3wICqp8bu/giphy.gif",
added_to_attachment_menu: false,
allows_write_to_pm: true,
} satisfies tg.User),
hash: "",
signature: "",
auth_date: Date.now().toString(),
}),
tgWebAppStartParam: MOCKED_START_PARAM,
tgWebAppThemeParams: {},
tgWebAppVersion: "8",
tgWebAppPlatform: "android",
},
});
type WithChecks<Result> = {
ifAvailable: (...args: any[]) => { ok: true; data: Result } | { ok: false };
};
const isPromise = <T>(value: T | Promise<T>): value is Promise<T> =>
value && typeof value === "object" && "then" in value && typeof value.then === "function";
const promisify = <T>(value: T | Promise<T>): Promise<T> =>
isPromise(value) ? value : Promise.resolve(value);
const isTMA = () => tg.retrieveLaunchParams().tgWebAppStartParam !== MOCKED_START_PARAM;
const fallbackImplementation = <
T extends ((...args: any) => any) & WithChecks<any>,
Async extends boolean,
Params extends Parameters<T>,
Result extends T extends WithChecks<infer Result> ? Result : never,
F extends (...args: Params) => Async extends true ? Result | Awaited<Result> : Result,
>(
async: Async,
args: Params,
cb: T,
onErr: F,
): Async extends true ? Promise<Result> : Awaited<Result> => {
if (isTMA()) {
const res = cb.ifAvailable.apply(null, args);
const returnValue = (res.ok ? res.data : onErr.apply(null, args)) as Result;
if (!async) return returnValue as Async extends true ? Promise<Result> : Awaited<Result>;
return promisify(returnValue).catch(() => onErr.apply(null, args)) as Async extends true
? Promise<Result>
: Awaited<Result>;
} else {
const result = onErr.apply(null, args);
return (async ? promisify(result) : result) as Async extends true
? Promise<Result>
: Awaited<Result>;
}
};
export default {
init: () => {
try {
tg.setDebug(import.meta.env.DEV);
tg.init({ acceptCustomStyles: true });
tg.requestFullscreen();
tg.disableVerticalSwipes();
tg.expandViewport();
tg.setMiniAppHeaderColor("#000000");
} catch {
console.warn("Telegram SDK not available in browser.");
}
tg.setDebug(import.meta.env.DEV);
tg.init({ acceptCustomStyles: true });
tg.viewport.requestFullscreen.ifAvailable();
tg.swipeBehavior.disableVertical.ifAvailable();
tg.viewport.expand.ifAvailable();
tg.miniApp.setHeaderColor.ifAvailable("#000000");
console.log(isTMA() ? "Telegram Mini App initialized" : "TMA Debug mode in Web initialized");
},
openLink(url: string | URL, options?: tg.OpenLinkOptions) {
tg.openLink.ifAvailable(url, options);
@@ -28,25 +93,32 @@ export default {
},
initData: tg.initData,
storage: {
clear() {
localStorage.clear();
tg.cloudStorage.clear.ifAvailable();
async clear() {
return fallbackImplementation(true, [], tg.cloudStorage.clear, localStorage.clear);
},
getItem(key: StorageKey, options?: tg.InvokeCustomMethodOptions) {
return tg.cloudStorage
.getItem(key, options)
.catch(
() =>
localStorage.getItem(key) ?? tg.AbortablePromise.reject(new Error("Item not found")),
);
async getItem(key: StorageKey, options?: tg.InvokeCustomMethodFpOptions) {
return fallbackImplementation(
true,
[key, options],
tg.cloudStorage.getItem,
(key) => localStorage.getItem(key) ?? "",
);
},
setItem(key: StorageKey, value: string, options?: tg.InvokeCustomMethodOptions) {
localStorage.setItem(key, value);
tg.cloudStorage.setItem.ifAvailable(key, value, options);
async setItem(key: StorageKey, value: string, options?: tg.InvokeCustomMethodFpOptions) {
return fallbackImplementation(
true,
[key, value, options],
tg.cloudStorage.setItem,
localStorage.setItem,
);
},
deleteItem(key: StorageKey, options?: tg.InvokeCustomMethodOptions) {
tg.cloudStorage.deleteItem.ifAvailable(key, options);
localStorage.removeItem(key);
async deleteItem(key: StorageKey, options?: tg.InvokeCustomMethodFpOptions) {
return fallbackImplementation(
true,
[key, options],
tg.cloudStorage.deleteItem,
localStorage.removeItem as typeof tg.cloudStorage.deleteItem,
);
},
},
};

View File

@@ -19,22 +19,6 @@ export default defineConfig({
}),
tanstackDevtools({
removeDevtoolsOnBuild: true,
logging: true,
injectSource: {
enabled: true,
},
editor: {
name: "WebStorm",
open: async (path, lineNumber, columnNumber) => {
const { exec } = await import("node:child_process");
exec(
`webstorm -g "${path.replaceAll("$", "\\$")}${lineNumber ? `:${lineNumber}` : ""}${columnNumber ? `:${columnNumber}` : ""}"`,
);
},
},
enhancedLogs: {
enabled: true,
},
}),
tanstackRouter({
target: "react",