Repository: titicacadev/triple-frontend
Pull request #3628: [DMTALK-112] 새로운 메시지 UI 추가
Author: login: dia-triple
Labels: name: tds-widget
Created: 2025-04-01T01:06:28Z | Merged: 2025-04-03T06:31:05Z

<!-- 이 PR을 요약한 내용으로 위 제목 폼을 채워 주세요. -->

## PR 설명

<!-- PR의 목적, PR이 구현하는 기획이나 디자인(figma, slack or jira) 등 리뷰어가 참고할 내용을 적어주세요. -->

- 새로운 메시지 UI를 추가합니다.
  - 새로운 메시지 UI 컴포넌트 추가 `packages/tds-widget/src/chat/scroll-buttons-area`
  - input 창 resize에 따라 스크롤에 반영합니다. `packages/tds-widget/src/chat/input-area/nol-input-area-ui/use-input-resize-observer.ts`


## 변경 내역

<!-- 실제 변경이 발생한 부분을 위주로 서술해주세요. -->
<!-- 필요하다면 코드 레벨의 설명도 곁들일 수 있습니다. -->
<!-- 리뷰어가 변경점에 대해 빠르게 이해를 할 수 있도록 서술해주세요. -->

## 체크리스트

<!-- 프로젝트별로 반드시 확인해야 하는 항목을 나열해주세요. -->
<!-- 각 항목을 읽어 보시고, 해당하는 항목의 주석을 해제해주세요. -->
<!-- 조금이라도 명확하지 않은 부분이 있다면 슬랙 #triple-web-dev 채널로 질문해주세요! -->
<!-- - [x] 주요 동선의 통합 테스트를 진행하셨나요? -->
<!-- - [x] 기획자/디자이너에게 확인을 받았나요? 혹은 확인이 필요없는 이슈인가요? -->

## 스크린샷 & URL

<!-- 이 변경과 관련있는 스크린샷을 첨부해 주세요. -->
<!-- 반드시 필요한 게 아니라면 생략 가능합니다. -->
<!-- 변경 사항을 확인할 수 있는 샘플 URL을 알려주세요. 바로 동작하는 링크일수록 좋습니다. -->



Diff:
diff --git a/.github/workflows/add-label.yaml b/.github/workflows/add-label.yaml
index a25bc42431..55cdf1c2b4 100644
--- a/.github/workflows/add-label.yaml
+++ b/.github/workflows/add-label.yaml
@@ -7,7 +7,7 @@ on:
 jobs:
   label-pr:
     if: ${{ github.event.pull_request.draft == false && github.event.pull_request.user.login != 'triple-bot' && !contains(github.event.pull_request.labels.*.name, 'release') }}
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4
diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml
index 928ea6f3ce..1596b11a6a 100644
--- a/.github/workflows/cd.yaml
+++ b/.github/workflows/cd.yaml
@@ -16,24 +16,12 @@ env:
   NPM_REGISTRY_URL: 'https://registry.npmjs.org'
   NODE_AUTH_TOKEN: ${{ secrets.READ_ONLY_NPM_TOKEN }}
   HUSKY: 0
-  # Slack notifications
-  SLACK_WEBHOOK: ${{ secrets.GHA_NOTIFICATIONS_WEBHOOK_URL }}
-  SLACK_CHANNEL: '#triple-web-dev-notifications' # 메시지 보낼 채널
-  SLACK_USERNAME: 'Triple Frontend' # 메시지를 보내는 계정 이름
-  SLACK_ICON_EMOJI: ':triple_new:'
-  SLACK_DETAIL_URL: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
-  SLACK_GITHUB_REPOSITORY: ${{ github.repository }}
-  SLACK_AUTHOR_NAME: ${{ github.event.sender.login }}
-  SLACK_AUTHOR_ICON: ${{ github.event.sender.avatar_url }}
-  SLACK_FOOTER: ${{ github.repository }}
-  SLACK_GITHUB_REF: ${{ github.event.ref }} # 메시지에 ref로 표시되는 값
-  SLACK_GITHUB_EVENT_NAME: ${{ github.event_name }} # 메시지에 Event로 표시되는 값
   # Nx Cloud
   NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
 
 jobs:
   wait-for-ci:
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - uses: actions/checkout@v4
@@ -56,22 +44,6 @@ jobs:
           token: ${{ secrets.GITHUB_TOKEN }}
           checkName: build
 
-      - name: Notify checking CI timeout
-        if: steps.wait-for-ci.outputs.conclusion == 'timed_out'
-        env:
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: CI Waiting timeout'
-          SLACK_TOPIC: 'CI workflow가 시작되었는지, 기다리는 job의 이름이 CI job과 일치하는지, 확인해주세요.'
-        run: pnpm dlx @titicaca/gha-tools notify
-
-      - name: Notify checking CI failure
-        if: steps.wait-for-ci.outputs.conclusion == 'failure'
-        env:
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: CI FAILURE'
-          SLACK_TOPIC: '배포를 중단합니다.'
-        run: pnpm dlx @titicaca/gha-tools notify
-
       - name: Shutdown workflow
         if: steps.wait-for-ci.outputs.conclusion != 'success'
         run: node -e 'process.exit(1)'
@@ -79,7 +51,7 @@ jobs:
   release:
     needs: wait-for-ci
     if: startsWith(github.event.ref, 'refs/tags/release-prod-')
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - uses: actions/checkout@v4
@@ -98,13 +70,6 @@ jobs:
       - name: Get release version
         run: echo "DEPLOY_VERSION=v$(cat ./lerna.json | jq -r '.version')" >> $GITHUB_ENV
 
-      - name: Notify deploy start to slack
-        env:
-          SLACK_COLOR: gray
-          SLACK_TITLE: ':rocket: Release WORKING'
-          SLACK_TOPIC: ${{ env.DEPLOY_VERSION }}
-        run: pnpm dlx @titicaca/gha-tools notify
-
       - name: Install dependencies
         env:
           NODE_AUTH_TOKEN: ${{ secrets.READ_ONLY_NPM_TOKEN }}
@@ -117,26 +82,10 @@ jobs:
           NODE_AUTH_TOKEN: ${{ secrets.READ_WRITE_NPM_TOKEN }}
         run: pnpm -r publish --no-git-checks
 
-      - name: Notify deploy success to slack
-        if: success()
-        env:
-          SLACK_COLOR: success
-          SLACK_TITLE: ':tada: Release SUCCESS'
-          SLACK_TOPIC: ${{ env.DEPLOY_VERSION }}
-        run: pnpm dlx @titicaca/gha-tools notify
-
-      - name: Notify deploy fail to slack
-        if: failure()
-        env:
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: Release FAILURE'
-          SLACK_TOPIC: ${{ env.DEPLOY_VERSION }}
-        run: pnpm dlx @titicaca/gha-tools notify
-
   tag:
     needs: release
     if: success()
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - uses: actions/checkout@v4
@@ -198,7 +147,7 @@ jobs:
   canary-release:
     needs: wait-for-ci
     if: startsWith(github.event.ref, 'refs/tags/release-pr-')
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - uses: actions/checkout@v4
@@ -229,13 +178,6 @@ jobs:
           echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
           echo "DEPLOY_VERSION=$NEXT_VERSION-pr-$PR_NUMBER.$REF_COUNT" >> $GITHUB_ENV
 
-      - name: Notify release start to Slack
-        env:
-          SLACK_COLOR: gray
-          SLACK_TITLE: ':rocket: Release WORKING'
-          SLACK_TOPIC: ${{ env.DEPLOY_VERSION }}
-        run: pnpm dlx @titicaca/gha-tools notify
-
       - name: Install dependencies
         env:
           NODE_AUTH_TOKEN: ${{ secrets.READ_ONLY_NPM_TOKEN }}
@@ -265,19 +207,3 @@ jobs:
             -H "Content-Type: application/json" \
             -f --request POST \
             -d "{\"body\":\"${{ env.DEPLOY_VERSION }} has been published!\"}"
-
-      - name: Notify release success to Slack
-        if: success()
-        env:
-          SLACK_COLOR: success
-          SLACK_TITLE: ':tada: Release SUCCESS'
-          SLACK_TOPIC: ${{ env.DEPLOY_VERSION }}
-        run: pnpm dlx @titicaca/gha-tools notify
-
-      - name: Notify release failure to Slack
-        if: failure()
-        env:
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: Release FAILURE'
-          SLACK_TOPIC: ${{ env.DEPLOY_VERSION }}
-        run: pnpm dlx @titicaca/gha-tools notify
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 6edb46be9f..42e47f82cc 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -13,24 +13,12 @@ env:
   NPM_REGISTRY_URL: 'https://registry.npmjs.org'
   NODE_AUTH_TOKEN: ${{ secrets.READ_ONLY_NPM_TOKEN }}
   HUSKY: 0
-  # Slack Notifications
-  SLACK_WEBHOOK: ${{ secrets.GHA_NOTIFICATIONS_WEBHOOK_URL }}
-  SLACK_CHANNEL: '#triple-web-dev-notifications' # 메시지 보낼 채널
-  SLACK_USERNAME: 'Triple Frontend' # 메시지를 보내는 계정 이름
-  SLACK_ICON_EMOJI: ':triple_new:'
-  SLACK_DETAIL_URL: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
-  SLACK_GITHUB_REPOSITORY: ${{ github.repository }}
-  SLACK_AUTHOR_NAME: ${{ github.event.sender.login }}
-  SLACK_AUTHOR_ICON: ${{ github.event.sender.avatar_url }}
-  SLACK_FOOTER: ${{ github.repository }}
-  SLACK_GITHUB_REF: ${{ github.event.ref }} # 메시지에 ref로 표시되는 값
-  SLACK_GITHUB_EVENT_NAME: ${{ github.event_name }} # 메시지에 Event로 표시되는 값
   # Nx Cloud
   NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
 
 jobs:
   check-dependencies:
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - uses: actions/checkout@v4
@@ -56,16 +44,8 @@ jobs:
             exit 1
           fi
 
-      - name: Notify checking dependency failure
-        if: failure() && steps.check-working-tree-clean.outcome == 'failure'
-        env:
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: Deps check FAILURE'
-          SLACK_TOPIC: '의존성 변경이 있습니다. `pnpm run sync-deps`로 변경을 반영해주세요.'
-        run: pnpm dlx @titicaca/gha-tools notify
-
   lint:
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - uses: actions/checkout@v4
@@ -87,16 +67,8 @@ jobs:
         id: lint
         run: pnpm run lint
 
-      - name: Notify linting failure
-        if: failure() && steps.lint.outcome == 'failure'
-        env:
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: Lint FAILURE'
-          SLACK_TOPIC: ''
-        run: pnpm dlx @titicaca/gha-tools notify
-
   build:
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - uses: actions/checkout@v4
@@ -118,16 +90,8 @@ jobs:
         id: build
         run: pnpm run build
 
-      - name: Notify building failure
-        if: failure() && steps.build.outcome == 'failure'
-        env:
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: Build FAILURE'
-          SLACK_TOPIC: ''
-        run: pnpm dlx @titicaca/gha-tools notify
-
   test:
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - uses: actions/checkout@v4
@@ -149,14 +113,6 @@ jobs:
         id: test
         run: pnpm run test:coverage -- --ci --reporters github-actions --reporters summary --maxWorkers 2
 
-      - name: Notify testing failure
-        if: failure() && steps.test.outcome == 'failure'
-        env:
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: Test FAILURE'
-          SLACK_TOPIC: ''
-        run: pnpm dlx @titicaca/gha-tools notify
-
       - name: Upload coverage to Codecov
         id: upload-coverage-to-codecov
         uses: codecov/codecov-action@v3
@@ -164,16 +120,8 @@ jobs:
           token: ${{ secrets.CODECOV_TOKEN }}
           directory: ./coverage
 
-      - name: Notify uploading coverage failure
-        if: failure() && steps.upload-coverage-to-codecov.outcome == 'failure'
-        env:
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: Upload coverage FAILURE'
-          SLACK_TOPIC: ''
-        run: pnpm dlx @titicaca/gha-tools notify
-
   chromatic-deployment:
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - uses: actions/checkout@v4
@@ -202,14 +150,6 @@ jobs:
           projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
           zip: true
 
-      - name: Notify publishing to Chromatic failure
-        if: failure() && steps.publish-to-chromatic.outcome == 'failure'
-        env:
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: Publish to Chromatic FAILURE'
-          SLACK_TOPIC: ''
-        run: pnpm dlx @titicaca/gha-tools notify
-
       - name: Publish to Chromatic and auto accept changes
         if: github.ref == 'refs/heads/main'
         uses: chromaui/action@latest
diff --git a/.github/workflows/pr-closed.yaml b/.github/workflows/pr-closed.yaml
index 8b23a479d8..bd0e0a5142 100644
--- a/.github/workflows/pr-closed.yaml
+++ b/.github/workflows/pr-closed.yaml
@@ -11,7 +11,7 @@ env:
 
 jobs:
   delete-release-tag:
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - name: Check tag ref exist
diff --git a/.github/workflows/tag-with-comment.yaml b/.github/workflows/tag-with-comment.yaml
index d6a2e35eab..95857ae2c8 100644
--- a/.github/workflows/tag-with-comment.yaml
+++ b/.github/workflows/tag-with-comment.yaml
@@ -11,23 +11,11 @@ env:
   NODE_VERSION: 'lts/*'
   PNPM_VERSION: '9'
   NPM_REGISTRY_URL: 'https://registry.npmjs.org'
-  # Slack Notifications
-  SLACK_WEBHOOK: ${{ secrets.GHA_NOTIFICATIONS_WEBHOOK_URL }}
-  SLACK_CHANNEL: '#triple-web-dev-notifications' # 메시지 보낼 채널
-  SLACK_USERNAME: 'Triple Frontend' # 메시지를 보내는 계정 이름
-  SLACK_ICON_EMOJI: ':triple_new:'
-  SLACK_DETAIL_URL: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
-  SLACK_GITHUB_REPOSITORY: ${{ github.repository }}
-  SLACK_AUTHOR_NAME: ${{ github.event.sender.login }}
-  SLACK_AUTHOR_ICON: ${{ github.event.sender.avatar_url }}
-  SLACK_FOOTER: ${{ github.repository }}
-  SLACK_GITHUB_REF: ${{ github.event.ref }} # 메시지에 ref로 표시되는 값
-  SLACK_GITHUB_EVENT_NAME: ${{ github.event_name }} # 메시지에 Event로 표시되는 값
 
 jobs:
   tag-with-comment:
     if: github.event.issue.state == 'open' && github.event.issue.pull_request && endsWith(github.event.comment.body, 'release-canary')
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
 
     steps:
       - uses: pnpm/action-setup@v3
@@ -116,21 +104,3 @@ jobs:
             -H 'Authorization: token ${{ secrets.TRIPLE_BOT_GITHUB_TOKEN }}' \
             -H 'Content-Type: application/json' \
             -d "{\"force\":true,\"sha\":\"${TAG_SHA}\"}"
-
-      - name: Notify tagging success to Slack
-        if: success()
-        env:
-          NODE_AUTH_TOKEN: ${{ secrets.READ_ONLY_NPM_TOKEN }}
-          SLACK_COLOR: success
-          SLACK_TITLE: ':label: Release tagging SUCCESS'
-          SLACK_TOPIC: '${{ github.event.sender.login }} tagged ${{ env.PR_REF }} as ${{ env.TAG_NAME }}.'
-        run: pnpm dlx @titicaca/gha-tools notify
-
-      - name: Notify tagging fail to Slack
-        if: failure()
-        env:
-          NODE_AUTH_TOKEN: ${{ secrets.READ_ONLY_NPM_TOKEN }}
-          SLACK_COLOR: fail
-          SLACK_TITLE: ':pleading: Release tagging FAILURE'
-          SLACK_TOPIC: 'Fail to tag ${{ env.PR_REF }} as ${{ env.TAG_NAME }}.'
-        run: pnpm dlx @titicaca/gha-tools notify
diff --git a/.github/workflows/update-changelog.yaml b/.github/workflows/update-changelog.yaml
index 6ab3956fcf..9e0f399ec9 100644
--- a/.github/workflows/update-changelog.yaml
+++ b/.github/workflows/update-changelog.yaml
@@ -15,7 +15,7 @@ env:
 
 jobs:
   update-changelog:
-    runs-on: [self-hosted, linux, x64]
+    runs-on: ${{ vars.NOL_RUNNER }}
     if: ${{ github.event.label.name == 'release' }}
     steps:
       - name: Check if a milestone exists on PR
diff --git a/packages/tds-widget/src/chat/chat/chat-room-messages/use-chat-room-messages.ts b/packages/tds-widget/src/chat/chat/chat-room-messages/use-chat-room-messages.ts
index da5e0ae7ce..1463e44237 100644
--- a/packages/tds-widget/src/chat/chat/chat-room-messages/use-chat-room-messages.ts
+++ b/packages/tds-widget/src/chat/chat/chat-room-messages/use-chat-room-messages.ts
@@ -117,7 +117,9 @@ export function useChatMessages<T = UserType>(
         action: MessagesActions.PENDING,
         message: tempMessage,
       })
-      triggerScrollToBottom()
+      setTimeout(() => {
+        triggerScrollToBottom()
+      }, 100)
     }
 
     try {
diff --git a/packages/tds-widget/src/chat/icons/arrow-bottom-16-icon.tsx b/packages/tds-widget/src/chat/icons/arrow-bottom-16-icon.tsx
new file mode 100644
index 0000000000..a2e7002787
--- /dev/null
+++ b/packages/tds-widget/src/chat/icons/arrow-bottom-16-icon.tsx
@@ -0,0 +1,25 @@
+export function ArrowBottom16Icon({
+  color = '#545457',
+  ...props
+}: {
+  color?: string
+}) {
+  return (
+    <svg
+      width="16"
+      height="16"
+      viewBox="0 0 16 16"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      {...props}
+    >
+      <path
+        d="M14 5.07011L8.03526 11L2 5"
+        stroke={color}
+        strokeWidth="1.5"
+        strokeLinecap="round"
+        strokeLinejoin="round"
+      />
+    </svg>
+  )
+}
diff --git a/packages/tds-widget/src/chat/index.ts b/packages/tds-widget/src/chat/index.ts
index d28d67229c..2d96f657d8 100644
--- a/packages/tds-widget/src/chat/index.ts
+++ b/packages/tds-widget/src/chat/index.ts
@@ -10,3 +10,4 @@ export * from './reservation-info'
 export * from './expired'
 export * from './preview'
 export * from './list'
+export * from './scroll-buttons-area'
diff --git a/packages/tds-widget/src/chat/input-area/index.tsx b/packages/tds-widget/src/chat/input-area/index.tsx
index 3eb5e3a6d8..3fb455f63e 100644
--- a/packages/tds-widget/src/chat/input-area/index.tsx
+++ b/packages/tds-widget/src/chat/input-area/index.tsx
@@ -4,3 +4,4 @@ export {
   NolInputAreaUI,
   INPUT_AREA_HEIGHT as NOL_INPUT_AREA_HEIGHT,
 } from './nol-input-area-ui'
+export * from './nol-input-area-ui/use-input-resize-observer'
diff --git a/packages/tds-widget/src/chat/input-area/nol-input-area-ui/index.tsx b/packages/tds-widget/src/chat/input-area/nol-input-area-ui/index.tsx
index 8d97834059..a0073c26f5 100644
--- a/packages/tds-widget/src/chat/input-area/nol-input-area-ui/index.tsx
+++ b/packages/tds-widget/src/chat/input-area/nol-input-area-ui/index.tsx
@@ -1,5 +1,5 @@
 import { Container } from '@titicaca/tds-ui'
-import { ChangeEvent, useRef } from 'react'
+import { ChangeEvent, ForwardedRef, forwardRef, useRef } from 'react'
 import styled from 'styled-components'
 
 import SelectPhotoIcon from '../../icons/select-photo-icon'
@@ -101,22 +101,25 @@ export interface NolInputAreaUIProps
   activeButtonColor?: string
 }
 
-export function NolInputAreaUI({
-  buttonDisabled = false,
-  inputValue,
-  setInputValue,
-  placeholder,
-  onImageUpload,
-  onSendMessage,
-  onInputClick,
-  onInputKeydown,
-  maxTextLength = MAX_TEXT_LENGTH,
-  multipleImageUpload = false,
-  color,
-  placeholderColor,
-  activeButtonColor,
-  ...props
-}: NolInputAreaUIProps) {
+function NolInputAreaUIImpl(
+  {
+    buttonDisabled = false,
+    inputValue,
+    setInputValue,
+    placeholder,
+    onImageUpload,
+    onSendMessage,
+    onInputClick,
+    onInputKeydown,
+    maxTextLength = MAX_TEXT_LENGTH,
+    multipleImageUpload = false,
+    color,
+    placeholderColor,
+    activeButtonColor,
+    ...props
+  }: NolInputAreaUIProps,
+  ref: ForwardedRef<HTMLDivElement>,
+) {
   const textareaRef = useRef<HTMLTextAreaElement>(null)
 
   const onTextAreaChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
@@ -125,7 +128,7 @@ export function NolInputAreaUI({
   }
 
   return (
-    <InputAreaContainer {...props}>
+    <InputAreaContainer {...props} ref={ref}>
       <UploadImageButtonWrapper>
         <UploadImageButton htmlFor="image_upload">
           <SelectPhotoIcon />
@@ -169,3 +172,5 @@ export function NolInputAreaUI({
     </InputAreaContainer>
   )
 }
+
+export const NolInputAreaUI = forwardRef(NolInputAreaUIImpl)
diff --git a/packages/tds-widget/src/chat/input-area/nol-input-area-ui/use-input-resize-observer.ts b/packages/tds-widget/src/chat/input-area/nol-input-area-ui/use-input-resize-observer.ts
new file mode 100644
index 0000000000..72ce6bc5d3
--- /dev/null
+++ b/packages/tds-widget/src/chat/input-area/nol-input-area-ui/use-input-resize-observer.ts
@@ -0,0 +1,54 @@
+import { useState, useEffect, useRef } from 'react'
+
+import { useScroll } from '../../chat'
+
+export function useInputResizeObserver(defaultHeight: number) {
+  const { setScrollBy, chatContainerRef } = useScroll()
+  const inputContainerRef = useRef<HTMLDivElement | null>(null)
+
+  const inputContainerHeightRef = useRef<number>(defaultHeight)
+  const [inputContainerHeight, setInputContainerHeight] =
+    useState<number>(defaultHeight)
+
+  useEffect(() => {
+    inputContainerHeightRef.current = inputContainerHeight
+  }, [inputContainerHeight])
+
+  useEffect(() => {
+    const inputContainer = inputContainerRef?.current
+
+    if (!inputContainer) {
+      return
+    }
+
+    const observer = new ResizeObserver(() => {
+      const diff = inputContainer.clientHeight - inputContainerHeightRef.current
+
+      const chatContainer = chatContainerRef?.current
+
+      if (
+        diff > 0 ||
+        !(
+          chatContainer &&
+          chatContainer.scrollTop + chatContainer.clientHeight ===
+            chatContainer.scrollHeight
+        )
+      ) {
+        setScrollBy(diff)
+      }
+      setInputContainerHeight(inputContainer.clientHeight)
+    })
+
+    observer.observe(inputContainer)
+
+    return () => {
+      observer.disconnect()
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [])
+
+  return {
+    inputContainerRef,
+    inputContainerHeight,
+  }
+}
diff --git a/packages/tds-widget/src/chat/messages/index.tsx b/packages/tds-widget/src/chat/messages/index.tsx
index d4edbdbd55..3507fed3e2 100644
--- a/packages/tds-widget/src/chat/messages/index.tsx
+++ b/packages/tds-widget/src/chat/messages/index.tsx
@@ -31,6 +31,7 @@ interface MessagesProp<
   onMessageIntersecting?: (
     entry: IntersectionObserverEntry,
     id: MessageInterface<Message, User>['id'],
+    createdMessage: boolean,
   ) => void
   calculateUnreadCount?: (
     message: MessageInterface<Message, User>,
@@ -227,7 +228,8 @@ export default function Messages<
           <IntersectionObserver
             onChange={
               onMessageIntersecting
-                ? (_inView, entry) => onMessageIntersecting(entry, id)
+                ? (_inView, entry) =>
+                    onMessageIntersecting(entry, id, !!createdAt)
                 : undefined
             }
           >
diff --git a/packages/tds-widget/src/chat/scroll-buttons-area/elements.tsx b/packages/tds-widget/src/chat/scroll-buttons-area/elements.tsx
new file mode 100644
index 0000000000..6e547b7bc5
--- /dev/null
+++ b/packages/tds-widget/src/chat/scroll-buttons-area/elements.tsx
@@ -0,0 +1,154 @@
+import { useEffect, useState } from 'react'
+import styled from 'styled-components'
+import { Container as BaseContainer, Text } from '@titicaca/tds-ui'
+
+import { ArrowBottom16Icon } from '../icons/arrow-bottom-16-icon'
+import { ChatMessageInterface, UserType } from '../types'
+import { getTextMessage } from '../preview'
+
+import { scrollButtonsTheme } from './theme-provider'
+
+export const Container = styled.div`
+  position: relative;
+`
+
+const ButtonContainer = styled(BaseContainer)`
+  position: absolute;
+  right: 16px;
+  top: -52px;
+
+  & > button {
+    background-color: ${({ theme }) =>
+      (theme.scrollButtons || scrollButtonsTheme).scrollToBottomButton
+        .backgroundColor};
+    border-radius: 50%;
+    border: 1px solid
+      ${({ theme }) =>
+        (theme.scrollButtons || scrollButtonsTheme).scrollToBottomButton
+          .borderColor};
+    width: 40px;
+    height: 40px;
+  }
+`
+
+export function ScrollToBottomButton({
+  onClick,
+  delay = 200,
+  ...props
+}: {
+  onClick: (behavior: ScrollBehavior) => void
+  delay?: number
+}) {
+  const [isVisible, setIsVisible] = useState(false)
+
+  useEffect(() => {
+    setTimeout(() => {
+      setIsVisible(true)
+    }, delay)
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [])
+
+  if (isVisible) {
+    return (
+      <ButtonContainer {...props}>
+        <button onClick={() => onClick('instant')} type="button">
+          <ArrowBottom16Icon />
+        </button>
+      </ButtonContainer>
+    )
+  }
+}
+
+const NewMessageButton = styled.button.attrs({ type: 'button' })`
+  padding: 8px 12px;
+  background-color: ${({ theme }) =>
+    (theme.scrollButtons || scrollButtonsTheme).newMessageButton
+      .backgroundColor};
+  border-radius: 8px;
+  border: 1px solid
+    ${({ theme }) =>
+      (theme.scrollButtons || scrollButtonsTheme).newMessageButton.borderColor};
+  box-shadow: 0 4px 20px 0
+    ${({ theme }) =>
+      (theme.scrollButtons || scrollButtonsTheme).newMessageButton
+        .boxShadowColor};
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 6px;
+`
+
+const Thumbnail = styled.img`
+  width: 24px;
+  height: 24px;
+  border-radius: 50%;
+  box-shadow: 0 0 0 1px
+    ${({ theme }) =>
+      (theme.scrollButtons || scrollButtonsTheme).newMessageButton
+        .thumbnailBorderColor};
+  object-fit: cover;
+  flex-shrink: 0;
+`
+
+const Name = styled(Text).attrs({
+  maxLines: 1,
+  bold: true,
+  size: 12,
+  lineHeight: '16px',
+  ellipsis: true,
+})`
+  max-width: 70px;
+  color: ${({ theme }) =>
+    (theme.scrollButtons || scrollButtonsTheme).newMessageButton.nameColor};
+  flex-shrink: 0;
+  overflow: hidden;
+`
+
+const Message = styled(Text).attrs({
+  maxLines: 1,
+  size: 12,
+  lineHeight: '16px',
+  ellipsis: true,
+})`
+  color: ${({ theme }) =>
+    (theme.scrollButtons || scrollButtonsTheme).newMessageButton.messageColor};
+  overflow: hidden;
+  flex-grow: 1;
+`
+
+const NewMessageContainer = styled(BaseContainer)`
+  position: absolute;
+  top: 100px;
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  padding: 0 16px;
+  transition: all 200ms ease-out;
+
+  &:has(${NewMessageButton}) {
+    top: -48px;
+  }
+`
+
+export function NewMessage<T = UserType>({
+  message,
+  onClick,
+  ...props
+}: {
+  message: ChatMessageInterface<T> | null
+  onClick: () => void
+}) {
+  return (
+    <NewMessageContainer {...props}>
+      {message && (
+        <NewMessageButton onClick={onClick}>
+          {message.sender.profile.thumbnail ? (
+            <Thumbnail src={message.sender.profile.thumbnail} />
+          ) : null}
+          <Name>{message.sender.profile.name}</Name>
+          <Message>{getTextMessage(message.payload)}</Message>
+        </NewMessageButton>
+      )}
+    </NewMessageContainer>
+  )
+}
diff --git a/packages/tds-widget/src/chat/scroll-buttons-area/index.tsx b/packages/tds-widget/src/chat/scroll-buttons-area/index.tsx
new file mode 100644
index 0000000000..5d64ed01d8
--- /dev/null
+++ b/packages/tds-widget/src/chat/scroll-buttons-area/index.tsx
@@ -0,0 +1,113 @@
+import {
+  PropsWithChildren,
+  useEffect,
+  useRef,
+  useState,
+  useImperativeHandle,
+  forwardRef,
+  ForwardedRef,
+} from 'react'
+import { Container } from '@titicaca/tds-ui'
+
+import { ChatMessageInterface, UserType } from '../types'
+import { useScroll } from '../chat'
+
+import { ScrollButtons, type ScrollButtonsProps } from './scroll-buttons'
+
+interface InspectionConfig {
+  id?: number
+  isIntersecting: boolean
+}
+
+interface ScrollButtonsAreaProps<T = UserType>
+  extends Pick<ScrollButtonsProps<T>, 'scrollButtonsStyle'> {
+  lastSeenMessageId?: number
+  lastMessage?: ChatMessageInterface<T>
+}
+
+export interface ScrollButtonsAreaHandler {
+  onNewOthersMessage: () => void
+  setBottomIntersecting: (data: InspectionConfig) => void
+}
+
+/**
+ * 색상 변경이 필요한 경우, ScrollButtonsProvider와 함께 사용해 주세요.
+ */
+function ScrollButtonsAreaImpl<T = UserType>(
+  {
+    lastSeenMessageId,
+    lastMessage,
+    scrollButtonsStyle,
+    children,
+  }: PropsWithChildren<ScrollButtonsAreaProps<T>>,
+  ref: ForwardedRef<ScrollButtonsAreaHandler>,
+) {
+  const [currentBottomIntersecting, setCurrentBottomIntersecting] =
+    useState<InspectionConfig>({ id: lastSeenMessageId, isIntersecting: false })
+
+  const prevBottomIntersecting = useRef<InspectionConfig>(
+    currentBottomIntersecting,
+  )
+
+  const mounted = useRef(false)
+
+  const { scrollToBottom } = useScroll()
+
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  const onButtonClick = (behavior: ScrollBehavior = 'smooth') => {
+    scrollToBottom({ scrollBehavior: behavior })
+  }
+
+  useImperativeHandle(ref, () => {
+    return {
+      onNewOthersMessage() {
+        if (prevBottomIntersecting.current.isIntersecting) {
+          onButtonClick()
+        }
+      },
+      setBottomIntersecting(data: InspectionConfig) {
+        setCurrentBottomIntersecting(data)
+        if (data.id === prevBottomIntersecting.current.id) {
+          prevBottomIntersecting.current = data
+        }
+      },
+    }
+  }, [onButtonClick])
+
+  useEffect(() => {
+    mounted.current = true
+  }, [])
+
+  useEffect(() => {
+    if (lastSeenMessageId !== prevBottomIntersecting.current.id) {
+      prevBottomIntersecting.current = currentBottomIntersecting
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [lastSeenMessageId])
+
+  const isNewMessageActive = !currentBottomIntersecting.id
+    ? false
+    : !(
+        currentBottomIntersecting.isIntersecting ||
+        !lastMessage ||
+        !lastSeenMessageId ||
+        Number(lastMessage.id) === Number(lastSeenMessageId)
+      )
+
+  return (
+    <Container>
+      {mounted.current && lastMessage !== undefined && (
+        <ScrollButtons
+          scrollButtonsStyle={scrollButtonsStyle}
+          onClick={onButtonClick}
+          message={lastMessage}
+          isBottomIntersecting={currentBottomIntersecting.isIntersecting}
+          newMessageActive={isNewMessageActive}
+        />
+      )}
+      {children}
+    </Container>
+  )
+}
+
+export const ScrollButtonsArea = forwardRef(ScrollButtonsAreaImpl)
diff --git a/packages/tds-widget/src/chat/scroll-buttons-area/scroll-buttons.tsx b/packages/tds-widget/src/chat/scroll-buttons-area/scroll-buttons.tsx
new file mode 100644
index 0000000000..f0ec60dab1
--- /dev/null
+++ b/packages/tds-widget/src/chat/scroll-buttons-area/scroll-buttons.tsx
@@ -0,0 +1,70 @@
+import { useEffect, useRef, useState } from 'react'
+import { CSSProp } from 'styled-components'
+
+import { ChatMessageInterface, UserType } from '../types'
+
+import { NewMessage, ScrollToBottomButton } from './elements'
+
+export interface ScrollButtonsProps<T = UserType> {
+  onClick: (behavior: ScrollBehavior) => void
+  message?: ChatMessageInterface<T>
+  newMessageActive: boolean
+  isBottomIntersecting: boolean
+  scrollButtonsStyle?: {
+    scrollToButton?: {
+      css?: CSSProp
+    }
+    newMessage?: {
+      css?: CSSProp
+    }
+  }
+}
+
+export function ScrollButtons<T = UserType>({
+  message: latestMessage,
+  onClick,
+  isBottomIntersecting,
+  newMessageActive,
+  scrollButtonsStyle = {},
+}: ScrollButtonsProps<T>) {
+  const [message, setMessage] = useState<ChatMessageInterface<T> | null>(null)
+  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
+
+  useEffect(() => {
+    if (latestMessage && latestMessage.id !== message?.id && newMessageActive) {
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current)
+      }
+      setMessage(latestMessage)
+
+      timeoutRef.current = setTimeout(() => {
+        setMessage(null)
+      }, 3000)
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [latestMessage])
+
+  const handleClick = () => {
+    onClick('instant')
+    if (timeoutRef.current) {
+      clearTimeout(timeoutRef.current)
+    }
+    setMessage(null)
+  }
+
+  return (
+    <>
+      {!(isBottomIntersecting || message) && (
+        <ScrollToBottomButton
+          onClick={onClick}
+          css={scrollButtonsStyle?.scrollToButton?.css}
+        />
+      )}
+      <NewMessage
+        message={message}
+        onClick={handleClick}
+        css={scrollButtonsStyle?.newMessage?.css}
+      />
+    </>
+  )
+}
diff --git a/packages/tds-widget/src/chat/scroll-buttons-area/theme-provider.tsx b/packages/tds-widget/src/chat/scroll-buttons-area/theme-provider.tsx
new file mode 100644
index 0000000000..97b1d3e204
--- /dev/null
+++ b/packages/tds-widget/src/chat/scroll-buttons-area/theme-provider.tsx
@@ -0,0 +1,30 @@
+import { PropsWithChildren } from 'react'
+import { DefaultTheme, ThemeProvider } from 'styled-components'
+
+export const scrollButtonsTheme = {
+  scrollToBottomButton: {
+    backgroundColor: 'rgba(255, 255, 255, 1)', // color-neutral-w-100
+    borderColor: 'rgba(234, 234, 234, 1)', // color-neutral-g-10
+  },
+  newMessageButton: {
+    backgroundColor: 'rgba(255, 255, 255, 1)', // color-neutral-w-100
+    borderColor: 'rgba(41, 41, 45, 0.1)', // color-neutral-b-10
+    boxShadowColor: 'rgba(0, 0, 0, 0.12)',
+    messageColor: 'rgba(41, 41, 45, 1)', // color-neutral-b-100
+    nameColor: 'rgba(126, 126, 129, 1)', // color-neutral-g-60
+    thumbnailBorderColor: 'rgba(223, 223, 224, 1)', // color-neutral-g-15
+  },
+}
+
+export type ScrollButtonsTheme = typeof scrollButtonsTheme
+
+export function ScrollButtonsProvider({
+  theme = scrollButtonsTheme,
+  children,
+}: PropsWithChildren<{ theme?: ScrollButtonsTheme }>) {
+  return (
+    <ThemeProvider theme={{ scrollButtons: theme } as unknown as DefaultTheme}>
+      {children}
+    </ThemeProvider>
+  )
+}


Reviews:
user: login: dia-triple
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T05:09:04Z
user: login: dia-triple
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T05:12:11Z
user: login: choisohyun
body: 수고하셨습니다! 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T02:31:14Z
user: login: dia-triple
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T03:31:02Z
user: login: dia-triple
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T05:28:07Z

Review comments:
diff_hunk: @@ -101,22 +101,25 @@ export interface NolInputAreaUIProps
   activeButtonColor?: string
 }
 
-export function NolInputAreaUI({
-  buttonDisabled = false,
-  inputValue,
-  setInputValue,
-  placeholder,
-  onImageUpload,
-  onSendMessage,
-  onInputClick,
-  onInputKeydown,
-  maxTextLength = MAX_TEXT_LENGTH,
-  multipleImageUpload = false,
-  color,
-  placeholderColor,
-  activeButtonColor,
-  ...props
-}: NolInputAreaUIProps) {
+function NolInputAreaUIImpl(
+  {
+    buttonDisabled = false,
+    inputValue,
+    setInputValue,
+    placeholder,
+    onImageUpload,
+    onSendMessage,
+    onInputClick,
+    onInputKeydown,
+    maxTextLength = MAX_TEXT_LENGTH,
+    multipleImageUpload = false,
+    color,
+    placeholderColor,
+    activeButtonColor,
+    ...props
+  }: NolInputAreaUIProps,
+  ref: ForwardedRef<HTMLDivElement>,
path: packages/tds-widget/src/chat/input-area/nol-input-area-ui/index.tsx
user: login: dia-triple
body: input 창의 resize observer를 위해 forwardRef 형태로 변환합니다.
created_at: 2025-04-02T05:09:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -227,7 +228,8 @@ export default function Messages<
           <IntersectionObserver
             onChange={
               onMessageIntersecting
-                ? (_inView, entry) => onMessageIntersecting(entry, id)
+                ? (_inView, entry) =>
+                    onMessageIntersecting(entry, id, !!createdAt)
path: packages/tds-widget/src/chat/messages/index.tsx
user: login: dia-triple
body: created 된 메시지에 따라 intersecting을 분기처리 하기 위해 추가합니다.
created_at: 2025-04-02T05:12:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,25 @@
+export function ArrowBottom16Icon({
path: packages/tds-widget/src/chat/icons/arrow-bottom-16-icon.tsx
user: login: choisohyun
body: 다른 bottom icon이 없다면 16 제거해도 되지 않을까용?? 
created_at: 2025-04-03T02:09:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,111 @@
+import {
+  PropsWithChildren,
+  useEffect,
+  useRef,
+  useState,
+  useImperativeHandle,
+  forwardRef,
+  ForwardedRef,
+} from 'react'
+import { Container } from '@titicaca/tds-ui'
+
+import { ChatMessageInterface, UserType } from '../types'
+import { useScroll } from '../chat'
+
+import { ScrollButtons, type ScrollButtonsProps } from './scroll-buttons'
+
+interface InspectionConfig {
+  id?: number
+  isIntersecting: boolean
+}
+
+interface ScrollButtonsAreaProps<T = UserType>
+  extends Pick<ScrollButtonsProps<T>, 'scrollButtonsStyle'> {
+  lastSeenMessageId?: number
+  lastMessage?: ChatMessageInterface<T>
+}
+
+export interface ScrollButtonsAreaHandler {
+  onNewOthersMessage: () => void
+  setBottomIntersecting: (data: InspectionConfig) => void
+}
+
+/**
+ * 색상 변경이 필요한 경우, ScrollButtonsProvider와 함께 사용해 주세요.
+ */
+function ScrollButtonsAreaImpl<T = UserType>(
+  {
+    lastSeenMessageId,
+    lastMessage,
+    scrollButtonsStyle,
+    children,
+  }: PropsWithChildren<ScrollButtonsAreaProps<T>>,
+  ref: ForwardedRef<ScrollButtonsAreaHandler>,
+) {
+  const [currentBottomIntersecting, setCurrentBottomIntersecting] =
+    useState<InspectionConfig>({ id: lastSeenMessageId, isIntersecting: false })
+
+  const prevBottomIntersecting = useRef<InspectionConfig>(
+    currentBottomIntersecting,
+  )
+
+  const mounted = useRef(false)
+
+  const { scrollToBottom } = useScroll()
+
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  const onButtonClick = (behavior: ScrollBehavior = 'smooth') => {
+    setTimeout(() => {
+      scrollToBottom({ scrollBehavior: behavior })
+    }, 100)
path: packages/tds-widget/src/chat/scroll-buttons-area/index.tsx
user: login: choisohyun
body: 클릭이벤트라서 클릭할 때만 실행될것 같은 느낌인데 setTimeout 들어간 이유가 궁금합니다!
created_at: 2025-04-03T02:29:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,25 @@
+export function ArrowBottom16Icon({
path: packages/tds-widget/src/chat/icons/arrow-bottom-16-icon.tsx
user: login: dia-triple
body: 아! chat/icons에 arrow-top (12), arrow-right (6*10)이 있어서 추가했어요..!
created_at: 2025-04-03T03:31:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,111 @@
+import {
+  PropsWithChildren,
+  useEffect,
+  useRef,
+  useState,
+  useImperativeHandle,
+  forwardRef,
+  ForwardedRef,
+} from 'react'
+import { Container } from '@titicaca/tds-ui'
+
+import { ChatMessageInterface, UserType } from '../types'
+import { useScroll } from '../chat'
+
+import { ScrollButtons, type ScrollButtonsProps } from './scroll-buttons'
+
+interface InspectionConfig {
+  id?: number
+  isIntersecting: boolean
+}
+
+interface ScrollButtonsAreaProps<T = UserType>
+  extends Pick<ScrollButtonsProps<T>, 'scrollButtonsStyle'> {
+  lastSeenMessageId?: number
+  lastMessage?: ChatMessageInterface<T>
+}
+
+export interface ScrollButtonsAreaHandler {
+  onNewOthersMessage: () => void
+  setBottomIntersecting: (data: InspectionConfig) => void
+}
+
+/**
+ * 색상 변경이 필요한 경우, ScrollButtonsProvider와 함께 사용해 주세요.
+ */
+function ScrollButtonsAreaImpl<T = UserType>(
+  {
+    lastSeenMessageId,
+    lastMessage,
+    scrollButtonsStyle,
+    children,
+  }: PropsWithChildren<ScrollButtonsAreaProps<T>>,
+  ref: ForwardedRef<ScrollButtonsAreaHandler>,
+) {
+  const [currentBottomIntersecting, setCurrentBottomIntersecting] =
+    useState<InspectionConfig>({ id: lastSeenMessageId, isIntersecting: false })
+
+  const prevBottomIntersecting = useRef<InspectionConfig>(
+    currentBottomIntersecting,
+  )
+
+  const mounted = useRef(false)
+
+  const { scrollToBottom } = useScroll()
+
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  const onButtonClick = (behavior: ScrollBehavior = 'smooth') => {
+    setTimeout(() => {
+      scrollToBottom({ scrollBehavior: behavior })
+    }, 100)
path: packages/tds-widget/src/chat/scroll-buttons-area/index.tsx
user: login: dia-triple
body: 앗 없어도 되겠군요! 제거했습니다.!

그리고 첫 메시지 전송 시 새로운 메시지 UI가 노출되는 오류가 있어 조건 수정했어요오! [76f5f23](https://github.com/titicacadev/triple-frontend/pull/3628/commits/76f5f23a345bf7423e5a352f9fab2d81f625b1fc)
created_at: 2025-04-03T05:28:07Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-04-01T01:12:46Z
body: ## [Codecov](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) Report
Attention: Patch coverage is `0%` with `118 lines` in your changes missing coverage. Please review.
> Project coverage is 13.16%. Comparing base [(`53a75bf`)](https://app.codecov.io/gh/titicacadev/triple-frontend/commit/53a75bffe99c786a35f7ca2f86f3baf8bffd6840?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) to head [(`76f5f23`)](https://app.codecov.io/gh/titicacadev/triple-frontend/commit/76f5f23a345bf7423e5a352f9fab2d81f625b1fc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev).
> Report is 48 commits behind head on epic/dmtalk.

| [Files with missing lines](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) | Patch % | Lines |
|---|---|---|
| [.../tds-widget/src/chat/scroll-buttons-area/index.tsx](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&filepath=packages%2Ftds-widget%2Fsrc%2Fchat%2Fscroll-buttons-area%2Findex.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev#diff-cGFja2FnZXMvdGRzLXdpZGdldC9zcmMvY2hhdC9zY3JvbGwtYnV0dG9ucy1hcmVhL2luZGV4LnRzeA==) | 0.00% | [26 Missing and 4 partials :warning: ](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) |
| [...s-widget/src/chat/scroll-buttons-area/elements.tsx](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&filepath=packages%2Ftds-widget%2Fsrc%2Fchat%2Fscroll-buttons-area%2Felements.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev#diff-cGFja2FnZXMvdGRzLXdpZGdldC9zcmMvY2hhdC9zY3JvbGwtYnV0dG9ucy1hcmVhL2VsZW1lbnRzLnRzeA==) | 0.00% | [27 Missing and 1 partial :warning: ](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) |
| [...rea/nol-input-area-ui/use-input-resize-observer.ts](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&filepath=packages%2Ftds-widget%2Fsrc%2Fchat%2Finput-area%2Fnol-input-area-ui%2Fuse-input-resize-observer.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev#diff-cGFja2FnZXMvdGRzLXdpZGdldC9zcmMvY2hhdC9pbnB1dC1hcmVhL25vbC1pbnB1dC1hcmVhLXVpL3VzZS1pbnB1dC1yZXNpemUtb2JzZXJ2ZXIudHM=) | 0.00% | [23 Missing and 2 partials :warning: ](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) |
| [...et/src/chat/scroll-buttons-area/scroll-buttons.tsx](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&filepath=packages%2Ftds-widget%2Fsrc%2Fchat%2Fscroll-buttons-area%2Fscroll-buttons.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev#diff-cGFja2FnZXMvdGRzLXdpZGdldC9zcmMvY2hhdC9zY3JvbGwtYnV0dG9ucy1hcmVhL3Njcm9sbC1idXR0b25zLnRzeA==) | 0.00% | [19 Missing and 2 partials :warning: ](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) |
| [...et/src/chat/input-area/nol-input-area-ui/index.tsx](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&filepath=packages%2Ftds-widget%2Fsrc%2Fchat%2Finput-area%2Fnol-input-area-ui%2Findex.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev#diff-cGFja2FnZXMvdGRzLXdpZGdldC9zcmMvY2hhdC9pbnB1dC1hcmVhL25vbC1pbnB1dC1hcmVhLXVpL2luZGV4LnRzeA==) | 0.00% | [5 Missing :warning: ](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) |
| [...et/src/chat/scroll-buttons-area/theme-provider.tsx](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&filepath=packages%2Ftds-widget%2Fsrc%2Fchat%2Fscroll-buttons-area%2Ftheme-provider.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev#diff-cGFja2FnZXMvdGRzLXdpZGdldC9zcmMvY2hhdC9zY3JvbGwtYnV0dG9ucy1hcmVhL3RoZW1lLXByb3ZpZGVyLnRzeA==) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) |
| [.../chat/chat-room-messages/use-chat-room-messages.ts](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&filepath=packages%2Ftds-widget%2Fsrc%2Fchat%2Fchat%2Fchat-room-messages%2Fuse-chat-room-messages.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev#diff-cGFja2FnZXMvdGRzLXdpZGdldC9zcmMvY2hhdC9jaGF0L2NoYXQtcm9vbS1tZXNzYWdlcy91c2UtY2hhdC1yb29tLW1lc3NhZ2VzLnRz) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) |
| [...tds-widget/src/chat/icons/arrow-bottom-16-icon.tsx](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&filepath=packages%2Ftds-widget%2Fsrc%2Fchat%2Ficons%2Farrow-bottom-16-icon.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev#diff-cGFja2FnZXMvdGRzLXdpZGdldC9zcmMvY2hhdC9pY29ucy9hcnJvdy1ib3R0b20tMTYtaWNvbi50c3g=) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) |
| [packages/tds-widget/src/chat/messages/index.tsx](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&filepath=packages%2Ftds-widget%2Fsrc%2Fchat%2Fmessages%2Findex.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev#diff-cGFja2FnZXMvdGRzLXdpZGdldC9zcmMvY2hhdC9tZXNzYWdlcy9pbmRleC50c3g=) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev) |

<details><summary>Additional details and impacted files</summary>


```diff
@@               Coverage Diff               @@
##           epic/dmtalk    #3628      +/-   ##
===============================================
- Coverage        13.29%   13.16%   -0.14%     
===============================================
  Files              780      786       +6     
  Lines            11161    11273     +112     
  Branches          3906     3949      +43     
===============================================
  Hits              1484     1484              
- Misses            9217     9320     +103     
- Partials           460      469       +9     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/titicacadev/triple-frontend/pull/3628?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=titicacadev).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: dia-triple
created_at: 2025-04-01T02:05:22Z
body: release-canary
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-01T02:16:37Z
body: v14.0.12-pr-3628.218 has been published!
author_association: NONE
user: login: dia-triple
created_at: 2025-04-01T05:17:01Z
body: release-canary
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-01T05:27:41Z
body: v14.0.12-pr-3628.219 has been published!
author_association: NONE
user: login: dia-triple
created_at: 2025-04-01T05:44:54Z
body: release-canary
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-01T06:03:09Z
body: v14.0.12-pr-3628.220 has been published!
author_association: NONE
user: login: dia-triple
created_at: 2025-04-01T06:23:35Z
body: release-canary
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-01T06:35:01Z
body: v14.0.12-pr-3628.221 has been published!
author_association: NONE
user: login: dia-triple
created_at: 2025-04-01T06:37:23Z
body: release-canary
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-01T06:54:38Z
body: v14.0.12-pr-3628.222 has been published!
author_association: NONE
user: login: dia-triple
created_at: 2025-04-01T08:33:33Z
body: release-canary
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-01T08:58:20Z
body: v14.0.12-pr-3628.224 has been published!
author_association: NONE
user: login: dia-triple
created_at: 2025-04-02T04:46:45Z
body: release-canary
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-02T04:59:25Z
body: v14.0.12-pr-3628.226 has been published!
author_association: NONE
user: login: dia-triple
created_at: 2025-04-02T05:45:52Z
body: release-canary
author_association: CONTRIBUTOR
user: login: dia-triple
created_at: 2025-04-03T00:10:30Z
body: release-canary
author_association: CONTRIBUTOR
user: login: dia-triple
created_at: 2025-04-03T00:29:59Z
body: release-canary
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-03T01:06:13Z
body: v14.0.12-pr-3628.229 has been published!
author_association: NONE
user: login: dia-triple
created_at: 2025-04-03T04:05:36Z
body: release-canary
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-03T04:15:18Z
body: v14.0.12-pr-3628.229 has been published!
author_association: NONE
user: login: dia-triple
created_at: 2025-04-03T04:50:27Z
body: release-canary
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-03T05:05:03Z
body: v14.0.12-pr-3628.230 has been published!
author_association: NONE