Skip to content

feat: new components and visual#91

Merged
hmbanan666 merged 3 commits into
mainfrom
telegram-visual
Aug 26, 2025
Merged

feat: new components and visual#91
hmbanan666 merged 3 commits into
mainfrom
telegram-visual

Conversation

@hmbanan666

@hmbanan666 hmbanan666 commented Aug 26, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Added Telegram back-button handling and a persistent bottom navigation bar.
    • Introduced a themed UI with new color tokens, typography for headings, and utility classes.
    • Added haptic feedback on button taps and a reusable modal with swipe/tap-to-close.
    • New pages: Epic, Secret 1, Secret 2 placeholders.
    • Updated home title translation to “Задачи”.
    • Improved small-screen experience with conditional fullscreen and refined scrolling.
  • Style
    • Hidden scrollbars and safe-area padding utilities.
  • Chores
    • Removed an unused dependency and bumped pnpm to 10.15.x.

@hmbanan666 hmbanan666 self-assigned this Aug 26, 2025
@coderabbitai

coderabbitai Bot commented Aug 26, 2025

Copy link
Copy Markdown

Walkthrough

Introduces Telegram-specific UI/UX for the Atrium app: back-button integration with router, a persistent bottom navigation, new UI components (Button, Modal, PageContainer), CSS theme/utilities, placeholder pages, store exposure of Telegram init data state, fullscreen handling in init, removal of a legacy composable and loader, and minor package/i18n updates.

Changes

Cohort / File(s) Summary
Telegram back button & layout
apps/atrium-telegram/app/app.vue, apps/atrium-telegram/app/composables/useBackButton.ts, apps/atrium-telegram/app/components/PageContainer.vue, apps/atrium-telegram/app/layouts/default.vue, apps/atrium-telegram/app/pages/index.vue
Added useBackButton integration and initialization; PageContainer can hide Telegram back button; layout wraps content in main and adds persistent Navigation; index page uses PageContainer with back disabled and switches to userStore for init data.
Navigation
apps/atrium-telegram/app/components/Navigation.vue, apps/atrium-telegram/i18n/locales/ru-RU.json
New bottom navigation component with haptics and route highlighting; adds ru key app.home.
UI components
apps/atrium-telegram/app/components/Button.vue, apps/atrium-telegram/app/components/Modal.vue
New Button with Telegram haptic feedback; new Modal (bottom sheet) with props (isOpened, title), close emit, swipe/outside-click close, and i18n close text.
Styling & layout utilities
apps/atrium-telegram/app/assets/css/styles.css
Adds theme layer with Telegram-like CSS vars; utilities for bg/text/border/safe-area; global scroll behavior; header typography.
Pages (placeholders)
apps/atrium-telegram/app/pages/epic.vue, apps/atrium-telegram/app/pages/secret1.vue, apps/atrium-telegram/app/pages/secret2.vue
Adds three simple pages with PageContainer and static message.
Store updates
apps/atrium-telegram/app/stores/user.ts
Exposes initDataRaw and initDataState signals via store; no change to update logic.
Init fullscreen behavior
apps/atrium-telegram/app/utils/init.ts
Mounts closing behavior; requests fullscreen when available post-viewport mount; exits fullscreen on large screens with delay; guarded by availability checks.
Removals
apps/atrium-telegram/app/composables/useTelegram.ts, apps/atrium-telegram/app/spa-loading-template.html
Removes Telegram composable (replaced by store usage) and SPA loading image/animation.
Package/config
apps/atrium-telegram/package.json, package.json
Drops arktype dependency; bumps pnpm version requirements from 10.14.x to 10.15.x.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant R as Vue Router
  participant UB as useBackButton
  participant TB as Telegram backButton
  participant U as User

  R->>UB: route change
  alt route.name === 'index'
    UB->>TB: hide()
    UB-->>TB: offClick() cleanup
  else other routes
    UB->>TB: show() if not visible
    UB->>TB: onClick(register handler)
  end
  U->>TB: tap back
  TB-->>UB: onClick()
  UB->>R: go(-1)
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant Nav as Navigation.vue
  participant HF as hapticFeedback
  participant R as Vue Router

  U->>Nav: click item
  Nav->>HF: impactOccurred.isAvailable()
  alt available
    Nav->>HF: impactOccurred('light')
  end
  Nav->>R: push(target path)
Loading
sequenceDiagram
  autonumber
  participant P as Parent
  participant M as Modal.vue
  participant U as User

  P->>M: isOpened=true, title
  U->>M: click backdrop or swipe down
  M->>M: set isClosing=true (500ms)
  M-->>P: emit('close')
  M->>M: set isClosing=false
Loading
sequenceDiagram
  autonumber
  participant App as init()
  participant VP as viewport
  participant FS as Fullscreen API

  App->>App: mountClosingBehavior()
  App->>VP: wait for mount
  App->>FS: requestFullscreen() if available
  Note over App: finally: delay 50ms
  App->>FS: exitFullscreen() if width > 600
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A nibble of code, a hop through the UI,
Back buttons behave, with a gentle haptic sigh.
New pages peek out, secrets in tow,
A modal slides down, soft as snow.
I thump my paws—navigation’s neat!
Carrots for themes; this release is sweet. 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch telegram-visual

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sonarqubecloud

Copy link
Copy Markdown

@hmbanan666 hmbanan666 merged commit f1580c6 into main Aug 26, 2025
7 of 8 checks passed
@hmbanan666 hmbanan666 deleted the telegram-visual branch August 26, 2025 10:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/atrium-telegram/app/utils/init.ts (1)

46-55: Logic bug: tp ||= retrieveLaunchParams().tgWebAppThemeParams will never run because {} is truthy.

On the first theme request, you intend to emit launch params. With tp = {}, ||= won’t assign. Result: an empty object is emitted.

-          let tp: ThemeParams = {}
+          let tp: ThemeParams = {}
           if (firstThemeSent) {
             tp = themeParamsState()
           } else {
             firstThemeSent = true
-            tp ||= retrieveLaunchParams().tgWebAppThemeParams
+            tp = retrieveLaunchParams().tgWebAppThemeParams || {}
           }
🧹 Nitpick comments (23)
apps/atrium-telegram/i18n/locales/ru-RU.json (1)

8-8: Align translation key with its semantics or add an alias

  • apps/atrium-telegram/i18n/locales/ru-RU.json defines only
    app.home: "Задачи"
    • no app.tasks entry exists
  • apps/web-app and apps/web-storefront ru-RU.json files do not define either app.home or app.tasks
  • Navigation.vue (apps/atrium-telegram/app/components/Navigation.vue:43) uses t('app.home') to label the Tasks view

Action items (choose one):

  • Rename the key from hometasks (and update all code references, plus add app.tasks in every locale)
  • Or keep app.home and add an alias under app.tasks in ru-RU.json (and any future locales) so translators aren’t misled by the route-based key name
apps/atrium-telegram/app/stores/user.ts (2)

2-2: Exposing both initDataRaw and initDataState is fine — but align on a single source of truth.

You now keep two reactive sources for Telegram init data. Consider standardizing on initDataState and deriving/validating the raw string from it to avoid drift and subtle bugs.

Would you like me to refactor update() to prefer initDataState and only send the header when present? Example outside this diff:

// Example: guard header and prefer state
const authHeader = computed(() => initDataRaw.value ? `tma ${initDataRaw.value}` : undefined)

const data = await $fetch('/api/auth/me', {
  headers: authHeader.value ? { Authorization: authHeader.value } : undefined,
})

20-21: initDataState added but currently unused in logic.

Given pages now read userStore.initDataState?.user?.id, ensure initDataState stays in sync and is the canonical place for Telegram user id. If update() depends on initDataRaw but UI reads initDataState, inconsistent states can surface after re-init.

I can add a small watcher to reconcile both (e.g., when initDataState updates but you haven’t called update() yet).

apps/atrium-telegram/app/composables/useBackButton.ts (1)

18-21: Minor: use router.back() for clarity.

router.back() is clearer than go(-1) and expresses intent.

Apply this diff:

-  function onBackButtonClick(): void {
-    router.go(-1)
-  }
+  function onBackButtonClick(): void {
+    router.back()
+  }
apps/atrium-telegram/app/pages/epic.vue (1)

3-3: Localize user-facing text.

Hard-coded RU text will bypass i18n and cannot be translated. Use i18n and a new key, e.g., pages.epic.soon.

Apply this diff to the template line:

-    <h1>Еще рано сюда заходить</h1>
+    <h1>{{ t('pages.epic.soon') }}</h1>

And add script setup (outside this hunk):

<script setup lang="ts">
const { t } = useI18n()
</script>

Remember to add the key to all locales:

{
  "pages": {
    "epic": {
      "soon": "Еще рано сюда заходить"
    }
  }
}
apps/atrium-telegram/app/pages/secret1.vue (1)

1-5: Externalize the static heading to i18n and avoid duplication.

Hardcoded Russian text makes localization harder and duplicates across pages (secret1/secret2/epic). Use a shared translation key and/or a small “ComingSoon” component.

Inline change:

-    <h1>Еще рано сюда заходить</h1>
+    <h1>{{ $t('comingSoon') }}</h1>

Add locales (example):

// ru-RU.json
{ "comingSoon": "Еще рано сюда заходить" }

// en-US.json
{ "comingSoon": "Too early to enter here" }

Optionally create components/ComingSoon.vue and reuse it across these placeholder pages. Want me to draft it?

apps/atrium-telegram/app/pages/secret2.vue (1)

1-5: Same i18n improvement as secret1.vue.

Mirror the i18n change here to keep placeholders consistent and translatable.

-    <h1>Еще рано сюда заходить</h1>
+    <h1>{{ $t('comingSoon') }}</h1>
apps/atrium-telegram/app/pages/index.vue (1)

2-5: Localize user-facing strings in index.vue

Two hardcoded Russian strings in apps/atrium-telegram/app/pages/index.vue (lines 2–5) should be replaced with i18n keys and interpolation to support multiple locales:

• File: apps/atrium-telegram/app/pages/index.vue
• Lines: 2–5

Suggested diff:

--- a/apps/atrium-telegram/app/pages/index.vue
+++ b/apps/atrium-telegram/app/pages/index.vue
@@ 2,5
-  <PageContainer :back="false">
-    <h1>Привет, {{ userStore.name }}!</h1>
-    <p>Id пользователя: {{ userStore.initDataState?.user?.id }}</p>
+  <PageContainer :back="false">
+    <h1>{{ $t('greeting', { name: userStore.name }) }}</h1>
+    <p>{{ $t('userId') }}: {{ userStore.initDataState?.user?.id }}</p>

Add corresponding entries to your locale files:

// src/locales/ru-RU.json
{
  "greeting": "Привет, {name}!",
  "userId": "Id пользователя"
}
// src/locales/en-US.json
{
  "greeting": "Hi, {name}!",
  "userId": "User ID"
}

Verified that initDataState is exported by useUserStore in
apps/atrium-telegram/app/stores/user.ts (no changes needed).

apps/atrium-telegram/app/components/PageContainer.vue (2)

12-12: Prop changes after mount aren’t observed.

If back can toggle per route state, consider watching it and applying show/hide accordingly. If it’s truly static per page, ignore.

-onMounted(() => {
+onMounted(() => {
   if (!back && (hideBackButton as any)?.isAvailable?.() !== false) {
     hideBackButton()
   }
 })
+watch(() => back, (val) => {
+  if ((hideBackButton as any)?.isAvailable?.() === false) return
+  val ? (showBackButton && (showBackButton as any)?.isAvailable?.() !== false && showBackButton())
+      : hideBackButton()
+})

9-15: Prevent global back-button state from leaking between pages
The direct call to hideBackButton() in PageContainer.vue alters a global UI control without a matching restore. The shared useBackButton composable only toggles visibility on route changes, not on component unmounts. As a result, if you disable the back button on one page (via back = false), it will remain hidden until the next navigation triggers the route watcher.

• Add a guard to avoid errors when the SDK isn’t present (e.g. non-Telegram contexts).
• Restore the button on unmount so the “hidden” state doesn’t leak to subsequent pages.
• (Optional) Instead of calling the SDK methods directly, consider driving visibility via the existing useBackButton composable for consistency.

Proposed minimal diff:

 <script setup lang="ts">
-import { hideBackButton } from '@telegram-apps/sdk-vue'
+import { hideBackButton, showBackButton } from '@telegram-apps/sdk-vue'
 
 const { back = true } = defineProps<{ back?: boolean }>()
 
-onMounted(() => !back && hideBackButton())
+onMounted(() => {
+  if (!back && (hideBackButton as any)?.isAvailable?.() !== false) {
+    hideBackButton()
+  }
+})
+
+onUnmounted(() => {
+  if (!back && (showBackButton as any)?.isAvailable?.() !== false) {
+    showBackButton()
+  }
+})
 </script>
apps/atrium-telegram/app/utils/init.ts (1)

72-79: Minor: prefer async/await over nested .then for readability and linear flow.

This block already uses async APIs; converting to await improves readability and error handling.

apps/atrium-telegram/app/components/Navigation.vue (3)

5-11: Use type="button" on nav items.

Prevents unintended form submissions if the nav is rendered inside a form.

-        <button
+        <button
+          type="button"
           v-for="route in mainRoutes"
           :key="route.path"
           class="flex flex-col items-center justify-center gap-1 px-4 cursor-pointer tg-text-subtitle"
           @click="handleClick(route.path)"
         >

12-26: Avoid duplicating active-state logic and improve accessibility.

The active check is repeated and tied to path strict equality. Prefer a helper/computed and expose aria-current for screen readers; consider matching by route name.

-            :class="[
-              router.currentRoute.value.path === route.path && 'tg-bg-button tg-text-button',
-            ]"
+            :class="[isActive(route) && 'tg-bg-button tg-text-button']"
+            :aria-current="isActive(route) ? 'page' : undefined"

Add inside <script setup>:

+const current = computed(() => router.currentRoute.value)
+function isActive(r: { path: string; name?: string }) {
+  return current.value.name === r.name || current.value.path === r.path
+}

39-64: Hardcoded titles ‘Секрет’—consider i18n keys for consistency.

Three items use a Russian literal while the first uses t('app.home'). Recommend moving them into i18n.

   {
     path: '/epic',
     name: 'quests',
-    title: 'Секрет',
+    title: t('app.quests'),
     icon: 'i-lucide-lock',
   },
   {
     path: '/secret1',
     name: 'shop',
-    title: 'Секрет',
+    title: t('app.secret1'),
     icon: 'i-lucide-lock',
   },
   {
     path: '/secret2',
     name: 'top',
-    title: 'Секрет',
+    title: t('app.secret2'),
     icon: 'i-lucide-lock',
   },
apps/atrium-telegram/app/components/Modal.vue (3)

53-57: Swipe-to-close fires on any swipe state change; gate on actual swiping.

Close only when swiping and direction is down.

-watch(isSwiping, () => {
-  if (direction.value === 'down') {
-    onClose()
-  }
-})
+watch(isSwiping, (now) => {
+  if (now && direction.value === 'down') onClose()
+})

15-21: Add dialog semantics for accessibility.

Give the sheet proper dialog semantics and label it.

-  <div
+  <div
     class="z-40 flex flex-col justify-end fixed left-0 right-0 bottom-0 mx-auto w-full max-w-lg max-h-[100dvh] overflow-y-auto p-4 m-0 pb-20 shadow-none translate-y-full transition-transform duration-500"
+    role="dialog"
+    aria-modal="true"
+    :aria-labelledby="'modal-title'"
     :class="[
       isOpened && '!top-0 !translate-y-0',
       isClosing && '!translate-y-full',
     ]"
   >
-    <div ref="target" class="relative mb-10 p-4 md:p-6 lg:p-8 space-y-3 tg-bg-section tg-text rounded-xl shadow-lg max-h-[70dvh] overflow-y-auto !overflow-visible">
-      <h3 class="text-xl md:text-2xl font-medium leading-tight">
+    <div ref="target" class="relative mb-10 p-4 md:p-6 lg:p-8 space-y-3 tg-bg-section tg-text rounded-xl shadow-lg max-h-[70dvh] overflow-y-auto !overflow-visible">
+      <h3 id="modal-title" class="text-xl md:text-2xl font-medium leading-tight">
         {{ title }}
       </h3>

43-45: Type emitted events for better DX.

Optional, but helps with TS hints.

-const emit = defineEmits(['close'])
+const emit = defineEmits<{ (e: 'close'): void }>()
apps/atrium-telegram/app/assets/css/styles.css (6)

27-46: Provide a compound “button” utility to avoid forgetting text color on button backgrounds.

Right now consumers must combine .tg-bg-button + .tg-text-button. A single utility reduces errors.

   .tg-bg-section {
     background-color: var(--tg-theme-section-bg-color);
   }
+
+  /* Convenience utility composing bg + text color for buttons */
+  .tg-button {
+    background-color: var(--tg-theme-button-color);
+    color: var(--tg-theme-button-text-color);
+  }

66-68: Ensure links remain distinguishable; add underlines by default or offer a dedicated utility.

Color-only links risk accessibility regressions, especially on low-contrast screens.

Option A (default underline):

   .tg-text-link {
-    color: var(--tg-theme-link-color);
+    color: var(--tg-theme-link-color);
+    text-decoration: underline;
   }

Option B (new utility, keeps current behavior unchanged elsewhere):

+  .tg-link {
+    color: var(--tg-theme-link-color);
+    text-decoration: underline;
+    text-underline-offset: 2px;
+  }

102-105: Hidden scrollbars: add Firefox support and consider platform-guarding for accessibility.

Currently only WebKit is affected; add scrollbar-width for Gecko.

 #app::-webkit-scrollbar {
   display: none;
   width: 0;
 }
+/* Firefox */
+#app {
+  scrollbar-width: none;
+}

If this UI ever targets desktop, consider a feature flag to preserve discoverability of scroll areas.


92-101: Use dynamic viewport units to improve mobile viewport stability.

100% height can misbehave with browser UI chrome; 100dvh/100svh better reflect actual usable height.

 html, body, #app {
   width: 100%;
-  height: 100%;
+  /* Prefer dynamic viewport units on modern browsers */
+  height: 100dvh;
+  min-height: 100svh;
   overflow: hidden;
   overscroll-behavior: none;
 }

1-24: Minor: add comments mapping tokens to Telegram themeParams keys for maintainability.

Speeds up future maintenance and cross-team handoffs.

 @layer theme {
   :root {
+    /* Mirrors Telegram WebApp themeParams: https://core.telegram.org/bots/webapps#themeparams */
     --tg-theme-bg-color: #ffffff;
     --tg-theme-secondary-bg-color: #f1f1f1;
     --tg-theme-header-bg-color: #ffffff;
     --tg-theme-bottom-bar-bg-color: #ffffff;

107-109: Headings: add font fallbacks and move into a base layer for predictable cascade (esp. with Tailwind).

Prevents blank fonts when --font-headers is unset and stabilizes order with other layers.

-h1, h2, h3, h4, h5, h6 {
-  font-family: var(--font-headers);
-}
+@layer base {
+  h1, h2, h3, h4, h5, h6 {
+    font-family: var(--font-headers, system-ui, -apple-system, "Segoe UI", Roboto,
+      "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji",
+      "Segoe UI Symbol", "Noto Color Emoji", sans-serif);
+  }
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dd87801 and 6e87860.

⛔ Files ignored due to path filters (2)
  • apps/atrium-telegram/public/sushi-heart.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • apps/atrium-telegram/app/app.vue (1 hunks)
  • apps/atrium-telegram/app/assets/css/styles.css (1 hunks)
  • apps/atrium-telegram/app/components/Button.vue (1 hunks)
  • apps/atrium-telegram/app/components/Modal.vue (1 hunks)
  • apps/atrium-telegram/app/components/Navigation.vue (1 hunks)
  • apps/atrium-telegram/app/components/PageContainer.vue (1 hunks)
  • apps/atrium-telegram/app/composables/useBackButton.ts (1 hunks)
  • apps/atrium-telegram/app/composables/useTelegram.ts (0 hunks)
  • apps/atrium-telegram/app/layouts/default.vue (1 hunks)
  • apps/atrium-telegram/app/pages/epic.vue (1 hunks)
  • apps/atrium-telegram/app/pages/index.vue (1 hunks)
  • apps/atrium-telegram/app/pages/secret1.vue (1 hunks)
  • apps/atrium-telegram/app/pages/secret2.vue (1 hunks)
  • apps/atrium-telegram/app/spa-loading-template.html (0 hunks)
  • apps/atrium-telegram/app/stores/user.ts (3 hunks)
  • apps/atrium-telegram/app/utils/init.ts (2 hunks)
  • apps/atrium-telegram/i18n/locales/ru-RU.json (1 hunks)
  • apps/atrium-telegram/package.json (0 hunks)
  • package.json (1 hunks)
💤 Files with no reviewable changes (3)
  • apps/atrium-telegram/app/spa-loading-template.html
  • apps/atrium-telegram/package.json
  • apps/atrium-telegram/app/composables/useTelegram.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-01T10:34:55.317Z
Learnt from: hmbanan666
PR: nextorders/roll-stack#40
File: package.json:5-6
Timestamp: 2025-08-01T10:34:55.317Z
Learning: The latest version of pnpm on npm registry is 10.14.0 as of August 2025, so "npm install -g pnpm" installs this version without needing explicit version specification.

Applied to files:

  • package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (7)
apps/atrium-telegram/app/stores/user.ts (1)

100-102: Approve removal of legacy exports

No consumer code references userStore.type or userStore.phone under apps/**, so removing those exports poses no breakages.

• Verification command run:

rg -nP "userStore\.(type|phone)\b" -g "apps/**"

• No matches were found.

apps/atrium-telegram/app/app.vue (1)

38-40: Back-button initialization timing is spot on.

Calling useBackButton() right after init(...) and before store initialization is the right place for wiring Telegram navigation.

apps/atrium-telegram/app/layouts/default.vue (1)

2-6: Layout semantics and persistent nav look good.

Placing <Navigation /> outside <main> keeps landmarks correct and content accessible. No issues spotted.

apps/atrium-telegram/app/pages/index.vue (1)

2-5: Good use of PageContainer with back disabled and store-sourced init data.

:back="false" on the root page aligns with Telegram UX, and moving to userStore.initDataState centralizes the source of truth.

apps/atrium-telegram/app/assets/css/styles.css (3)

1-24: Theme tokens are hardcoded light values; verify dark mode/TG theme param integration.

Telegram surfaces themeParams and dynamic theme switching; these static values may diverge from user themes.

Do we already map Telegram.WebApp.themeParams into these CSS variables at runtime? If not, I can add a small init that syncs incoming themeParams and listens for themeChanged to update :root. Optional: add a dark fallback via prefers-color-scheme if Telegram context is absent.


27-90: Layering with Tailwind (if present): confirm load order to avoid specificity surprises.

Using @layer utilities alongside Tailwind requires this file to be loaded after Tailwind’s base/components but before its utilities (or merged in the same pipeline) to achieve expected ordering.

If Tailwind is active, please confirm this stylesheet’s position in the build and that the classes are not purged. I can supply a minimal PR to safelist .tg-* if needed.


15-20: Contrast check for accent/link colors.

Ensure --tg-theme-link-color and --tg-theme-accent-text-color pass WCAG AA (4.5:1) on their intended backgrounds at typical font sizes.

If you want, I can run a quick audit and tune the palette or add state tokens (hover/active) for buttons and links.

Comment on lines +38 to +40
// Telegram
useBackButton()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Guard against SSR and verify cleanup in the composable.

If useBackButton() touches window/Telegram APIs synchronously, it may break server-side rendering. Either ensure the composable gates side effects behind onMounted/import.meta.client and unregisters handlers on unmount, or guard the call here.

Apply if needed:

-// Telegram
-useBackButton()
+// Telegram
+if (import.meta.client) {
+  useBackButton()
+}

Run this script to confirm SSR-safety and cleanup within the composable:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Ensure the composable exists
fd -a useBackButton.ts apps/atrium-telegram

# 2) Inspect for client-only lifecycle usage and cleanup
rg -nC2 -e 'onMounted|onUnmounted|BackButton|window\.' apps/atrium-telegram/app/composables/useBackButton.ts || true

# 3) Show file content (first 200 lines) for a quick glance
sed -n '1,200p' apps/atrium-telegram/app/composables/useBackButton.ts 2>/dev/null || true

Length of output: 1300


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Locate createSharedComposable definition(s)
rg -n 'createSharedComposable' -C3 -g '*.ts' apps/atrium-telegram

# 2) If found, display the first 200 lines of its implementation
file=$(rg -l 'createSharedComposable' -g '*.ts' apps/atrium-telegram | head -n1 || true)
if [[ -n "$file" ]]; then
  echo "=== Showing implementation of $file ==="
  sed -n '1,200p' "$file"
else
  echo "createSharedComposable not found under apps/atrium-telegram"
fi

Length of output: 1391


Ensure SSR‐safety and proper cleanup in useBackButton

After inspecting apps/atrium-telegram/app/composables/useBackButton.ts, we see that:

  • The watcher on router.currentRoute (and all backButton calls) run immediately in setup—even during SSR—since there’s no onMounted guard.
  • There’s no onUnmounted hook to call the stored offClick(), so any registered Telegram back-button handler may never be torn down.

To address both concerns, please apply one of the following refactorings:

Refactor the composable itself (apps/atrium-telegram/app/composables/useBackButton.ts):

 function _useBackButton() {
   let offClick: () => void = () => {}

   const router = useRouter()

+  onMounted(() => {
+    const stopWatch = watch(router.currentRoute, () => {
+      if (router.currentRoute.value.name === 'index') {
+        backButton.hide()
+        offClick()
+      } else if (!backButton.isVisible()) {
+        backButton.show()
+        offClick = backButton.onClick(onBackButtonClick)
+      }
+    })
+
+    onUnmounted(() => {
+      offClick()
+      stopWatch()
+    })
+  })

   function onBackButtonClick(): void {
     router.go(-1)
   }
 }

This ensures all backButton interactions only run on the client, and that any handler is removed when the component is torn down.

Guard the call‐site (apps/atrium-telegram/app/app.vue, around line 38):

- // Telegram
- useBackButton()
+ // Telegram
+ if (import.meta.client) {
+   useBackButton()
+ }

This quick-and-dirty alternative prevents any part of the composable from executing during SSR—but still leaves the cleanup hole if you don’t also add an onUnmounted inside the composable.

Choose the option that best fits your SSR strategy. Ensuring both client‐only execution and teardown will prevent build-time errors and memory leaks.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Telegram
useBackButton()
// Telegram
if (import.meta.client) {
useBackButton()
}

Comment on lines +22 to +24
--tg-viewport-safe-area-inset-top: 20px;
--tg-viewport-content-safe-area-inset-top: 20px;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Safe-area variables are top-only; add bottom counterparts for notch/home-indicator devices.

Without bottom insets, content can be obscured by iOS/Android gesture areas or the app’s bottom bar.

Apply this diff inside the :root block to introduce bottom inset tokens:

   :root {
     --tg-viewport-safe-area-inset-top: 20px;
     --tg-viewport-content-safe-area-inset-top: 20px;
+    /* Bottom insets used for gesture/home indicator areas */
+    --tg-viewport-safe-area-inset-bottom: 0px;
+    --tg-viewport-content-safe-area-inset-bottom: 0px;
+    /* Height of the persistent bottom navigation (override from component/init) */
+    --tg-bottom-bar-height: 56px;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
--tg-viewport-safe-area-inset-top: 20px;
--tg-viewport-content-safe-area-inset-top: 20px;
}
:root {
--tg-viewport-safe-area-inset-top: 20px;
--tg-viewport-content-safe-area-inset-top: 20px;
/* Bottom insets used for gesture/home indicator areas */
--tg-viewport-safe-area-inset-bottom: 0px;
--tg-viewport-content-safe-area-inset-bottom: 0px;
/* Height of the persistent bottom navigation (override from component/init) */
--tg-bottom-bar-height: 56px;
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/assets/css/styles.css around lines 22 to 24, the
:root CSS defines only top safe-area variables which can cause content to be
obscured by device bottom gesture areas; add matching bottom variables (e.g.,
--tg-viewport-safe-area-inset-bottom and
--tg-viewport-content-safe-area-inset-bottom) with appropriate values (use
env(safe-area-inset-bottom, 0px) or a fixed fallback like 20px) inside the :root
block so layouts can reference bottom insets for padding/margins.

Comment on lines +79 to +89
.tg-safe-area {
padding-top: var(--tg-viewport-safe-area-inset-top);
padding-bottom: 0;
}
.tg-content-safe-area-top {
padding-top: var(--tg-viewport-content-safe-area-inset-top);
}
.tg-content-safe-area {
padding-top: var(--tg-viewport-content-safe-area-inset-top);
padding-bottom: 200px;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Hardcoded 200px bottom padding will cause large blank space or content clipping.

This is brittle across devices and dynamic UI heights.

Update safe-area utilities to derive from env() + variables instead of a fixed 200px:

   .tg-safe-area {
-    padding-top: var(--tg-viewport-safe-area-inset-top);
-    padding-bottom: 0;
+    /* Use device env() fallback and custom vars as a floor */
+    padding-top: max(env(safe-area-inset-top, 0px), var(--tg-viewport-safe-area-inset-top));
+    padding-bottom: max(env(safe-area-inset-bottom, 0px), var(--tg-viewport-safe-area-inset-bottom));
   }
   .tg-content-safe-area-top {
     padding-top: var(--tg-viewport-content-safe-area-inset-top);
   }
   .tg-content-safe-area {
-    padding-top: var(--tg-viewport-content-safe-area-inset-top);
-    padding-bottom: 200px;
+    padding-top: var(--tg-viewport-content-safe-area-inset-top);
+    /* Bottom content safe area = device inset + bottom bar height */
+    padding-bottom: calc(
+      max(env(safe-area-inset-bottom, 0px), var(--tg-viewport-content-safe-area-inset-bottom))
+      + var(--tg-bottom-bar-height)
+    );
   }

If the bottom nav height is dynamic, set --tg-bottom-bar-height from the component on mount/resize.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.tg-safe-area {
padding-top: var(--tg-viewport-safe-area-inset-top);
padding-bottom: 0;
}
.tg-content-safe-area-top {
padding-top: var(--tg-viewport-content-safe-area-inset-top);
}
.tg-content-safe-area {
padding-top: var(--tg-viewport-content-safe-area-inset-top);
padding-bottom: 200px;
}
.tg-safe-area {
/* Use device env() fallback and custom vars as a floor */
padding-top: max(env(safe-area-inset-top, 0px), var(--tg-viewport-safe-area-inset-top));
padding-bottom: max(env(safe-area-inset-bottom, 0px), var(--tg-viewport-safe-area-inset-bottom));
}
.tg-content-safe-area-top {
padding-top: var(--tg-viewport-content-safe-area-inset-top);
}
.tg-content-safe-area {
padding-top: var(--tg-viewport-content-safe-area-inset-top);
/* Bottom content safe area = device inset + bottom bar height */
padding-bottom: calc(
max(env(safe-area-inset-bottom, 0px), var(--tg-viewport-content-safe-area-inset-bottom))
var(--tg-bottom-bar-height)
);
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/assets/css/styles.css around lines 79-89, the
.tg-content-safe-area currently uses a hardcoded padding-bottom: 200px which
causes layout issues; replace the fixed value with a calculation that combines
the OS safe-area inset and a CSS variable for the bottom bar height (e.g.
padding-bottom: calc(env(safe-area-inset-bottom, 0px) +
var(--tg-bottom-bar-height, 0px))); and ensure components that render the bottom
nav set/update --tg-bottom-bar-height on mount and on resize (measuring the nav
element height and writing it to the root or component style) so the padding
adapts dynamically.

Comment on lines +2 to +7
<button
class="p-3 tg-bg-button w-full rounded-2xl font-medium cursor-pointer active:scale-95 duration-200"
@click="handleClick"
>
<slot />
</button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Set explicit button type to avoid accidental form submissions.

Default HTML button type is "submit". Explicitly set type="button" for a generic action button.

-  <button
+  <button
+    type="button"
     class="p-3 tg-bg-button w-full rounded-2xl font-medium cursor-pointer active:scale-95 duration-200"
     @click="handleClick"
   >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
class="p-3 tg-bg-button w-full rounded-2xl font-medium cursor-pointer active:scale-95 duration-200"
@click="handleClick"
>
<slot />
</button>
<button
type="button"
class="p-3 tg-bg-button w-full rounded-2xl font-medium cursor-pointer active:scale-95 duration-200"
@click="handleClick"
>
<slot />
</button>
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/Button.vue around lines 2 to 7, the
button lacks an explicit type attribute so it defaults to "submit" and can
accidentally submit surrounding forms; update the button element to include
type="button" to prevent unintended form submission while keeping the existing
classes and click handler unchanged.

Comment on lines +10 to +18
<script setup lang="ts">
import { hapticFeedback } from '@telegram-apps/sdk-vue'

function handleClick() {
if (hapticFeedback.impactOccurred.isAvailable()) {
hapticFeedback.impactOccurred('light')
}
}
</script>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Button does not emit a click event to consumers.

Parent listeners like <Button @click="..."> will not fire unless the component emits a click. Emit the native event so consumers can react.

 <script setup lang="ts">
 import { hapticFeedback } from '@telegram-apps/sdk-vue'
 
-function handleClick() {
+const emit = defineEmits<{ (e: 'click', event: MouseEvent): void }>()
+
+function handleClick(event: MouseEvent) {
   if (hapticFeedback.impactOccurred.isAvailable()) {
     hapticFeedback.impactOccurred('light')
   }
+  emit('click', event)
 }
 </script>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<script setup lang="ts">
import { hapticFeedback } from '@telegram-apps/sdk-vue'
function handleClick() {
if (hapticFeedback.impactOccurred.isAvailable()) {
hapticFeedback.impactOccurred('light')
}
}
</script>
<script setup lang="ts">
import { hapticFeedback } from '@telegram-apps/sdk-vue'
const emit = defineEmits<{ (e: 'click', event: MouseEvent): void }>()
function handleClick(event: MouseEvent) {
if (hapticFeedback.impactOccurred.isAvailable()) {
hapticFeedback.impactOccurred('light')
}
emit('click', event)
}
</script>
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/Button.vue around lines 10–18, the click
handler triggers haptic feedback but does not emit a click event to parent
components; add a defineEmits(...) call in the <script setup> and update
handleClick to accept the native event (e.g., MouseEvent), call emit('click',
event) after performing hapticFeedback, and ensure the template binds the
handler (e.g., @click="handleClick") so parent <Button @click="..."> listeners
receive the native event.

@@ -0,0 +1,23 @@
import { backButton } from '@telegram-apps/sdk-vue'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Import createSharedComposable explicitly and guard for SSR.

createSharedComposable may not be auto-imported (it doesn’t start with “use”). Also, importing Telegram SDK APIs on server can throw; guard the composable for client-only usage.

Apply this diff to import explicitly:

-import { backButton } from '@telegram-apps/sdk-vue'
+import { backButton } from '@telegram-apps/sdk-vue'
+import { createSharedComposable } from '@vueuse/core'

Optionally, add a client guard inside the composable (see next comments).


🏁 Script executed:

#!/bin/bash
# Display the content of the useBackButton composable
cat apps/atrium-telegram/app/composables/useBackButton.ts || echo "File not found: apps/atrium-telegram/app/composables/useBackButton.ts"

Length of output: 606


Import createSharedComposable and guard against SSR in useBackButton

To prevent runtime errors and ensure createSharedComposable is properly recognized, update your composable as follows:

• File: apps/atrium-telegram/app/composables/useBackButton.ts
– At the top, explicitly import createSharedComposable (and an SSR-check helper) from @vueuse/core.
– Inside _useBackButton, bail out early on the server to avoid calling backButton APIs during SSR.

 import { backButton } from '@telegram-apps/sdk-vue'
+import { createSharedComposable, isClient } from '@vueuse/core'

 function _useBackButton() {
+  if (!isClient) { return }
   let offClick: () => void = () => {}

   const router = useRouter()

Optionally, you can also wrap only the Watcher logic in an if (isClient) { … } block instead of exiting the entire composable.

With these changes:

  1. TypeScript/Vite auto-importers will correctly resolve createSharedComposable.
  2. All calls to the Telegram SDK’s backButton happen only in the browser, preventing server-side errors.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { backButton } from '@telegram-apps/sdk-vue'
import { backButton } from '@telegram-apps/sdk-vue'
import { createSharedComposable, isClient } from '@vueuse/core'
function _useBackButton() {
if (!isClient) { return }
let offClick: () => void = () => {}
const router = useRouter()
// …rest of your logic…
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/composables/useBackButton.ts around line 1, the file
currently imports only backButton which can cause SSR issues and prevents
createSharedComposable from being resolved; import createSharedComposable and an
SSR helper (isClient or useIsClient) from @vueuse/core at the top, then inside
the _useBackButton implementation guard against server execution by returning
early when not running on the client (or wrap the watcher/Telegram SDK calls in
an if (isClient) { ... } block) so that backButton is only called in the browser
and TypeScript/Vite auto-imports resolve createSharedComposable correctly.

Comment on lines +3 to +7
function _useBackButton() {
let offClick: () => void = () => {}

const router = useRouter()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add client-only guard and cleanup on scope dispose.

Prevent SSR execution and ensure we always detach the click handler on unmount.

Apply this diff:

 function _useBackButton() {
-  let offClick: () => void = () => {}
+  if (!import.meta.client)
+    return
+
+  let offClick: () => void = () => {}
 
   const router = useRouter()
+  onScopeDispose(() => {
+    try { offClick() } catch {}
+    try { backButton.hide() } catch {}
+  })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function _useBackButton() {
let offClick: () => void = () => {}
const router = useRouter()
function _useBackButton() {
if (!import.meta.client)
return
let offClick: () => void = () => {}
const router = useRouter()
onScopeDispose(() => {
try { offClick() } catch {}
try { backButton.hide() } catch {}
})
// …rest of your back-button logic…
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/composables/useBackButton.ts around lines 3 to 7,
guard any DOM/window or event-listener logic so it only runs on the client (e.g.
if (typeof window !== 'undefined') or process.client) and set the offClick
variable to the function that removes the attached click handler; also register
onScopeDispose(() => offClick()) so the handler is always detached on unmount;
ensure offClick remains a no-op when not on the client and import/use
onScopeDispose if not already present.

Comment on lines +8 to +16
watch(router.currentRoute, () => {
if (router.currentRoute.value.name === 'index') {
backButton.hide()
offClick()
} else if (!backButton.isVisible()) {
backButton.show()
offClick = backButton.onClick(onBackButtonClick)
}
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Watcher is not immediate; route.name check is brittle with i18n (e.g., index___ru).

Without immediate: true, initial pages won’t set visibility. Matching name === 'index' will fail with locale-suffixed names. Prefer route meta (e.g., back: false) or a tolerant check; also make watch immediate.

Apply this diff:

-  watch(router.currentRoute, () => {
-    if (router.currentRoute.value.name === 'index') {
-      backButton.hide()
-      offClick()
-    } else if (!backButton.isVisible()) {
-      backButton.show()
-      offClick = backButton.onClick(onBackButtonClick)
-    }
-  })
+  const route = useRoute()
+  watch(
+    () => ({ name: route.name, back: (route.meta as any)?.back }),
+    () => {
+      // Prefer explicit meta flag if provided; default: show back button when not "home"
+      const isHome =
+        (route.meta as any)?.back === false ||
+        (typeof route.name === 'string' && route.name.startsWith('index'))
+
+      if (isHome) {
+        backButton.hide()
+        offClick()
+      } else if (!backButton.isVisible()) {
+        backButton.show()
+        offClick = backButton.onClick(onBackButtonClick)
+      }
+    },
+    { immediate: true }
+  )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
watch(router.currentRoute, () => {
if (router.currentRoute.value.name === 'index') {
backButton.hide()
offClick()
} else if (!backButton.isVisible()) {
backButton.show()
offClick = backButton.onClick(onBackButtonClick)
}
})
const route = useRoute()
watch(
() => ({ name: route.name, back: (route.meta as any)?.back }),
() => {
// Prefer explicit meta flag if provided; default: show back button when not "home"
const isHome =
(route.meta as any)?.back === false ||
(typeof route.name === 'string' && route.name.startsWith('index'))
if (isHome) {
backButton.hide()
offClick()
} else if (!backButton.isVisible()) {
backButton.show()
offClick = backButton.onClick(onBackButtonClick)
}
},
{ immediate: true }
)
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/composables/useBackButton.ts around lines 8 to 16,
the watcher is missing immediate: true and the strict name === 'index' check is
brittle with locale-suffixed route names; update the watch to run immediately
and determine whether to hide the back button by preferring a route.meta flag
(e.g., route.meta.back === false) or, if meta is not available, using a tolerant
name check (e.g., route.name?.toString().startsWith('index') or
includes('index')). Also ensure offClick is managed correctly when toggling
(remove previous handler before assigning a new one) so you don’t leak handlers.

Comment on lines 83 to 99
if (mountViewport.isAvailable()) {
mountViewport().then(() => {
bindViewportCssVars()

if (requestFullscreen.isAvailable()) {
requestFullscreen().finally(() => {
// Wait
setTimeout(() => {
// The app is now in fullscreen
if (window.innerWidth > 600) {
// Application should be in fullscreen mode only on small screens!
exitFullscreen()
}
}, 50)
})
}
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fullscreen flow can flicker on large screens and may cause unhandled rejections.

  • You call requestFullscreen, then immediately exit on width > 600. This may flash fullscreen on desktops.
  • If requestFullscreen rejects (common without user gesture), the promise is never caught (finally doesn’t handle rejections), leading to an unhandled rejection.

Prefer gating before requesting, and handle errors:

   if (mountViewport.isAvailable()) {
-    mountViewport().then(() => {
-      bindViewportCssVars()
-      if (requestFullscreen.isAvailable()) {
-        requestFullscreen().finally(() => {
-          // Wait
-          setTimeout(() => {
-            // The app is now in fullscreen
-            if (window.innerWidth > 600) {
-              // Application should be in fullscreen mode only on small screens!
-              exitFullscreen()
-            }
-          }, 50)
-        })
-      }
-    })
+    mountViewport().then(async () => {
+      bindViewportCssVars()
+      const smallScreen = window.innerWidth <= 600
+      if (smallScreen && requestFullscreen.isAvailable()) {
+        try {
+          await requestFullscreen()
+        } catch (e) {
+          // ignore: fullscreen not allowed without user gesture, or user denied
+        }
+      } else if (!smallScreen && exitFullscreen?.isAvailable?.()) {
+        // Ensure we don’t remain in fullscreen on large screens
+        try { await exitFullscreen() } catch {}
+      }
+    })
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (mountViewport.isAvailable()) {
mountViewport().then(() => {
bindViewportCssVars()
if (requestFullscreen.isAvailable()) {
requestFullscreen().finally(() => {
// Wait
setTimeout(() => {
// The app is now in fullscreen
if (window.innerWidth > 600) {
// Application should be in fullscreen mode only on small screens!
exitFullscreen()
}
}, 50)
})
}
})
if (mountViewport.isAvailable()) {
mountViewport().then(async () => {
bindViewportCssVars()
const smallScreen = window.innerWidth <= 600
if (smallScreen && requestFullscreen.isAvailable()) {
try {
await requestFullscreen()
} catch (e) {
// ignore: fullscreen not allowed without user gesture, or user denied
}
} else if (!smallScreen && exitFullscreen?.isAvailable?.()) {
// Ensure we don’t remain in fullscreen on large screens
try {
await exitFullscreen()
} catch {}
}
})
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/utils/init.ts around lines 83 to 99, the fullscreen
flow can flicker on large screens and produces unhandled rejections because
requestFullscreen is always invoked then conditionally exited and its rejection
isn't caught; change the logic to check screen width before calling
requestFullscreen (only request on small screens), await or chain the promise
and add a catch to handle rejection, remove the immediate exitFullscreen branch
that runs after a short timeout, and ensure any cleanup still runs in finally if
needed so no rejection is left unhandled and fullscreen is never briefly entered
on large screens.

Comment thread package.json
"version": "",
"private": true,
"packageManager": "pnpm@10.14.0",
"packageManager": "pnpm@10.15.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

pnpm bumped to 10.15.0 — verify availability and CI alignment.

Looks good to pin via packageManager and engines. Please confirm CI/dev containers install/use pnpm 10.15.x and that this version exists on npm.

  1. Check latest pnpm on npm:

  2. Check GitHub Actions/CI to ensure it uses pnpm >=10.15:

Also applies to: 13-13


🏁 Script executed:

#!/bin/bash
# Verify published version of pnpm
npm view pnpm version

Length of output: 246


🏁 Script executed:

#!/bin/bash
# Search workflow files for pnpm setup/install and Node versions
rg -n -C3 -g ".github/workflows/**" "(setup-node|pnpm|corepack)"

Length of output: 215


🏁 Script executed:

#!/bin/bash
# Check for GitHub workflows directory and list files
if [ -d .github/workflows ]; then
  echo "Found GitHub workflows:"
  ls .github/workflows || true
else
  echo "No GitHub workflows directory"
fi

# Check for other CI config files
for file in .gitlab-ci.yml azure-pipelines.yml .circleci/config.yml Jenkinsfile; do
  if [ -f "$file" ]; then echo "Found CI config: $file"; fi
done

# Check for devcontainer configuration
if [ -d .devcontainer ]; then
  echo "Found .devcontainer directory:"
  ls .devcontainer || true
else
  echo "No .devcontainer directory"
fi

# Search throughout the repo for pnpm, corepack, or setup-node mentions
rg -n 'pnpm' -C3 .
rg -n 'corepack' -C3 .
rg -n 'setup-node' -C3 .

Length of output: 8279


🏁 Script executed:

#!/bin/bash
# Print the first 200 lines of the CI workflow to inspect how pnpm is set up
sed -n '1,200p' .github/workflows/ci.yml

Length of output: 1014


Pin pnpm version in CI workflows and Dockerfiles

Verified that pnpm@10.15.0 exists on npm (npm view pnpm version → 10.15.0) and can be installed without error. The CI workflow currently uses pnpm/action-setup@v4 without a version pin (so it will always fetch “latest”), and all Dockerfiles install pnpm globally without specifying a version. To ensure consistent, reproducible builds, please pin pnpm@10.15.0 in both places.

.github/workflows/ci.yml
Under the pnpm/action-setup@v4 step, add version: '10.15.0' alongside run_install: false.
docker/*/Dockerfile (all services)
Change global install from npm install --ignore-scripts -g pnpm to npm install --ignore-scripts -g pnpm@10.15.0.
• No .devcontainer directory found—confirm if a Dev Container should also pin pnpm.

Example diff:

# .github/workflows/ci.yml
-      - name: PNPM
-        uses: pnpm/action-setup@v4
-        with:
-          run_install: false
+      - name: PNPM
+        uses: pnpm/action-setup@v4
+        with:
+          version: '10.15.0'
+          run_install: false

# docker/web-app/Dockerfile (apply to other Dockerfiles similarly)
- RUN npm install --ignore-scripts -g pnpm && \
+ RUN npm install --ignore-scripts -g pnpm@10.15.0 && \
🤖 Prompt for AI Agents
In package.json around line 5, the project pins packageManager to "pnpm@10.15.0"
but CI workflows and Dockerfiles are unpinned; update .github/workflows/ci.yml
to add version: '10.15.0' to the pnpm/action-setup@v4 step (next to run_install:
false) and change all docker/*/Dockerfile occurrences that run npm install
--ignore-scripts -g pnpm to npm install --ignore-scripts -g pnpm@10.15.0; also
check if a .devcontainer exists and, if so, pin pnpm there too.

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