diff --git a/components/common/CommonPaginator.vue b/components/common/CommonPaginator.vue
index 4b976d65..0fd1b558 100644
--- a/components/common/CommonPaginator.vue
+++ b/components/common/CommonPaginator.vue
@@ -2,12 +2,13 @@
 // @ts-expect-error missing types
 import { DynamicScroller } from 'vue-virtual-scroller'
 import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
-import type { Paginator } from 'masto'
+import type { Paginator, WsEvents } from 'masto'
 
-const { paginator, keyProp = 'id', virtualScroller = false } = defineProps<{
+const { paginator, stream, keyProp = 'id', virtualScroller = false } = defineProps<{
   paginator: Paginator<any, any[]>
   keyProp?: string
   virtualScroller: boolean
+  stream?: WsEvents
 }>()
 
 defineSlots<{
@@ -15,14 +16,19 @@ defineSlots<{
     item: any
     active?: boolean
   }
+  updater: {
+    number: number
+    update: () => void
+  }
   loading: {}
 }>()
 
-const { items, state, endAnchor, error } = usePaginator(paginator)
+const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, stream)
 </script>
 
 <template>
   <div>
+    <slot v-if="prevItems.length" name="updater" v-bind="{ number: prevItems.length, update }" />
     <template v-if="virtualScroller">
       <DynamicScroller
         v-slot="{ item, active }"
diff --git a/components/timeline/TimelinePaginator.vue b/components/timeline/TimelinePaginator.vue
index 6f56e8d7..3b91389d 100644
--- a/components/timeline/TimelinePaginator.vue
+++ b/components/timeline/TimelinePaginator.vue
@@ -1,15 +1,21 @@
 <script setup lang="ts">
 // @ts-expect-error missing types
 import { DynamicScrollerItem } from 'vue-virtual-scroller'
-import type { Paginator, Status } from 'masto'
+import type { Paginator, Status, WsEvents } from 'masto'
 
-const { paginator } = defineProps<{
+const { paginator, stream } = defineProps<{
   paginator: Paginator<any, Status[]>
+  stream?: WsEvents
 }>()
 </script>
 
 <template>
-  <CommonPaginator :paginator="paginator" virtual-scroller>
+  <CommonPaginator v-bind="{ paginator, stream }" virtual-scroller>
+    <template #updater="{ number, update }">
+      <button py-4 border="b base" flex="~ col" p-3 w-full text-primary font-bold @click="update">
+        Show {{ number }} new items
+      </button>
+    </template>
     <template #default="{ item, active }">
       <DynamicScrollerItem :item="item" :active="active" tag="article">
         <StatusCard
diff --git a/composables/paginator.ts b/composables/paginator.ts
index 10dc1196..a051976b 100644
--- a/composables/paginator.ts
+++ b/composables/paginator.ts
@@ -1,11 +1,12 @@
-import type { Paginator } from 'masto'
+import type { Paginator, WsEvents } from 'masto'
 import { useDeactivated } from './lifecycle'
 import type { PaginatorState } from '~/types'
 
-export function usePaginator<T>(paginator: Paginator<any, T[]>) {
+export function usePaginator<T>(paginator: Paginator<any, T[]>, stream?: WsEvents) {
   const state = ref<PaginatorState>('idle')
   const items = ref<T[]>([])
-  const newItems = ref<T[]>([])
+  const nextItems = ref<T[]>([])
+  const prevItems = ref<T[]>([])
 
   const endAnchor = ref<HTMLDivElement>()
   const bound = reactive(useElementBounding(endAnchor))
@@ -13,6 +14,22 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
   const error = ref<unknown | undefined>()
   const deactivated = useDeactivated()
 
+  async function update() {
+    items.value.unshift(...prevItems.value)
+    prevItems.value = []
+  }
+
+  stream?.on('update', (status) => {
+    prevItems.value.unshift(status as any)
+  })
+
+  // TODO: update statuses
+  stream?.on('status.update', (status) => {
+    const index = items.value.findIndex((s: any) => s.id === status.id)
+    if (index >= 0)
+      items.value[index] = status as any
+  })
+
   async function loadNext() {
     if (state.value !== 'idle')
       return
@@ -22,8 +39,8 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
       const result = await paginator.next()
 
       if (result.value?.length) {
-        newItems.value = result.value
-        items.value.push(...newItems.value)
+        nextItems.value = result.value
+        items.value.push(...nextItems.value)
         state.value = 'idle'
       }
       else {
@@ -59,7 +76,9 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
 
   return {
     items,
-    newItems,
+    prevItems,
+    nextItems,
+    update,
     state,
     error,
     endAnchor,
diff --git a/pages/home.vue b/pages/home.vue
index 95ad5569..23913eed 100644
--- a/pages/home.vue
+++ b/pages/home.vue
@@ -12,6 +12,8 @@ if (useRoute().path === '/signin/callback') {
 }
 
 const paginator = useMasto().timelines.getHomeIterable()
+const stream = await useMasto().stream.streamUser()
+onBeforeUnmount(() => stream.disconnect())
 
 const { t } = useI18n()
 useHead({
@@ -26,7 +28,7 @@ useHead({
     </template>
     <slot>
       <PublishWidget draft-key="home" border="b base" />
-      <TimelinePaginator :paginator="paginator" />
+      <TimelinePaginator v-bind="{ paginator, stream }" />
     </slot>
   </MainContent>
 </template>
diff --git a/pages/public/index.vue b/pages/public/index.vue
index bbfdec7d..23c984d3 100644
--- a/pages/public/index.vue
+++ b/pages/public/index.vue
@@ -1,5 +1,7 @@
 <script setup lang="ts">
 const paginator = useMasto().timelines.getPublicIterable()
+const stream = await useMasto().stream.streamPublicTimeline()
+onBeforeUnmount(() => stream.disconnect())
 
 useHead({
   title: 'Federated',
@@ -13,7 +15,7 @@ useHead({
     </template>
 
     <slot>
-      <TimelinePaginator :paginator="paginator" />
+      <TimelinePaginator v-bind="{ paginator, stream }" />
     </slot>
   </MainContent>
 </template>
diff --git a/pages/public/local.vue b/pages/public/local.vue
index d563ffe5..88cc9950 100644
--- a/pages/public/local.vue
+++ b/pages/public/local.vue
@@ -1,5 +1,7 @@
 <script setup lang="ts">
 const paginator = useMasto().timelines.getPublicIterable({ local: true })
+const stream = await useMasto().stream.streamCommunityTimeline()
+onBeforeUnmount(() => stream.disconnect())
 
 useHead({
   title: 'Local',
@@ -13,7 +15,7 @@ useHead({
     </template>
 
     <slot>
-      <TimelinePaginator :paginator="paginator" />
+      <TimelinePaginator v-bind="{ paginator, stream }" />
     </slot>
   </MainContent>
 </template>
diff --git a/pages/tags/[tag].vue b/pages/tags/[tag].vue
index d8d8c2bc..e228f83e 100644
--- a/pages/tags/[tag].vue
+++ b/pages/tags/[tag].vue
@@ -3,6 +3,8 @@ const params = useRoute().params
 const tag = $(computedEager(() => params.tag as string))
 
 const paginator = useMasto().timelines.getHashtagIterable(tag)
+const stream = await useMasto().stream.streamTagTimeline(tag)
+onBeforeUnmount(() => stream.disconnect())
 
 useHead({
   title: `#${tag}`,
@@ -16,7 +18,7 @@ useHead({
     </template>
 
     <slot>
-      <TimelinePaginator :paginator="paginator" />
+      <TimelinePaginator v-bind="{ paginator, stream }" />
     </slot>
   </MainContent>
 </template>