Skip to content

feat: epic pages and cards#102

Merged
hmbanan666 merged 1 commit into
mainfrom
epics
Aug 28, 2025
Merged

feat: epic pages and cards#102
hmbanan666 merged 1 commit into
mainfrom
epics

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Aug 28, 2025

Summary by CodeRabbit

  • New Features

    • Introduced Epics: list and detail pages with titles, descriptions, and comment threads.
    • Create and edit epics via modals.
    • Add and delete your comments; contextual menu with beacon/like/edit/delete (shown per permissions).
    • Epic data now refreshes periodically with other content.
    • Added Russian translations for epic actions.
  • Style

    • New interactive cards with haptic feedback and slide animations.
    • Unified Section container across pages; refined spacing and smaller create card.
    • More accurate navigation active state (exact match for Home, prefix for subpaths).

@hmbanan666 hmbanan666 self-assigned this Aug 28, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 28, 2025

Walkthrough

Adds an Epic feature end-to-end: shared schemas, Pinia store, server APIs (list/create/update, comment create/delete), UI components (cards, comments, forms, modals), and pages for epic list/detail. Integrates epic store updates into app startup/interval loops. Introduces layout components (Section, ActiveCard) and adjusts navigation active matching and minor UI spacings/animations.

Changes

Cohort / File(s) Summary
Epic store & schemas
apps/atrium-telegram/app/stores/epic.ts, apps/atrium-telegram/shared/services/epic.ts
New Pinia store useEpicStore with epics, update/add/remove comment methods; introduces EpicWithData and EpicCommentWithData. Adds arktype schemas/types for create/update epic and create epic comment.
Epic server APIs
apps/atrium-telegram/server/api/epic/list.get.ts, apps/atrium-telegram/server/api/epic/index.post.ts, apps/atrium-telegram/server/api/epic/id/[epicId]/index.patch.ts, apps/atrium-telegram/server/api/epic/id/[epicId]/comment.post.ts, apps/atrium-telegram/server/api/epic/comment/id/[commentId]/index.delete.ts
Adds authenticated endpoints: list epics, create epic (with staff notifications), update epic, create comment, delete comment with ownership check; centralized error handling.
Epic pages
apps/atrium-telegram/app/pages/epic/index.vue, apps/atrium-telegram/app/pages/epic/[epicId]/index.vue, apps/atrium-telegram/app/pages/epic.vue
New epic list and detail pages; list renders epics and opens create modal; detail shows epic info, comments, and edit modal. Removes old placeholder epic page content.
Epic components
apps/atrium-telegram/app/components/EpicCard.vue, apps/atrium-telegram/app/components/EpicComment.vue
New components to render epic cards and individual comments with dropdown actions and optional beacon modal trigger.
Epic forms & modals
apps/atrium-telegram/app/components/form/CreateEpicComment.vue, apps/atrium-telegram/app/components/modal/CreateEpic.vue, apps/atrium-telegram/app/components/modal/UpdateEpic.vue, apps/atrium-telegram/app/components/modal/CreateEpicCommentBeacon.vue
New form for adding epic comments; modals for create/update epic and create comment beacon; close overlays on submit/success.
App init integration
apps/atrium-telegram/app/app.vue
Integrates useEpicStore(); adds epic.update() to initial and 30s interval Promise.all update batches.
Layout primitives & usage
apps/atrium-telegram/app/components/Section.vue, apps/atrium-telegram/app/components/ActiveCard.vue, apps/atrium-telegram/app/components/TaskList.vue, apps/atrium-telegram/app/pages/index.vue, apps/atrium-telegram/app/components/CreateCard.vue, apps/atrium-telegram/app/components/PageContainer.vue
Adds Section and ActiveCard components; replaces wrappers with Section in TaskList and home index; tweaks min height in CreateCard; adds bottom margin in PageContainer.
UI polish
apps/atrium-telegram/app/components/TaskCard.vue, apps/atrium-telegram/app/components/Navigation.vue
Adds slide-left motion classes to dropdown items; navigation active-state supports exact vs prefix match, home marked exact.
Localization
apps/atrium-telegram/i18n/locales/ru-RU.json
Adds ru-RU strings for epic create/update entries.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant App as App (Nuxt)
  participant Store as EpicStore
  participant API as Server API
  participant Repo as Repository

  Note over App,Store: App startup / periodic refresh
  U->>App: Open app
  App->>Store: epic.update()
  Store->>API: GET /api/epic/list (auth)
  API->>Repo: epic.list()
  Repo-->>API: Epics[]
  API-->>Store: Epics with comments/notifications
  Store-->>App: Update state

  Note over U,App: Create Epic via modal
  U->>App: Open Create Epic modal
  U->>API: POST /api/epic (auth, body)
  API->>Repo: epic.create(...)
  Repo-->>API: Epic
  API-->>U: { ok: true, result }
  App->>Store: epic.update()

  Note over U,App: Update Epic via modal
  U->>API: PATCH /api/epic/id/:epicId (auth, body)
  API->>Repo: epic.update(...)
  Repo-->>API: Epic
  API-->>U: { ok: true, result }
  App->>Store: epic.update()
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant App as Epic Detail Page
  participant Store as EpicStore
  participant API as Server API
  participant Repo as Repository

  Note over U,App: Add comment
  U->>App: Submit comment text
  App->>Store: addComment(epicId, text)
  Store->>API: POST /api/epic/id/:epicId/comment
  API->>Repo: epic.createComment(...)
  Repo-->>API: Comment
  API-->>Store: { ok: true }
  Store->>API: GET /api/epic/list
  API-->>Store: Epics[]
  Store-->>App: Refresh detail

  Note over U,App: Delete own comment
  U->>API: DELETE /api/epic/comment/id/:commentId
  API->>Repo: epic.deleteComment(...)
  Repo-->>API: { ok: true }
  API-->>U: { ok: true }
  App->>Store: epic.update()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

In burrows of code where epics grow,
I thump my paw—onward we go!
Cards that shimmer, comments that chime,
Modals pop up, just in time.
With gentle hops through schemas and APIs,
I stash new features—carrots for the eyes. 🥕✨

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 epics

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 @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit 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:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit 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 @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @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

Quality Gate Failed Quality Gate failed

Failed conditions
15.0% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@hmbanan666 hmbanan666 merged commit 4204b8b into main Aug 28, 2025
6 of 8 checks passed
@hmbanan666 hmbanan666 deleted the epics branch August 28, 2025 09:53
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 18

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/app.vue (1)

71-77: Make startup updates resilient to a single store failure (use allSettled).
One rejected promise will currently block the rest and the interval setup.

Apply:

-onMounted(async () => {
-  await Promise.all([
+onMounted(async () => {
+  await Promise.allSettled([
     user.updateOnline(),
     user.update(),
     task.update(),
     epic.update(),
   ])
🧹 Nitpick comments (20)
apps/atrium-telegram/app/components/Navigation.vue (2)

22-23: Reuse the same active helper for label styling.

Prevents duplication and keeps logic consistent with the icon container.

-              (route.exact ? router.currentRoute.value.path === route.path : router.currentRoute.value.path.startsWith(route.path)) && 'tg-text',
+              isActive(route) && 'tg-text',

47-51: i18n and naming consistency.

  • Title 'Эпики' is hardcoded; prefer i18n key (e.g., t('app.epic.list')).
  • name: 'quests' doesn’t match path /epic; consider name: 'epic'.
apps/atrium-telegram/shared/services/epic.ts (2)

5-6: Remove redundant | undefined when also using .optional().

Keeps schemas precise and avoids allowing explicit undefined payloads.

-  description: type('string <= 1500 | undefined').describe('error.length.invalid').optional(),
+  description: type('string <= 1500').describe('error.length.invalid').optional(),

10-12: Same redundancy in update schema.

Make each field optional, but validate only the concrete string values.

-  title: type('2 <= string <= 150 | undefined').describe('error.length.invalid').optional(),
-  description: type('string <= 1500 | undefined').describe('error.length.invalid').optional(),
+  title: type('2 <= string <= 150').describe('error.length.invalid').optional(),
+  description: type('string <= 1500').describe('error.length.invalid').optional(),
apps/atrium-telegram/app/components/modal/CreateEpic.vue (1)

1-7: Close only on success to avoid hiding validation errors.
Closing on submitted can dismiss the modal before an API error is shown.

Apply:

-      <FormCreateEpic @submitted="overlay.closeAll" @success="overlay.closeAll" />
+      <FormCreateEpic @success="overlay.closeAll" />
apps/atrium-telegram/app/components/modal/CreateEpicCommentBeacon.vue (1)

4-8: Mirror create-modal behavior: close on success only.
Prevents accidental dismissal on failures.

Apply:

-        @submitted="overlay.closeAll"
-        @success="overlay.closeAll"
+        @success="overlay.closeAll"
apps/atrium-telegram/app/pages/epic/index.vue (2)

9-15: Reduce network churn when many items by disabling link prefetch.
Useful if epics list grows.

Apply:

-    <NuxtLink
+    <NuxtLink
+      prefetch="false"
       v-for="epic of epicStore.epics"
       :key="epic.id"
       :to="`/epic/${epic.id}`"
     >

23-30: Consider an empty/skeleton state.
Show placeholder when epicStore.epics is empty/loading.

apps/atrium-telegram/server/api/epic/list.get.ts (2)

3-13: Add error handling and minimal logging.
Prevent leaking stack traces and aid observability.

Apply:

-export default defineEventHandler(async (event) => {
+export default defineEventHandler(async (event) => {
   const user = event.context.user
   if (!user) {
     throw createError({
       statusCode: 401,
       message: 'Not logged in',
     })
   }
-
-  return repository.epic.list()
+  try {
+    return await repository.epic.list()
+  } catch (err) {
+    // TODO: plug logger
+    throw createError({ statusCode: 500, message: 'Failed to fetch epics' })
+  }
 })

1-13: Consider ETag/conditional GETs for 30s polling.
Will cut payload and DB load when nothing changed.

apps/atrium-telegram/server/api/epic/id/[epicId]/comment.post.ts (1)

9-13: Clarify 400 message for missing route param.

Use the param name for better debugging.

-      throw createError({
-        statusCode: 400,
-        message: 'Id is required',
-      })
+      throw createError({
+        statusCode: 400,
+        message: 'epicId is required',
+      })
apps/atrium-telegram/app/components/form/CreateEpicComment.vue (1)

10-22: Enforce max length on the client and localize the placeholder.

Keeps UX aligned with server (<=1500) and prepares for i18n.

       <UTextarea
         v-model="text"
         color="neutral"
         variant="none"
         size="xl"
         required
         autoresize
-        placeholder="Напишите свою мысль..."
+        :placeholder="$t('epic.comment.placeholder')"
         :rows="3"
+        :maxlength="1500"
         :disabled="loading"
         class="w-full"
         :ui="{ base: 'p-1' }"
       />
apps/atrium-telegram/server/api/epic/index.post.ts (1)

37-46: Parallelize notifications with Promise.all (or a batch create).

Sends notifications faster and avoids serial IO.

-      for (const staff of allStaffExceptUser) {
-        await repository.notification.create({
-          authorId: user.id,
-          userId: staff.id,
-          epicId: epic.id,
-          type: 'epic_created',
-          title: `${suffixByGender(['Создал', 'Создала'], user.gender)} эпик «${epic.title}»`,
-          description: epic.description ? epic.description : 'Без описания',
-        })
-      }
+      await Promise.all(
+        allStaffExceptUser.map((staff) =>
+          repository.notification.create({
+            authorId: user.id,
+            userId: staff.id,
+            epicId: epic.id,
+            type: 'epic_created',
+            title: `${suffixByGender(['Создал', 'Создала'], user.gender)} эпик «${epic.title}»`,
+            description: epic.description ? epic.description : 'Без описания',
+          }),
+        ),
+      )
apps/atrium-telegram/app/components/EpicCard.vue (1)

15-18: Nit: optional chaining on required prop.

epic is a required prop; epic?.comments.length can be epic.comments.length.

-          <p>{{ epic?.comments.length }}</p>
+          <p>{{ epic.comments.length }}</p>
apps/atrium-telegram/app/pages/epic/[epicId]/index.vue (1)

56-58: Pass the known route param to the modal instead of a possibly-empty epic id.

This removes a race with store loading and avoids sending an empty id.

-function handleEditEpic() {
-  modalUpdateEpic.open({ epicId: epic.value?.id ?? '' })
-}
+function handleEditEpic() {
+  modalUpdateEpic.open({ epicId: params.epicId })
+}
apps/atrium-telegram/app/components/EpicComment.vue (1)

77-77: Pass epicId to the beacon modal.

The modal likely needs both ids; currently only commentId is sent.

-      onSelect: () => modalCreateEpicCommentBeacon.open({ commentId }),
+      onSelect: () => modalCreateEpicCommentBeacon.open({ epicId, commentId }),
apps/atrium-telegram/app/stores/epic.ts (4)

4-10: Export EpicCommentWithData for reuse across components.

This helps type consumers that need comment notifications.

-type EpicCommentWithData = EpicComment & {
+export type EpicCommentWithData = EpicComment & {
   notifications: Notification[]
 }

41-53: Same: guard auth and type body for addComment.

Prevents unauthenticated calls and clarifies payload shape.

   async function addComment(epicId: string, text: string) {
     try {
+      if (!initDataRaw.value) return
       await $fetch(`/api/epic/id/${epicId}/comment`, {
         method: 'POST',
         headers: {
           Authorization: `tma ${initDataRaw.value}`,
         },
         body: {
           text,
         },
       })
 
       await update()

66-75: Same: guard auth for removeComment.

   async function removeComment(id: string) {
     try {
+      if (!initDataRaw.value) return
       await $fetch(`/api/epic/comment/id/${id}`, {
         method: 'DELETE',
         headers: {
           Authorization: `tma ${initDataRaw.value}`,
         },
       })

28-39: Improve error handling with status-based branching.

String-matching on messages is brittle; prefer status codes from ofetch errors.

I can refactor to use isFetchError(e)/(e as FetchError).response?.status and surface toasts.

📜 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 9445dab and 60190d6.

📒 Files selected for processing (26)
  • apps/atrium-telegram/app/app.vue (2 hunks)
  • apps/atrium-telegram/app/components/ActiveCard.vue (1 hunks)
  • apps/atrium-telegram/app/components/CreateCard.vue (1 hunks)
  • apps/atrium-telegram/app/components/EpicCard.vue (1 hunks)
  • apps/atrium-telegram/app/components/EpicComment.vue (1 hunks)
  • apps/atrium-telegram/app/components/Navigation.vue (2 hunks)
  • apps/atrium-telegram/app/components/PageContainer.vue (1 hunks)
  • apps/atrium-telegram/app/components/Section.vue (1 hunks)
  • apps/atrium-telegram/app/components/TaskCard.vue (1 hunks)
  • apps/atrium-telegram/app/components/TaskList.vue (2 hunks)
  • apps/atrium-telegram/app/components/form/CreateEpicComment.vue (1 hunks)
  • apps/atrium-telegram/app/components/modal/CreateEpic.vue (1 hunks)
  • apps/atrium-telegram/app/components/modal/CreateEpicCommentBeacon.vue (1 hunks)
  • apps/atrium-telegram/app/components/modal/UpdateEpic.vue (1 hunks)
  • apps/atrium-telegram/app/pages/epic.vue (0 hunks)
  • apps/atrium-telegram/app/pages/epic/[epicId]/index.vue (1 hunks)
  • apps/atrium-telegram/app/pages/epic/index.vue (1 hunks)
  • apps/atrium-telegram/app/pages/index.vue (1 hunks)
  • apps/atrium-telegram/app/stores/epic.ts (1 hunks)
  • apps/atrium-telegram/i18n/locales/ru-RU.json (2 hunks)
  • apps/atrium-telegram/server/api/epic/comment/id/[commentId]/index.delete.ts (1 hunks)
  • apps/atrium-telegram/server/api/epic/id/[epicId]/comment.post.ts (1 hunks)
  • apps/atrium-telegram/server/api/epic/id/[epicId]/index.patch.ts (1 hunks)
  • apps/atrium-telegram/server/api/epic/index.post.ts (1 hunks)
  • apps/atrium-telegram/server/api/epic/list.get.ts (1 hunks)
  • apps/atrium-telegram/shared/services/epic.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/atrium-telegram/app/pages/epic.vue
🧰 Additional context used
🧬 Code graph analysis (4)
apps/atrium-telegram/server/api/epic/id/[epicId]/comment.post.ts (1)
apps/atrium-telegram/shared/services/epic.ts (1)
  • createEpicCommentSchema (15-17)
apps/atrium-telegram/server/api/epic/id/[epicId]/index.patch.ts (1)
apps/atrium-telegram/shared/services/epic.ts (1)
  • updateEpicSchema (9-12)
apps/atrium-telegram/app/stores/epic.ts (2)
packages/database/src/types.ts (1)
  • EpicComment (168-168)
packages/database/src/tables.ts (1)
  • epics (708-718)
apps/atrium-telegram/server/api/epic/index.post.ts (2)
apps/atrium-telegram/shared/services/epic.ts (1)
  • createEpicSchema (3-6)
packages/database/src/tables.ts (1)
  • users (75-93)
⏰ 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 (16)
apps/atrium-telegram/app/components/PageContainer.vue (1)

3-3: LGTM! Improved bottom spacing for enhanced layout.

The addition of mb-20 provides better spacing at the bottom of the page container, which aligns well with the new Section-based layout components introduced in this PR.

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

1-5: LGTM! Clean and reusable layout component.

This is a well-designed layout component that provides consistent styling across the application. The use of Tailwind CSS classes and the group/section utility for potential child element styling is appropriate. The component follows Vue 3 composition API best practices with a simple slot-based design.

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

2-2: LGTM! Consistent adoption of the new Section component.

The migration from a manual div wrapper to the Section component maintains the same styling while improving consistency across the application. This change aligns perfectly with the new layout pattern established in this PR.

Also applies to: 90-90

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

39-39: LGTM! Enhanced user experience with smooth animations.

The addition of motion-preset-slide-left motion-duration-200 classes to dropdown menu items provides a polished sliding animation that enhances the user interface. The 200ms duration is appropriate for responsive feel without being too slow.

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

36-38: LGTM! Consistent Section component adoption and improved spacing.

The replacement of the manual div wrapper with the Section component maintains consistent styling while following the new layout pattern. The removal of the trailing <div class="mb-20" /> spacer is also appropriate since the PageContainer now handles bottom margin with mb-20.

apps/atrium-telegram/i18n/locales/ru-RU.json (2)

38-41: Add matching keys to other locales or provide fallbacks.

The new create-epic strings look good. Ensure en/other locales define app.create.epic.button|title to avoid mixed-language UI.


58-61: Mirror update-epic keys across locales.

Add app.update.epic.button|title to all supported locales, or confirm fallback strategy.

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

2-2: LGTM: reduced min height aligns with denser card grid.

No functional impact; check that touch targets remain ≥44px on small screens.

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

58-61: Epic store is correctly integrated into the app store bootstrap.
Looks consistent with existing user/task patterns.

apps/atrium-telegram/app/components/modal/CreateEpic.vue (1)

9-11: Confirm component auto-registration.
Assuming FormCreateEpic is auto-registered; otherwise import it.

apps/atrium-telegram/app/components/modal/CreateEpicCommentBeacon.vue (1)

14-16: Validate prop type against backend ID type.
If comment IDs are numeric/UUID, keep as string; otherwise adjust.

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

1-21: Solid page scaffold; list + create affordance looks good.
Clean layout; proper keys on list.

apps/atrium-telegram/server/api/epic/list.get.ts (1)

12-13: Verify tenant scoping to the authenticated user/workspace.
Calling repository.epic.list() without user context may expose unrelated data unless the repo scopes internally.

If scoping is not internal, pass user identifiers (e.g., workspaceId/userId) to the repository.

apps/atrium-telegram/app/pages/epic/[epicId]/index.vue (1)

26-29: Fix comment count check and verify helper availability

  • Change the v-if to v-if="epic?.comments?.length > 0" (instead of >= 0).
  • Ensure pluralizationRu is either explicitly imported in this component or auto-registered (e.g. via your Nuxt/Vite auto-import setup).
apps/atrium-telegram/server/api/epic/id/[epicId]/index.patch.ts (1)

29-34: Response shape LGTM.

Consistent { ok, result } contract.

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

70-105: Type-safe menu items with conditional rendering & fixed disabled logic

Define a local ConditionalMenuItem to include condition, filter and strip it before returning, and enable actions when shown. Update the beacon call to include both epicId and commentId.

 import type { DropdownMenuItem } from '@nuxt/ui'
@@
-type items = computed<DropdownMenuItem[]>(() => {
-  const menuItems: DropdownMenuItem[] = [
+type ConditionalMenuItem = DropdownMenuItem & { condition?: boolean }
+
+const items = computed<DropdownMenuItem[]>(() => {
+  const menuItems: ConditionalMenuItem[] = [
     {
       label: 'Маякнуть',
       icon: 'i-lucide-users-round',
       color: 'neutral',
-      disabled: false,
-      onSelect: () => modalCreateEpicCommentBeacon.open({ commentId }),
+      disabled: false,
+      onSelect: () => modalCreateEpicCommentBeacon.open({ epicId, commentId }),
       condition: true,
     },
     {
       label: 'Поставить лайк',
       icon: 'i-lucide-thumbs-up',
       color: 'neutral',
-      disabled: true,
+      disabled: false, // only disabled when it’s your own comment
       onSelect: () => {},
       condition: user.value?.id !== userStore.id,
     },
     {
       label: 'Редактировать',
       icon: 'i-lucide-edit',
-      disabled: true,
+      disabled: false,
       onSelect: () => {},
       condition: user.value?.id === userStore.id,
     },
     {
       label: 'Удалить',
       icon: 'i-lucide-trash-2',
-      disabled: true,
+      disabled: false,
       onSelect: () => {},
       condition: user.value?.id === userStore.id,
     },
   ]
 
-  return menuItems.filter((item) => item.condition)
+  return menuItems
+    .filter((item) => item.condition !== false)
+    .map(({ condition, ...item }) => item)
 })

Please confirm that userStore.id indeed returns the current authenticated user’s ID. If the store nests the user under another key (e.g. userStore.user.id), adjust the condition accordingly.

Comment on lines 79 to 86
interval = setInterval(async () => {
await Promise.all([
user.updateOnline(),
user.update(),
task.update(),
epic.update(),
])
}, 30000)
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

Harden periodic updates and avoid type pitfall for interval handle.

  • Same resilience issue as above.
  • In browser, prefer ReturnType over NodeJS.Timeout to avoid TS friction.

Apply:

-  interval = setInterval(async () => {
-    await Promise.all([
+  interval = setInterval(async () => {
+    await Promise.allSettled([
       user.updateOnline(),
       user.update(),
       task.update(),
       epic.update(),
     ])
   }, 30000)

Additionally (outside this hunk), change the declaration:

// Before: let interval: NodeJS.Timeout
let interval: ReturnType<typeof setInterval>

🏁 Script executed:

#!/bin/bash
rg -n "let interval" -C3 apps/atrium-telegram/app/app.vue

Length of output: 181


Harden periodic updates and use correct interval type
Replace Promise.all with Promise.allSettled in the interval callback so one failed update doesn’t cancel the others, and change the interval’s declaration from NodeJS.Timeout to the browser-compatible ReturnType<typeof setInterval>.

--- a/apps/atrium-telegram/app/app.vue
@@ -79,7 +79,7 @@ onMounted(async () => {
   interval = setInterval(async () => {
-    await Promise.all([
+    await Promise.allSettled([
       user.updateOnline(),
       user.update(),
       task.update(),
       epic.update(),
     ])
   }, 30000)

And at line 69:

- let interval: NodeJS.Timeout
+ let interval: ReturnType<typeof setInterval>
📝 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
interval = setInterval(async () => {
await Promise.all([
user.updateOnline(),
user.update(),
task.update(),
epic.update(),
])
}, 30000)
// use a browser-compatible timer handle
let interval: ReturnType<typeof setInterval>
onMounted(async () => {
// use allSettled so one rejection doesn’t short-circuit the rest
interval = setInterval(async () => {
await Promise.allSettled([
user.updateOnline(),
user.update(),
task.update(),
epic.update(),
])
}, 30000)
})
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/app.vue around lines 69 and 79-86, the periodic
update uses Promise.all and the interval is typed as NodeJS.Timeout which is not
browser-compatible; change the interval variable's type from NodeJS.Timeout to
ReturnType<typeof setInterval> (at/near line 69) and replace Promise.all with
Promise.allSettled inside the setInterval callback so one rejection doesn't
cancel the other updates; after awaiting Promise.allSettled, optionally inspect
results and log or handle rejected entries to surface failures without breaking
the other updates.

Comment on lines +2 to +6
<div class="relative active:scale-90 duration-200 motion-preset-slide-left" @click="vibrate()">
<Section>
<slot />
</Section>
</div>
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

Make the clickable wrapper accessible (keyboard + ARIA).

The root div is clickable but not keyboard-accessible. Add role/tabindex and handle Enter/Space.

-  <div class="relative active:scale-90 duration-200 motion-preset-slide-left" @click="vibrate()">
+  <div
+    role="button"
+    tabindex="0"
+    class="relative active:scale-90 duration-200 motion-preset-slide-left"
+    @click="vibrate()"
+    @keydown.enter.prevent="vibrate()"
+    @keydown.space.prevent="vibrate()"
+  >
     <Section>
       <slot />
     </Section>
   </div>
📝 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
<div class="relative active:scale-90 duration-200 motion-preset-slide-left" @click="vibrate()">
<Section>
<slot />
</Section>
</div>
<div
role="button"
tabindex="0"
class="relative active:scale-90 duration-200 motion-preset-slide-left"
@click="vibrate()"
@keydown.enter.prevent="vibrate()"
@keydown.space.prevent="vibrate()"
>
<Section>
<slot />
</Section>
</div>
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/ActiveCard.vue around lines 2 to 6, the
root div is clickable but not keyboard-accessible; add accessibility attributes
and keyboard handling by giving the div role="button" and tabindex="0" and
wiring a keydown handler that invokes the same vibrate() method when Enter or
Space are pressed (for Space, prevent default to avoid page scroll); keep the
existing @click handler and ensure any ARIA label/aria-labelledby is provided by
the consumer if necessary.

Comment on lines +21 to +25
<time
:datetime="epic.createdAt"
class="text-sm text-muted"
v-text="format(new Date(epic.createdAt), 'от d MMMM yyyy', { locale: ru })"
/>
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

Escape literal text in date-fns format string.

'o' is a token; without quotes you'll get incorrect output. Wrap "от" in quotes.

-        v-text="format(new Date(epic.createdAt), 'от d MMMM yyyy', { locale: ru })"
+        v-text="format(new Date(epic.createdAt), `'от' d MMMM yyyy`, { locale: ru })"
📝 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
<time
:datetime="epic.createdAt"
class="text-sm text-muted"
v-text="format(new Date(epic.createdAt), 'от d MMMM yyyy', { locale: ru })"
/>
<time
:datetime="epic.createdAt"
class="text-sm text-muted"
v-text="format(
new Date(epic.createdAt),
`'от' d MMMM yyyy`,
{ locale: ru }
)"
/>
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/EpicCard.vue around lines 21 to 25, the
date-fns format string uses unquoted literal text "от" which will be interpreted
as a token; update the format string to escape the literal by wrapping it in
quotes (e.g. "'от' d MMMM yyyy") so the word "от" is rendered literally when
formatting epic.createdAt.

Comment on lines +32 to +33
import { format } from 'date-fns'
import { ru } from 'date-fns/locale/ru'
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

Fix date-fns locale import (named import from locale/ru is invalid).

Use the default export from 'date-fns/locale/ru' to avoid runtime undefined.

-import { format } from 'date-fns'
-import { ru } from 'date-fns/locale/ru'
+import { format } from 'date-fns'
+import ru from 'date-fns/locale/ru'
📝 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 { format } from 'date-fns'
import { ru } from 'date-fns/locale/ru'
import { format } from 'date-fns'
import ru from 'date-fns/locale/ru'
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/EpicCard.vue around lines 32-33, the code
uses a named import for the Russian locale (import { ru } from
'date-fns/locale/ru') which is invalid and yields undefined at runtime; change
it to use the default import (import ru from 'date-fns/locale/ru') and keep
passing ru to format/locale options, ensuring any references to ru remain the
same.

Comment on lines +52 to +54
import { format } from 'date-fns'
import { ru } from 'date-fns/locale/ru'

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

Fix date-fns locale import (named import from locale/ru is invalid).

Same issue as in EpicCard.vue.

-import { format } from 'date-fns'
-import { ru } from 'date-fns/locale/ru'
+import { format } from 'date-fns'
+import ru from 'date-fns/locale/ru'
📝 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 { format } from 'date-fns'
import { ru } from 'date-fns/locale/ru'
import { format } from 'date-fns'
import ru from 'date-fns/locale/ru'
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/EpicComment.vue around lines 52-54 the
locale import from date-fns is wrong: change the named import "import { ru }
from 'date-fns/locale/ru'" to a default import "import ru from
'date-fns/locale/ru'" (same fix used in EpicCard.vue) and ensure any format
calls pass the locale as { locale: ru }.

Comment on lines +15 to +21
const user = event.context.user
if (!user) {
throw createError({
statusCode: 401,
message: 'Not logged in',
})
}
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

Authorization gap: any logged-in user can update any epic.

Enforce ownership (or role-based access) before updating to prevent privilege escalation.

     const user = event.context.user
     if (!user) {
       throw createError({
         statusCode: 401,
         message: 'Not logged in',
       })
     }
+
+    // Ensure only the owner (or authorized roles) can update this epic
+    const existing = await repository.epic.findById(epicId)
+    if (!existing) {
+      throw createError({ statusCode: 404, message: 'Epic not found' })
+    }
+    if (existing.userId !== user.id) {
+      throw createError({ statusCode: 403, message: 'Forbidden' })
+    }

🏁 Script executed:

#!/bin/bash
# Discover available repository methods to use for the ownership check
rg -nP "repository\.epic\." packages apps | sed -n '1,120p'

Length of output: 1719


Enforce epic ownership before update
After verifying the user is logged in, load the epic and check its userId against user.id to prevent any logged-in user from updating another user’s epic:

const existing = await repository.epic.find(epicId)
if (!existing) throw createError({ statusCode: 404, message: 'Epic not found' })
if (existing.userId !== user.id) throw createError({ statusCode: 403, message: 'Forbidden' })
🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/epic/id/[epicId]/index.patch.ts around lines
15 to 21, after confirming event.context.user exists, load the epic with await
repository.epic.find(epicId) (using the route param epicId), throw createError({
statusCode: 404, message: 'Epic not found' }) if not found, then compare
existing.userId !== user.id and if they differ throw createError({ statusCode:
403, message: 'Forbidden' }) to enforce ownership before allowing updates.

Comment on lines +23 to +27
const body = await readBody(event)
const data = updateEpicSchema(body)
if (data instanceof type.errors) {
throw data
}
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

Use arktype assert for clearer validation flow.

Avoid sentinel checks; let assert throw a typed error which your resolver can handle.

-    const body = await readBody(event)
-    const data = updateEpicSchema(body)
-    if (data instanceof type.errors) {
-      throw data
-    }
+    const body = await readBody(event)
+    const data = updateEpicSchema.assert(body)
📝 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
const body = await readBody(event)
const data = updateEpicSchema(body)
if (data instanceof type.errors) {
throw data
}
const body = await readBody(event)
const data = updateEpicSchema.assert(body)
🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/epic/id/[epicId]/index.patch.ts around lines
23 to 27, replace the sentinel-style validation (assigning
updateEpicSchema(body) to data and checking `data instanceof type.errors`) with
arktype's assert pattern: call the schema's assert function directly on the
parsed body so it throws a typed validation error on failure and returns the
validated value on success; remove the `if (data instanceof type.errors) { throw
data }` branch and use the assert-returned value as your `data` for the rest of
the handler.

Comment on lines +35 to +37
} catch (error) {
throw errorResolver(error)
}
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

Missing import for error resolver.

errorResolver is used but not imported; this will throw at runtime.

-import { updateEpicSchema } from '#shared/services/epic'
+import { updateEpicSchema } from '#shared/services/epic'
+import { errorResolver } from '#shared/utils/error' // adjust path if different
📝 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
} catch (error) {
throw errorResolver(error)
}
// apps/atrium-telegram/server/api/epic/id/[epicId]/index.patch.ts
import { NextApiRequest, NextApiResponse } from 'next'
import { withMethods } from '#shared/lib/api-middleware/with-methods'
import { updateEpicSchema } from '#shared/services/epic'
import { errorResolver } from '#shared/utils/error' // adjust path if different
import prisma from '#shared/lib/prisma'
import { revalidate } from '#shared/lib/revalidate'
// …rest of file…
} catch (error) {
throw errorResolver(error)
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/epic/id/[epicId]/index.patch.ts around lines
35–37, errorResolver is thrown but not imported; add the appropriate import at
the top of the file referencing the module that exports errorResolver (use the
correct relative path and match named vs default export), e.g. add an import
statement for errorResolver alongside the other imports and ensure
linter/formatter ordering is respected so the runtime can find and call
errorResolver in the catch block.

Comment on lines +32 to +45
// Notify all staff
if (user.type === 'staff') {
const users = await repository.user.list()
const allStaffExceptUser = users.filter((u) => u.type === 'staff' && u.id !== user.id)

for (const staff of allStaffExceptUser) {
await repository.notification.create({
authorId: user.id,
userId: staff.id,
epicId: epic.id,
type: 'epic_created',
title: `${suffixByGender(['Создал', 'Создала'], user.gender)} эпик «${epic.title}»`,
description: epic.description ? epic.description : 'Без описания',
})
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

Verify suffixByGender availability/import.

It’s used but not imported here. If not globally auto-imported, add an explicit import.

Run to verify declaration/imports:

If it’s not auto-imported, add an import (adjust path to your utilities module):

 import { type } from 'arktype'
+import { suffixByGender } from '#shared/utils/suffixByGender'

🏁 Script executed:

#!/bin/bash
# Find suffixByGender definition and imports across the repo
rg -n -C2 -S '\bexport (const|function)\s+suffixByGender\b|\bsuffixByGender\s*\('

Length of output: 7096


Add missing import of suffixByGender
At the top of apps/atrium-telegram/server/api/epic/index.post.ts, add:

import { suffixByGender } from '../../../shared/utils/helpers'
🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/epic/index.post.ts around lines 32 to 45, the
function suffixByGender is used but not imported; add the import at the top of
the file: import { suffixByGender } from '../../../shared/utils/helpers' and
ensure the import path is correct relative to this file so the suffixByGender
utility is available where used.

Comment on lines +33 to +36
if (user.type === 'staff') {
const users = await repository.user.list()
const allStaffExceptUser = users.filter((u) => u.type === 'staff' && u.id !== user.id)

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

Avoid loading all users; filter in the query layer.

Fetching all users and filtering in memory doesn’t scale. Prefer a repository method that returns only staff and excludes the current user.

I can add a repository.user.listStaffExcept(userId) helper if desired.

🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/epic/index.post.ts around lines 33 to 36,
avoid loading all users and filtering in memory; instead call a repository
method that returns only staff excluding the current user (e.g.
repository.user.listStaffExcept(user.id)). Add or use a
repository.user.listStaffExcept(userId) implementation that performs the filter
at the data/query layer, replace the current repository.user.list() + in-memory
filter with an awaited call to that method, and ensure types/returns match
existing usage (handle empty results and errors as before).

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