Conversation
WalkthroughThis PR introduces a flow item management system across multiple applications. It adds a Pinia store for hub-telegram to fetch and track flow items, implements UI components for displaying flow items and comments, extends the database schema with an iframe field and new flow item types (hub_post, hub_iframe), and provides server API endpoints for retrieving items, recording views, and managing comments. Changes
Sequence DiagramsequenceDiagram
participant User
participant HubApp as Hub Telegram App
participant FlowStore as Flow Store
participant API as API Server
participant DB as Database
User->>HubApp: Load app
HubApp->>FlowStore: useFlowStore()
FlowStore->>API: GET /api/flow/hub-list
API->>DB: listHubItems()
DB-->>API: FlowItemWithData[]
API-->>FlowStore: items
FlowStore->>FlowStore: items = response
User->>HubApp: View flow list on index page
HubApp->>User: Render FlowItemCard components
User->>HubApp: Click flow item
HubApp->>FlowStore: Fetch item from store
FlowStore-->>HubApp: FlowItemWithData
HubApp->>User: Render detail page with comments
rect rgba(100, 200, 150, 0.2)
Note over User,DB: First view tracking
HubApp->>FlowStore: addView(itemId)
FlowStore->>API: POST /api/flow/id/{itemId}/view
API->>DB: createItemView(itemId, userId)
DB-->>API: ok
API-->>FlowStore: { ok: true }
FlowStore->>API: GET /api/flow/hub-list (refresh)
end
User->>HubApp: Type comment
HubApp->>HubApp: Validate with schema
User->>HubApp: Submit comment
HubApp->>API: POST /api/flow/id/{itemId}/comment
API->>DB: createItemComment(itemId, userId, text)
DB-->>API: ok
API-->>HubApp: { ok: true }
HubApp->>FlowStore: update() (refresh)
HubApp->>User: Show new comment in list
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas requiring extra attention:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (12)
apps/hub-telegram/app/components/Section.vue (1)
2-2: Consider using Vue's automatic class merging.The explicit
classprop with manual binding works but may be redundant. Vue 3 automatically mergesclassattributes to the root element by default.You can either:
- Keep the explicit prop if you need custom control over class handling
- Remove the prop definition and let Vue handle class merging automatically
If you choose to remove the explicit prop:
- <div class="relative p-4 tg-bg-section group/section space-y-3.5 rounded-lg" :class="props.class"> + <div class="relative p-4 tg-bg-section group/section space-y-3.5 rounded-lg"> <slot /> </div> </template> <script setup lang="ts"> -const props = defineProps<{ class?: string }>() </script>Also applies to: 8-8
apps/hub-telegram/app/components/Navigation.vue (1)
1-21: Consider accessibility improvements for navigation.The navigation component looks functional, but consider these enhancements:
ARIA labels: Add
role="navigation"andaria-label="Main navigation"to the<nav>element for screen reader users.Grid column logic: The ternary condition
mainRoutes.length > 3 ? 'grid-cols-4' : 'grid-cols-3'might not handle edge cases well (e.g., 1-2 routes). Consider whether the grid should adapt more granularly.Type safety: The script setup doesn't explicitly type the composable returns. Consider adding type annotations.
Apply this diff to improve accessibility:
<nav v-if="isNavigationShown" + role="navigation" + aria-label="Main navigation" class="w-full h-24 tg-bg-bottom-bar border-t border-default rounded-t-lg motion-preset-slide-up" >apps/atrium-telegram/app/components/flow/ItemCard.vue (1)
16-24: Consider localizing the hub indicator text.The hardcoded Russian text "В Хабе" should be moved to the localization file for maintainability and consistency with the rest of the application.
Apply this diff to use localized text:
- <p class="max-w-22 text-sm/4 font-bold"> - В Хабе - </p> + <p class="max-w-22 text-sm/4 font-bold"> + {{ $t('app.hub') }} + </p>And add the localization key to the i18n file.
apps/hub-telegram/app/components/ActiveCard.vue (1)
2-8: Consider adding accessibility attributes and disabled state support.The button lacks accessibility attributes and disabled state handling. Consider:
- Adding an optional
disabledprop to prevent interaction when needed.- Providing a way to pass
aria-labelor other ARIA attributes for better accessibility.- Adding
:disabled="disabled"to the button element.Apply this diff to enhance the component:
<template> <button class="relative w-full active:scale-95 duration-200 text-left cursor-pointer outline-0" - :class="props.class" + :class="[props.class, { 'opacity-50 pointer-events-none': disabled }]" + :disabled="disabled" + :aria-label="ariaLabel" @click="vibrate()" > <slot /> </button> </template> <script setup lang="ts"> -const props = defineProps<{ class?: string }>() +const props = defineProps<{ + class?: string + disabled?: boolean + ariaLabel?: string +}>() const { vibrate } = useFeedback() </script>apps/hub-telegram/app/components/CounterBadge.vue (1)
3-3: Simplify the condition or make it more explicit.The condition
value != null && value !== '0' && value !== 0is checking multiple cases, but the loose equality!=combined with strict inequality checks (!==) may be confusing. Consider:
- Using strict equality throughout for consistency:
value !== null && value !== undefined- Converting to number and checking:
value && Number(value) !== 0- Adding a comment explaining why both string '0' and number 0 need separate checks.
Consider this more explicit approach:
<UBadge - v-if="value != null && value !== '0' && value !== 0" + v-if="value !== null && value !== undefined && value !== '0' && value !== 0" size="lg"Or a more concise approach if you want to treat falsy values uniformly:
<UBadge - v-if="value != null && value !== '0' && value !== 0" + v-if="value && value !== '0' && Number(value) !== 0" size="lg"apps/hub-telegram/app/pages/index.vue (2)
15-29: Add loading and empty states for flow items.The flow items section doesn't handle loading or empty states. Users won't see feedback while items are loading or if there are no items available.
Consider adding loading and empty state handling:
<div class="flex flex-col gap-2.5"> <div class="flex flex-row justify-between items-center"> <SectionTitle :title="$t('app.flow')" /> </div> - <div class="flex flex-col gap-4"> + <div v-if="flowStore.loading" class="flex justify-center p-8"> + <UIcon name="i-lucide-loader-2" class="animate-spin" /> + </div> + <div v-else-if="flowStore.items.length === 0" class="text-center p-8 text-gray-500"> + {{ $t('app.noFlowItems') }} + </div> + <div v-else class="flex flex-col gap-4"> <NuxtLink v-for="item in flowStore.items" :key="item.id"Note: This assumes
flowStorehas aloadingproperty. If not, you may need to add state management for loading.
41-41: Consider extracting avatar fallback logic to a composable or helper.The avatar fallback logic uses a chained ternary with three fallbacks, which could be reused across components. Consider extracting it to a reusable utility.
Create a helper in
shared/utils/helpers.ts:export function getUserAvatar(user: User | null | undefined): string { return user?.initDataState?.user?.photo_url ?? user?.avatarUrl ?? '' }Then use it:
-const userAvatar = computed(() => userStore.initDataState?.user?.photo_url ?? userStore.avatarUrl ?? '') +const userAvatar = computed(() => getUserAvatar(userStore))apps/hub-telegram/app/components/form/CreateFlowItemComment.vue (1)
61-64: Consider handling partial failures in store updates.If one of the store updates fails, the error is caught but the user still gets an error vibration, even though the comment was successfully created. Consider handling this more gracefully.
await Promise.all([ - flowStore.update(), - userStore.update(), + flowStore.update().catch(err => console.warn('Failed to refresh flow store:', err)), + userStore.update().catch(err => console.warn('Failed to refresh user store:', err)), ]) vibrate('success') emit('success')This way, the comment creation success is still communicated to the user even if the store refresh fails.
apps/hub-telegram/app/components/NavigationButton.vue (2)
2-5: Consider extracting the click handler logic to a method.The ternary operator in the click handler is readable but could be clearer as a dedicated method.
Apply this diff to extract the logic:
<button class="flex flex-col items-center justify-center gap-1 px-4 cursor-pointer tg-text-subtitle" - @click="(isMainPage && canScrollToTop && isThisRoute) ? handleScrollToTop() : handleRedirect(route.path)" + @click="handleClick" >Add the method in the script section:
+function handleClick() { + if (isMainPage && canScrollToTop && isThisRoute) { + handleScrollToTop() + } else { + handleRedirect(route.path) + } +} + function handleScrollToTop() { vibrate() window.scrollTo({ top: 0, behavior: 'smooth' }) }
2-46: Add accessibility attributes for better screen reader support.The button lacks semantic attributes to indicate its navigation role and active state to assistive technologies.
Apply this diff to add accessibility attributes:
<button + :aria-label="route.title" + :aria-current="(isThisRoute || isThisName) ? 'page' : undefined" class="flex flex-col items-center justify-center gap-1 px-4 cursor-pointer tg-text-subtitle" @click="(isMainPage && canScrollToTop && isThisRoute) ? handleScrollToTop() : handleRedirect(route.path)" >apps/hub-telegram/app/stores/flow.ts (2)
6-6: Optimize reactivity by extracting user store reference.Calling
useUserStore().iddirectly within the computed callback may impact reactivity tracking and performance. Extract the store reference at the module level.Apply this diff:
+const userStore = useUserStore() + export const useFlowStore = defineStore('flow', () => { const items = ref<FlowItemWithData[]>([]) - const nowViewedItemsCount = computed(() => items.value.filter((item) => !item.views.some((view) => view.userId === useUserStore().id)).length) + const nowViewedItemsCount = computed(() => items.value.filter((item) => !item.views.some((view) => view.userId === userStore.id)).length)
3-7: Consider adding loading states for better UX.The store doesn't expose loading indicators, which would help UI components show loading spinners or disable interactions during API calls.
Apply this diff to add loading states:
export const useFlowStore = defineStore('flow', () => { const items = ref<FlowItemWithData[]>([]) + const isLoading = ref(false) const nowViewedItemsCount = computed(() => items.value.filter((item) => !item.views.some((view) => view.userId === useUserStore().id)).length)Then set
isLoading.value = trueat the start ofupdate()andaddView(), andfalsein the finally block.Expose it in the return statement:
return { items, + isLoading, nowViewedItemsCount,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (26)
apps/atrium-telegram/app/components/flow/ItemCard.vue(4 hunks)apps/atrium-telegram/app/pages/flow/[itemId]/index.vue(2 hunks)apps/atrium-telegram/shared/utils/helpers.ts(2 hunks)apps/hub-telegram/app/app.vue(2 hunks)apps/hub-telegram/app/components/ActiveCard.vue(1 hunks)apps/hub-telegram/app/components/CounterBadge.vue(1 hunks)apps/hub-telegram/app/components/Navigation.vue(1 hunks)apps/hub-telegram/app/components/NavigationButton.vue(1 hunks)apps/hub-telegram/app/components/Section.vue(1 hunks)apps/hub-telegram/app/components/flow/ItemCard.vue(1 hunks)apps/hub-telegram/app/components/flow/ItemComment.vue(1 hunks)apps/hub-telegram/app/components/form/CreateFlowItemComment.vue(1 hunks)apps/hub-telegram/app/composables/useNavigation.ts(1 hunks)apps/hub-telegram/app/layouts/default.vue(1 hunks)apps/hub-telegram/app/pages/flow/[itemId]/index.vue(1 hunks)apps/hub-telegram/app/pages/index.vue(2 hunks)apps/hub-telegram/app/stores/flow.ts(1 hunks)apps/hub-telegram/i18n/locales/ru-RU.json(1 hunks)apps/hub-telegram/server/api/flow/hub-list.get.ts(1 hunks)apps/hub-telegram/server/api/flow/id/[itemId]/comment.post.ts(1 hunks)apps/hub-telegram/server/api/flow/id/[itemId]/view.post.ts(1 hunks)apps/hub-telegram/shared/types/index.ts(2 hunks)apps/hub-telegram/shared/utils/helpers.ts(1 hunks)packages/database/src/repository/flow.ts(1 hunks)packages/database/src/tables.ts(1 hunks)packages/database/src/types/entities.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
apps/hub-telegram/shared/utils/helpers.ts (2)
apps/atrium-telegram/shared/utils/helpers.ts (1)
getIconNameForFlowItem(88-104)apps/hub-telegram/shared/types/index.ts (1)
FlowItemWithData(16-19)
apps/hub-telegram/server/api/flow/hub-list.get.ts (1)
apps/web-app/server/services/db.ts (1)
db(164-164)
apps/hub-telegram/server/api/flow/id/[itemId]/view.post.ts (1)
apps/web-app/server/services/db.ts (1)
db(164-164)
apps/atrium-telegram/shared/utils/helpers.ts (2)
apps/hub-telegram/shared/utils/helpers.ts (1)
getIconNameForFlowItem(3-19)apps/hub-telegram/shared/types/index.ts (1)
FlowItemWithData(16-19)
apps/hub-telegram/shared/types/index.ts (1)
packages/database/src/types/tables.ts (3)
FlowItemComment(169-169)FlowItem(166-166)FlowItemView(172-172)
apps/hub-telegram/app/stores/flow.ts (1)
apps/hub-telegram/shared/types/index.ts (1)
FlowItemWithData(16-19)
apps/hub-telegram/server/api/flow/id/[itemId]/comment.post.ts (2)
packages/schema/src/services/flow.ts (1)
createFlowItemCommentSchema(12-14)apps/web-app/server/services/db.ts (1)
db(164-164)
⏰ 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 (20)
apps/hub-telegram/app/components/Section.vue (1)
1-9: Clean and functional wrapper component.The overall structure is well-organized and appropriate for a simple section wrapper component. The use of Tailwind utilities is consistent, and the slot pattern allows flexible content composition.
packages/database/src/types/entities.ts (1)
38-39: LGTM! Type extension looks good.The addition of 'hub_post' and 'hub_iframe' to the FlowItemType union follows the existing naming convention and integrates well with the related changes across the PR (database schema, repository methods, and UI components).
apps/hub-telegram/i18n/locales/ru-RU.json (1)
24-24: LGTM! Localization addition is appropriate.The Russian translation "Поток" for "flow" is accurate and follows the existing localization structure.
apps/hub-telegram/app/app.vue (2)
62-62: LGTM! Flow store initialization follows existing patterns.The flow store is initialized consistently with the user store pattern.
75-79: No changes needed—error handling is already in place.Both
flow.update()anduser.update()implement identical error handling patterns. Each method wraps its API call in a try-catch block that silently handles errors without rethrowing, soPromise.all()will never reject due to failures in either method. This is intentional and consistent across both store methods.apps/atrium-telegram/shared/utils/helpers.ts (2)
1-1: LGTM! Type import is appropriate.The import of
FlowItemWithDataprovides type safety for the new helper function.
88-104: LGTM! Icon mapping function is well-implemented.The function provides a clean, type-safe way to map flow item types to icons with:
- Exhaustive case coverage for all FlowItemType values
- Sensible default fallback
- Consistent icon naming convention
- Proper fall-through for related types (daily/weekly task reports)
apps/atrium-telegram/app/components/flow/ItemCard.vue (2)
12-12: LGTM! Icon helper usage is correct.The component now uses the shared
getIconNameForFlowItemhelper, promoting consistency across the application.
76-76: LGTM! Hub detection logic is correct.The computed property correctly identifies hub-related flow items by checking for both
hub_iframeandhub_posttypes.packages/database/src/repository/flow.ts (1)
32-50: <!-- [scratchpad_start]
[task verification review_comment_flow.ts continued][observations]
- Full flow.ts now visible; imports from '../tables' which is the schema file location
- Related methods: findItem (no limit), listItems (limit: 500), listHubItems (limit: 100)
- No pagination parameters in any method - all hardcoded limits
- Drizzle ORM is being used
- Need to check tables definition file to see if index on type column exists
[pending]
- Locate and inspect tables definition file (likely packages/database/src/tables.ts or similar)
- Verify if index on type column exists in schema
- Verify pagination hardcoding across methods (it appears intentional but inconsistent: 500 vs 100)
- Determine if concerns in review comment are valid
[actions]
- Find and inspect tables.ts file
- Check flowItems table schema for indexes
- Verify pagination patterns to assess if this is a real concern or expected behavior
packages/database/src/tables.ts (1)
748-748: Verify iframe data flow and apply recommended constraints.The
iframefield on line 748 is defined as an unboundedvarcharwith no constraints. While the public creation schema (createFlowItemSchema) does not currently include this field, the concerns raised are valid:
- No visible validation/sanitization in the codebase for iframe content
- Unbounded storage poses efficiency and security risks if iframe data is ever populated
- Type discrimination shows
'hub_iframe'exists in the system but isn't creatable via standard API flowThe original suggestion to add validation, length constraints, and documentation remains sound. Confirm:
- How iframe data enters the system (internal APIs, admin operations, etc.)
- Whether iframe content needs HTML sanitization or URL validation
- Apply reasonable length constraints and document the expected format
apps/hub-telegram/app/layouts/default.vue (1)
1-26: LGTM! Layout structure is well organized.The layout properly:
- Wraps confetti in
ClientOnlyto avoid SSR issues- Conditionally renders navigation based on user authentication
- Uses ref to dynamically calculate confetti stage dimensions
- Applies appropriate Telegram-specific classes (
tg-text,tg-safe-area)apps/hub-telegram/server/api/flow/hub-list.get.ts (1)
3-5: Error handling is missing; authentication is already handled by middleware.The endpoint lacks try-catch for database errors, but authentication is already applied globally via
apps/hub-telegram/server/middleware/01.auth.ts. Add error handling (without the redundant auth check):export default defineEventHandler(async () => { + try { return db.flow.listHubItems() + } catch (error) { + throw errorResolver(error) + } })Explicit authentication checks in the endpoint are unnecessary—the middleware sets
event.context.userfor all requests and enforces auth before reaching API handlers. Focus on wrapping the database call with try-catch, consistent with other endpoints likeapi/auth/online.post.ts.Likely an incorrect or invalid review comment.
apps/hub-telegram/server/api/flow/id/[itemId]/view.post.ts (1)
30-33: Incorrect review comment - authentication is guaranteed by middleware.The authentication check is already enforced by the middleware (
apps/hub-telegram/server/middleware/01.auth.ts). The middleware runs on all/api/*routes and throws a 401 error if the user is not authenticated, only settingevent.context.userif authentication succeeds. By the time the route handler executes,event.context.useris guaranteed to exist.The referenced comment endpoint (
comment.post.ts) also accessesevent.context.user.idwithout explicit checks on the same endpoint structure, confirming this is the intended architectural pattern. All other endpoints in the application follow the same approach.Adding the suggested explicit check would be redundant and inconsistent with the codebase's established pattern of relying on middleware for authentication guarantees.
Likely an incorrect or invalid review comment.
apps/hub-telegram/server/api/flow/id/[itemId]/comment.post.ts (1)
31-35: The review comment is incorrect—authentication is already enforced by middleware.The middleware at
apps/hub-telegram/server/middleware/01.auth.tsprotects all/api/**routes. It throws a 401 error if the user is not authenticated, and only setsevent.context.userupon successful authentication. Since Nitro middleware always runs before the route handler,event.context.user.idis guaranteed to be defined and safe to access incomment.post.tsandview.post.ts. The suggested authentication check is redundant because the framework's middleware pattern already provides this guarantee.Likely an incorrect or invalid review comment.
apps/hub-telegram/shared/utils/helpers.ts (1)
3-18: Icon mapping keeps hub items aligned.This mirrors the atrium helper and adds
hub_iframe, so all flow types render consistent icons across apps. Nicely done.apps/hub-telegram/shared/types/index.ts (1)
12-19: Typed payload matches UI expectations.Extending
FlowItemwith comment/view payloads gives the components the structure they expect without extra casting. Looks good.apps/hub-telegram/app/components/NavigationButton.vue (2)
56-57: Ensureroute.namesis always defined as an array.Line 57 accesses
route.names.includes()without a null check. Verify that theNavigationRoutetype guaranteesnamesis always a non-null array to prevent runtime errors.The script from the previous comment will also help verify the structure of the
NavigationRoutetype, specifically whethernamesis guaranteed to be an array.
50-50: NavigationRoute type is properly accessible via Nuxt's auto-import configuration.The
NavigationRoutetype is used without explicit imports in bothNavigationButton.vueanduseNavigation.tscomposable, indicating it's auto-imported by Nuxt. This is configured through the parent config (@roll-stack/uiextended innuxt.config), which provides type auto-import capabilities. No action is required.apps/hub-telegram/app/stores/flow.ts (1)
10-16: Verify thatinitDataRawis always defined before making API calls.The Authorization header uses
initDataRaw.valuewithout checking if it's defined. If the TMA SDK hasn't initialized, this could send invalid headers.Consider adding a guard:
async function update() { + if (!initDataRaw.value) { + console.warn('TMA init data not available') + return + } + try { const data = await $fetch('/api/flow/hub-list', {Apply the same check in
addView()at line 34.
| </h3> | ||
|
|
||
| <div class="w-full text-base/5 font-normal whitespace-pre-wrap break-words line-clamp-8"> | ||
| <div class="w-full text-base/5 font-normal whitespace-pre-wrap wrap-break-word line-clamp-8"> |
There was a problem hiding this comment.
🧩 Analysis chain
Verify the custom CSS class.
The class name changed from the standard Tailwind class break-words to wrap-break-word. Ensure that wrap-break-word is defined in your custom CSS, as it's not a standard Tailwind utility.
🏁 Script executed:
#!/bin/bash
# Search for wrap-break-word class definition in CSS files
rg -nP 'wrap-break-word' --type=css --type=vue --type=scssLength of output: 27
I'll generate a corrected shell script to search for the wrap-break-word class definition with proper file type handling.
---
🏁 Script executed:
```shell
#!/bin/bash
# Search for wrap-break-word class definition without type restrictions
# Check CSS and SCSS files
rg -nP 'wrap-break-word' --type=css --type=scss
# Also search in all files to catch definitions in Vue files, global configs, etc.
echo "---"
rg -nP 'wrap-break-word' .
Length of output: 2418
I need to check where this class is actually defined. Let me search for it in the component's style block and look for global CSS configuration files.
---
🏁 Script executed:
```shell
#!/bin/bash
# Check the ItemCard.vue file content to see if it has a style block defining wrap-break-word
cat ./apps/atrium-telegram/app/components/flow/ItemCard.vue
echo "---"
# Search for CSS class definitions (look for dot followed by class name or similar patterns)
rg -nP '\bwrap-break-word\s*[{:]' .
echo "---"
# Look for Tailwind config files
fd -name 'tailwind.config.*' .
echo "---"
# Search in CSS files for any @apply or similar that might define this
rg -n '@apply.*wrap-break-word' .
echo "---"
# Look for global CSS or styles directory
fd -name '*.css' . | head -20
Length of output: 2855
Let me fix the syntax errors and search for Tailwind configuration and global CSS files:
---
🏁 Script executed:
```shell
#!/bin/bash
# Find tailwind config files
echo "=== Tailwind config files ==="
fd tailwind.config .
echo "=== Checking app root for style files ==="
ls -la ./apps/atrium-telegram/app/ | grep -i style
echo "=== Searching in nuxt.config for Tailwind config ==="
find ./apps/atrium-telegram -name 'nuxt.config.*' -o -name 'tailwind.config.*'
echo "=== Looking for app.vue ==="
find ./apps/atrium-telegram -name 'app.vue'
echo "=== Search for any CSS that defines wrap-break-word ==="
rg 'wrap-break-word' --type=css .
echo "=== Search for wrap-break-word in config files ==="
rg 'wrap-break-word' . -g '*.config.*' -g '*.json'
Length of output: 332
Let me check the Nuxt config and app.vue files for the class definition:
---
🏁 Script executed:
```shell
#!/bin/bash
# Check nuxt.config.ts for Tailwind configuration
echo "=== nuxt.config.ts content ==="
cat ./apps/atrium-telegram/nuxt.config.ts
echo ""
echo "=== app.vue content ==="
cat ./apps/atrium-telegram/app/app.vue
echo ""
echo "=== Search for wrap-break-word anywhere in config or app files ==="
rg 'wrap-break-word' ./apps/atrium-telegram/nuxt.config.ts ./apps/atrium-telegram/app/app.vue
Length of output: 3697
Now I found the global CSS file reference. Let me check the styles.css file:
---
🏁 Script executed:
```shell
#!/bin/bash
# Check the global styles.css file
echo "=== styles.css content ==="
cat ./apps/atrium-telegram/app/assets/css/styles.css
echo ""
echo "=== Check if wrap-break-word is defined ==="
rg 'wrap-break-word' ./apps/atrium-telegram/app/assets/css/
echo ""
echo "=== Look for other CSS files in assets ==="
find ./apps/atrium-telegram/app/assets -name '*.css' -o -name '*.scss'
Length of output: 3605
Add wrap-break-word class definition to your custom CSS.
The class wrap-break-word is used in 15+ locations across the codebase (ItemCard.vue:41, TaskInfoCard.vue, PartnerAgreementCard.vue, KitchenCard.vue, Card.vue, and several page files) but is not defined anywhere. It's not in your global styles.css, component style blocks, or Tailwind config. Either define this class in your CSS or revert to the standard Tailwind break-words class.
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/flow/ItemCard.vue around line 41, the
class wrap-break-word is used but not defined anywhere; either add a CSS
definition for .wrap-break-word in a global stylesheet (or component scoped
style) that maps to the expected behavior (e.g., breaking long words and
preserving wrapping), or replace all usages with the standard Tailwind utility
break-words (or break-all if stricter breaking is required); update the 15+
occurrences across the codebase consistently and run a quick visual check to
confirm line-wrapping behaves as intended.
| <iframe | ||
| v-if="item?.iframe" | ||
| :src="item.iframe" | ||
| width="100%" | ||
| height="240" | ||
| style="background-color: #000" | ||
| allow="autoplay; encrypted-media; fullscreen; screen-wake-lock;" | ||
| frameborder="0" | ||
| allowfullscreen | ||
| class="rounded-lg" | ||
| /> |
There was a problem hiding this comment.
Block potential XSS via unchecked iframe src.
Line 13 binds item.iframe directly into the iframe src. If that field ever contains javascript:/data: or an attacker-controlled URL, the iframe executes in our origin, which is a stored XSS vector. Please normalize the value to trusted protocols or whitelist hosts before rendering.
- <iframe
- v-if="item?.iframe"
- :src="item.iframe"
+ <iframe
+ v-if="iframeSrc"
+ :src="iframeSrc" const item = computed(() => flowStore.items.find((item) => item.id === params.itemId))
+const iframeSrc = computed(() => {
+ const src = item.value?.iframe
+ if (!src)
+ return undefined
+ try {
+ const parsed = new URL(src, 'https://example.invalid')
+ return parsed.protocol === 'https:' ? src : undefined
+ }
+ catch {
+ return undefined
+ }
+})
+
const isViewed = computed(() => item.value?.views.some((view) => view.userId === userStore?.id))📝 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.
| <iframe | |
| v-if="item?.iframe" | |
| :src="item.iframe" | |
| width="100%" | |
| height="240" | |
| style="background-color: #000" | |
| allow="autoplay; encrypted-media; fullscreen; screen-wake-lock;" | |
| frameborder="0" | |
| allowfullscreen | |
| class="rounded-lg" | |
| /> | |
| <iframe | |
| v-if="iframeSrc" | |
| :src="iframeSrc" | |
| width="100%" | |
| height="240" | |
| style="background-color: #000" | |
| allow="autoplay; encrypted-media; fullscreen; screen-wake-lock;" | |
| frameborder="0" | |
| allowfullscreen | |
| class="rounded-lg" | |
| /> |
| <iframe | |
| v-if="item?.iframe" | |
| :src="item.iframe" | |
| width="100%" | |
| height="240" | |
| style="background-color: #000" | |
| allow="autoplay; encrypted-media; fullscreen; screen-wake-lock;" | |
| frameborder="0" | |
| allowfullscreen | |
| class="rounded-lg" | |
| /> | |
| const item = computed(() => flowStore.items.find((item) => item.id === params.itemId)) | |
| const iframeSrc = computed(() => { | |
| const src = item.value?.iframe | |
| if (!src) | |
| return undefined | |
| try { | |
| const parsed = new URL(src, 'https://example.invalid') | |
| return parsed.protocol === 'https:' ? src : undefined | |
| } | |
| catch { | |
| return undefined | |
| } | |
| }) | |
| const isViewed = computed(() => item.value?.views.some((view) => view.userId === userStore?.id)) |
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/pages/flow/[itemId]/index.vue around lines 11 to 21,
the iframe src is bound directly to item.iframe which allows javascript:, data:,
or attacker-controlled URLs leading to stored XSS; validate and sanitize the
value before rendering by parsing the URL and only allowing safe protocols
(https: and optionally http:) and an explicit whitelist of trusted hostnames
(reject or null-out any url with protocol javascript:, data:, blob:, or an
unrecognized host), and if the value is not allowed do not render the iframe or
render a safe fallback; additionally add a restrictive iframe sandbox attribute
(e.g., sandbox="allow-scripts" avoided unless needed, prefer no
allow-same-origin/allow-top-navigation) and ensure allowfullscreen/allow
attributes are removed or minimized so that even if an attacker supplies a URL
it cannot execute in our origin.
| <script setup lang="ts"> | ||
| import { getIconNameForFlowItem } from '#shared/utils/helpers' | ||
| import { format } from 'date-fns' | ||
| import { ru } from 'date-fns/locale/ru' | ||
|
|
||
| const { item } = defineProps<{ | ||
| item: FlowItemWithData | ||
| }>() | ||
|
|
||
| const userStore = useUserStore() | ||
| const isViewed = computed(() => item.views.some((view) => view.userId === userStore?.id)) | ||
| </script> |
There was a problem hiding this comment.
Import FlowItemWithData so the file type-checks.
Line 53 references FlowItemWithData, but the type isn’t imported, so vue-tsc will fail. Please add the missing type-only import.
<script setup lang="ts">
-import { getIconNameForFlowItem } from '#shared/utils/helpers'
+import type { FlowItemWithData } from '#shared/types'
+import { getIconNameForFlowItem } from '#shared/utils/helpers'📝 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.
| <script setup lang="ts"> | |
| import { getIconNameForFlowItem } from '#shared/utils/helpers' | |
| import { format } from 'date-fns' | |
| import { ru } from 'date-fns/locale/ru' | |
| const { item } = defineProps<{ | |
| item: FlowItemWithData | |
| }>() | |
| const userStore = useUserStore() | |
| const isViewed = computed(() => item.views.some((view) => view.userId === userStore?.id)) | |
| </script> | |
| <script setup lang="ts"> | |
| import type { FlowItemWithData } from '#shared/types' | |
| import { getIconNameForFlowItem } from '#shared/utils/helpers' | |
| import { format } from 'date-fns' | |
| import { ru } from 'date-fns/locale/ru' | |
| const { item } = defineProps<{ | |
| item: FlowItemWithData | |
| }>() | |
| const userStore = useUserStore() | |
| const isViewed = computed(() => item.views.some((view) => view.userId === userStore?.id)) | |
| </script> |
🤖 Prompt for AI Agents
In apps/hub-telegram/app/components/flow/ItemCard.vue around lines 47 to 58, the
file references FlowItemWithData in the defineProps type but never imports it;
add a type-only import at the top of the script section (for example: import
type { FlowItemWithData } from '#shared/types') so TypeScript/vue-tsc can
resolve the type without pulling runtime code.
| async function onSubmit(event: FormSubmitEvent<CreateFlowItemComment>) { | ||
| emit('submitted') | ||
|
|
||
| try { | ||
| await $fetch(`/api/flow/id/${itemId}/comment`, { | ||
| method: 'POST', | ||
| headers: { | ||
| Authorization: `tma ${userStore.initDataRaw}`, | ||
| }, | ||
| body: event.data, | ||
| }) | ||
|
|
||
| await Promise.all([ | ||
| flowStore.update(), | ||
| userStore.update(), | ||
| ]) | ||
|
|
||
| vibrate('success') | ||
| emit('success') | ||
| } catch (error) { | ||
| console.error(error) | ||
| vibrate('error') | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add loading state to prevent double submissions and improve UX.
The form doesn't track submission state, which can lead to:
- Double submissions if the user clicks submit multiple times
- No visual feedback during the API call
- The text field not being cleared after successful submission
Apply this diff to add loading state and clear the form:
const state = ref<Partial<CreateFlowItemComment>>({
text: undefined,
})
+const isSubmitting = ref(false)
async function onSubmit(event: FormSubmitEvent<CreateFlowItemComment>) {
+ if (isSubmitting.value) return
+
emit('submitted')
+ isSubmitting.value = true
try {
await $fetch(`/api/flow/id/${itemId}/comment`, {
method: 'POST',
headers: {
Authorization: `tma ${userStore.initDataRaw}`,
},
body: event.data,
})
await Promise.all([
flowStore.update(),
userStore.update(),
])
+ state.value.text = undefined
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
vibrate('error')
+ } finally {
+ isSubmitting.value = false
}
}Then update the button:
<UButton
type="submit"
variant="solid"
color="secondary"
size="xl"
icon="i-lucide-send"
block
class="mt-3"
- :disabled="!state.text"
+ :disabled="!state.text || isSubmitting"
+ :loading="isSubmitting"
:label="$t('common.send')"
/>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/hub-telegram/app/components/form/CreateFlowItemComment.vue around lines
49 to 72, the submit handler lacks a loading state and does not clear the form,
allowing double submissions and no visual feedback; add a reactive loading
boolean (set true at start of submit and false in finally), early-return if
loading to prevent re-entry, disable the submit button via that loading state
and show a loading indicator on the button, clear the form model (or call the
form reset) after a successful POST, and ensure vibrate/error handling and
emit('success') happen only after success while keeping emit('submitted') at
submit start.
| @@ -0,0 +1,9 @@ | |||
| <template> | |||
| <div class="relative p-4 tg-bg-section group/section space-y-3.5 rounded-lg" :class="props.class"> | |||
There was a problem hiding this comment.
🧩 Analysis chain
Verify the group/section utility is being used.
The group/section class suggests hover interactions with child elements, but it's unclear from this component alone whether slotted content actually uses the corresponding group-hover/section:* modifiers. If unused, it can be removed.
Run the following script to check if the group variant is referenced in the codebase:
🏁 Script executed:
#!/bin/bash
# Description: Check if group-hover/section or similar modifiers are used
# Search for group-hover/section usage in Vue files
rg -n --type=vue 'group-hover/section' apps/hub-telegram/Length of output: 27
🏁 Script executed:
#!/bin/bash
# Description: Check if group-hover/section modifiers are used in the codebase
# Search for group-hover/section in all files (not restricting to vue type)
rg -n 'group-hover/section' apps/hub-telegram/
echo "---"
# Also search more broadly for group-hover with /section variant
rg -n 'group-hover.*section' apps/hub-telegram/
echo "---"
# Check where Section.vue component is imported/used
rg -n 'Section\.vue|<Section' apps/hub-telegram/ -A 3 -B 1Length of output: 3738
🏁 Script executed:
#!/bin/bash
# View the full content of Section.vue to check for any internal group-hover usage
cat apps/hub-telegram/app/components/Section.vueLength of output: 229
Remove the unused group/section utility class.
The group/section class in Section.vue enables group-hover/section:* modifiers for child elements, but a comprehensive search of the codebase confirms no such modifiers are used anywhere. The class can be safely removed.
- <div class="relative p-4 tg-bg-section group/section space-y-3.5 rounded-lg" :class="props.class">
+ <div class="relative p-4 tg-bg-section space-y-3.5 rounded-lg" :class="props.class">📝 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.
| <div class="relative p-4 tg-bg-section group/section space-y-3.5 rounded-lg" :class="props.class"> | |
| <div class="relative p-4 tg-bg-section space-y-3.5 rounded-lg" :class="props.class"> |
🤖 Prompt for AI Agents
In apps/hub-telegram/app/components/Section.vue around line 2, remove the unused
Tailwind utility class "group/section" from the root div's class list; replace
the class attribute so it no longer includes "group/section" while preserving
the other classes and the dynamic :class binding, then run a quick grep for
"group-hover/section" to confirm no usages remain.
| <iframe | ||
| v-if="item?.iframe" | ||
| :src="item.iframe" | ||
| width="100%" | ||
| height="240" | ||
| style="background-color: #000" | ||
| allow="autoplay; encrypted-media; fullscreen; screen-wake-lock;" | ||
| frameborder="0" | ||
| allowfullscreen | ||
| class="rounded-lg" | ||
| /> |
There was a problem hiding this comment.
Guard the iframe src against unsafe protocols.
Line 13 pipes item.iframe straight into the iframe src. An attacker who stores javascript: (or a hostile URL) there gains script execution in the hub app. Please validate/whitelist the URL before rendering.
- <iframe
- v-if="item?.iframe"
- :src="item.iframe"
+ <iframe
+ v-if="iframeSrc"
+ :src="iframeSrc" const item = computed(() => flowStore.items.find((item) => item.id === params.itemId))
+const iframeSrc = computed(() => {
+ const src = item.value?.iframe
+ if (!src)
+ return undefined
+ try {
+ const parsed = new URL(src, 'https://example.invalid')
+ return parsed.protocol === 'https:' ? src : undefined
+ }
+ catch {
+ return undefined
+ }
+})
+
const isViewed = computed(() => item.value?.views.some((view) => view.userId === userStore?.id))📝 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.
| <iframe | |
| v-if="item?.iframe" | |
| :src="item.iframe" | |
| width="100%" | |
| height="240" | |
| style="background-color: #000" | |
| allow="autoplay; encrypted-media; fullscreen; screen-wake-lock;" | |
| frameborder="0" | |
| allowfullscreen | |
| class="rounded-lg" | |
| /> | |
| <iframe | |
| v-if="iframeSrc" | |
| :src="iframeSrc" | |
| width="100%" | |
| height="240" | |
| style="background-color: #000" | |
| allow="autoplay; encrypted-media; fullscreen; screen-wake-lock;" | |
| frameborder="0" | |
| allowfullscreen | |
| class="rounded-lg" | |
| /> |
| <iframe | |
| v-if="item?.iframe" | |
| :src="item.iframe" | |
| width="100%" | |
| height="240" | |
| style="background-color: #000" | |
| allow="autoplay; encrypted-media; fullscreen; screen-wake-lock;" | |
| frameborder="0" | |
| allowfullscreen | |
| class="rounded-lg" | |
| /> | |
| const iframeSrc = computed(() => { | |
| const src = item.value?.iframe | |
| if (!src) | |
| return undefined | |
| try { | |
| const parsed = new URL(src, 'https://example.invalid') | |
| return parsed.protocol === 'https:' ? src : undefined | |
| } | |
| catch { | |
| return undefined | |
| } | |
| }) |
🤖 Prompt for AI Agents
In apps/hub-telegram/app/pages/flow/[itemId]/index.vue around lines 11 to 21,
the iframe src is assigned directly from item.iframe which allows unsafe
protocols like javascript:; validate and whitelist the URL before rendering by
parsing item.iframe with the URL constructor (or a safe URL parser), ensure the
protocol is only http: or https: (or allowlist specific hosts/origins), and only
then bind it to src; additionally, harden the iframe by adding a restrictive
sandbox attribute (e.g., sandbox="allow-scripts" only if necessary) and
remove/limit permissive allow values to minimize exposure.
| import { initData, useSignal } from '@tma.js/sdk-vue' | ||
|
|
||
| export const useFlowStore = defineStore('flow', () => { | ||
| const items = ref<FlowItemWithData[]>([]) |
There was a problem hiding this comment.
🧩 Analysis chain
Verify that FlowItemWithData type is properly imported.
The type is used but not explicitly imported. Ensure it's auto-imported from the shared types module.
Run this script to verify the type is accessible:
🏁 Script executed:
#!/bin/bash
# Description: Verify FlowItemWithData type definition and imports
# Find type definition
echo "=== Type Definition ==="
rg -n --type=ts -A5 'type FlowItemWithData|interface FlowItemWithData'
# Check auto-imports configuration
echo -e "\n=== Auto-imports Config ==="
fd 'nuxt.config|auto-imports' --exec cat {}Length of output: 10241
🏁 Script executed:
#!/bin/bash
# Check imports in hub-telegram flow.ts and auto-imports setup
echo "=== Full flow.ts file (hub-telegram) ==="
cat apps/hub-telegram/app/stores/flow.ts | head -30
echo -e "\n=== Looking for auto-imports.d.ts ==="
fd 'auto-imports.d.ts' --type f
echo -e "\n=== Checking for .nuxtrc files ==="
fd '.nuxtrc' --type f
echo -e "\n=== Hub-telegram nuxt.config content ==="
cat apps/hub-telegram/nuxt.config.tsLength of output: 291
🏁 Script executed:
#!/bin/bash
# Search for flow.ts files and check hub-telegram structure
echo "=== All flow.ts files in repository ==="
fd 'flow.ts' --type f
echo -e "\n=== Hub-telegram directory structure ==="
fd . apps/hub-telegram -type d | head -20
echo -e "\n=== Files in apps/hub-telegram/app if it exists ==="
fd . apps/hub-telegram/app 2>/dev/null | head -20 || echo "Directory does not exist"Length of output: 1426
🏁 Script executed:
#!/bin/bash
# Check the actual imports and first 40 lines of flow.ts
echo "=== First 40 lines of apps/hub-telegram/app/stores/flow.ts ==="
head -40 apps/hub-telegram/app/stores/flow.ts
echo -e "\n=== Search for FlowItemWithData import or reference ==="
rg -n 'FlowItemWithData|from.*types' apps/hub-telegram/app/stores/flow.ts -A2 -B2Length of output: 1417
Add missing import for FlowItemWithData type.
The type is used on line 4 but not imported. Add:
import { FlowItemWithData } from '../shared/types'🤖 Prompt for AI Agents
In apps/hub-telegram/app/stores/flow.ts around line 4 the FlowItemWithData type
is used but not imported; add the missing import statement: import {
FlowItemWithData } from '../shared/types' at the top of the file so the type
reference resolves.
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| if (error.message.includes('401')) { | ||
| // No | ||
| } | ||
| if (error.message.includes('404')) { | ||
| // Not found | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Implement proper error handling instead of empty catch blocks.
The error handling is inadequate:
- Empty catch blocks provide no feedback or logging
- Checking
error.message.includes('401')is fragile - useerror.statusCodeinstead - Users have no indication when the fetch fails
Apply this diff to improve error handling:
} catch (error) {
- if (error instanceof Error) {
- if (error.message.includes('401')) {
- // No
- }
- if (error.message.includes('404')) {
- // Not found
- }
+ if (error && typeof error === 'object' && 'statusCode' in error) {
+ const statusCode = (error as { statusCode?: number }).statusCode
+ if (statusCode === 401) {
+ console.error('Unauthorized: Invalid TMA credentials')
+ } else if (statusCode === 404) {
+ console.error('Flow items endpoint not found')
+ } else {
+ console.error('Failed to fetch flow items:', error)
+ }
+ } else {
+ console.error('Failed to fetch flow items:', error)
}
}📝 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.
| } catch (error) { | |
| if (error instanceof Error) { | |
| if (error.message.includes('401')) { | |
| // No | |
| } | |
| if (error.message.includes('404')) { | |
| // Not found | |
| } | |
| } | |
| } | |
| } catch (error) { | |
| if (error && typeof error === 'object' && 'statusCode' in error) { | |
| const statusCode = (error as { statusCode?: number }).statusCode | |
| if (statusCode === 401) { | |
| console.error('Unauthorized: Invalid TMA credentials') | |
| } else if (statusCode === 404) { | |
| console.error('Flow items endpoint not found') | |
| } else { | |
| console.error('Failed to fetch flow items:', error) | |
| } | |
| } else { | |
| console.error('Failed to fetch flow items:', error) | |
| } | |
| } |
🤖 Prompt for AI Agents
In apps/hub-telegram/app/stores/flow.ts around lines 22 to 31, the catch block
currently swallows errors and uses fragile message.includes checks; replace it
with concrete error handling: detect HTTP errors by checking a status code
property (e.g. error.statusCode or error.response?.status) instead of
error.message, log the full error (message and stack) to your logger, map 401
and 404 to explicit handling paths (auth failure and not-found) and for other
errors emit or return a user-visible error (throw a new Error or set an
observable/error state) so callers/users are notified of failures; ensure
non-HTTP errors are also logged and propagated.
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| if (error.message.includes('401')) { | ||
| // No | ||
| } | ||
| if (error.message.includes('404')) { | ||
| // Not found | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Implement proper error handling (same issues as update()).
This function has the same error handling problems as the update() function reviewed above:
- Empty catch blocks with no feedback
- Fragile error detection via
message.includes() - No logging or user notification
Apply similar improvements as suggested for the update() function.
🤖 Prompt for AI Agents
In apps/hub-telegram/app/stores/flow.ts around lines 44 to 53, the catch block
silently swallows errors and uses fragile string-matching on error.message;
replace this with robust error handling: detect HTTP/axios errors by checking
error.response?.status (or the library-specific status property) instead of
message.includes, log the full error and context via the module logger, and
either throw a wrapped error with a clear message or return a rejected
Promise/meaningful result so callers can react; also handle non-HTTP errors with
a generic log+rethrow path and include user-friendly messages for 401/404 cases
rather than empty branches.
| if (item.views.some((view) => view.userId === event.context.user.id)) { | ||
| throw createError({ | ||
| statusCode: 400, | ||
| message: 'Already viewed', | ||
| }) | ||
| } |
There was a problem hiding this comment.
Potential N+1 performance issue: fetching all views to check user view.
The code loads the entire item with all views (Line 16) just to check if the current user has already viewed it (Line 23). This is inefficient, especially as the number of views grows.
Consider adding a dedicated method to check view existence:
// In database layer
hasUserViewedItem(itemId: string, userId: string): Promise<boolean>Then refactor the handler:
- const item = await db.flow.findItem(itemId)
- if (!item) {
+ const [item, hasViewed] = await Promise.all([
+ db.flow.findItem(itemId),
+ db.flow.hasUserViewedItem(itemId, event.context.user.id),
+ ])
+
+ if (!item) {
throw createError({
statusCode: 404,
message: 'Item not found',
})
}
- if (item.views.some((view) => view.userId === event.context.user.id)) {
+ if (hasViewed) {
throw createError({This avoids loading all view records just to check for existence.
🤖 Prompt for AI Agents
In apps/hub-telegram/server/api/flow/id/[itemId]/view.post.ts around lines 23 to
28, avoid loading the whole item.views array to check if the current user
already viewed the item; add a database-layer method such as
hasUserViewedItem(itemId: string, userId: string): Promise<boolean> that
performs an efficient existence check (e.g., SELECT 1 WHERE item_id = ? AND
user_id = ? LIMIT 1) and replace the item.views.some(...) check with a call to
that method, preserving the current behavior of throwing createError({
statusCode: 400, message: 'Already viewed' }) when true.




Summary by CodeRabbit