Skip to content

Commit 0e761ff

Browse files
authored
Merge pull request #62 from 0x3EF8/pr/local-sync
Improve chat reliability and Supabase migration hardening
2 parents c8fb95f + b405716 commit 0e761ff

43 files changed

Lines changed: 1601 additions & 280 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ SENTRY_DNS=
1515
NEXT_PUBLIC_NORTON_SAFEWEB_SITE_VERIFICATION=
1616

1717
SUPABASE_ACCESS_TOKEN=
18-
SUPABASE_PROJECT_ID=
18+
SUPABASE_PROJECT_ID=vswabkwgipyweqsabzwv

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ next-env.d.ts
4848
# supabase
4949
supabase/*
5050
!supabase/migrations
51+
!supabase/migrations_archive
52+
!supabase/migrations_archive/**
5153

5254

5355
yarn.lock

README.md

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,38 +48,62 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the
4848

4949
## Database Migrations
5050

51-
Login first (if you haven't):
51+
For a brand-new Supabase project, use this flow from the repo root.
52+
53+
This repository now uses a squashed baseline migration for fresh installs:
54+
55+
- Active baseline: `supabase/migrations/20260407120000_baseline_fresh_setup.sql`
56+
57+
- Historical migrations archive: `supabase/migrations_archive/`
58+
59+
1. Login to Supabase CLI:
5260

5361
```bash
5462
npx supabase login
5563
```
5664

57-
Link the cloud project to this local one:
65+
2. Initialize local Supabase config (only if missing):
66+
67+
```bash
68+
npx supabase init
69+
```
70+
71+
3. Link this repo to your cloud project:
5872

5973
```bash
6074
npx supabase link
6175
```
6276

63-
It'll show the list of project you have select your project.
77+
You can select from the project list, or run `npx supabase link --project-ref <project-ref>`.
6478

65-
Push migrations to cloud:
79+
4. Push all migrations to the new project:
6680

6781
```bash
6882
npx supabase db push
6983
```
7084

71-
Pull migrations from cloud:
85+
On a fresh project this applies only the single baseline migration.
86+
87+
5. (Optional) Pull remote schema changes into migrations:
7288

7389
```bash
7490
npx supabase db pull
7591
```
7692

77-
Updating types (if you ever changed migrations):
93+
6. Regenerate Supabase TypeScript types after schema changes:
7894

7995
```bash
8096
npx supabase gen types typescript --project-id <project-id> --schema public > app/supabase-types.ts
8197
```
8298

99+
If you want to re-run the full migration chain on local development:
100+
101+
```bash
102+
npx supabase db reset
103+
```
104+
105+
If you need to inspect migration history, use the files in `supabase/migrations_archive/`.
106+
83107

84108
## Learn More
85109

app/components/Chat.tsx

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export default function Chat({ user }: { user: User }) {
169169
supabase,
170170
userId: user.id,
171171
showModal,
172+
globalConversationId: GLOBAL_CONVERSATION_ID,
172173
});
173174

174175
const { badgesByUserId } = useChatBadges({
@@ -197,7 +198,6 @@ export default function Chat({ user }: { user: User }) {
197198
setConversations,
198199
setParticipantMetaByConversationId,
199200
setLastSeenByUserId,
200-
setUnreadCountByConversationId,
201201
fetchUnreadCountsForConversations,
202202
markConversationAsRead,
203203
});
@@ -236,9 +236,10 @@ export default function Chat({ user }: { user: User }) {
236236

237237
const bucketName = process.env.NEXT_PUBLIC_SUPABASE_BUCKET_NAME || "";
238238

239-
const { sendMessage } = useChatMessageComposer({
239+
const { sendMessage, isSendingMessage } = useChatMessageComposer({
240240
supabase,
241241
userId: user.id,
242+
channelRef,
242243
conversationId,
243244
input,
244245
attachments,
@@ -255,6 +256,7 @@ export default function Chat({ user }: { user: User }) {
255256
const { textareaRef, handleInputChange, handleInputKeyDown } =
256257
useChatInputBehavior({
257258
input,
259+
isSendingMessage,
258260
conversationId,
259261
attachmentsCount: attachments.length,
260262
setInput,
@@ -263,10 +265,8 @@ export default function Chat({ user }: { user: User }) {
263265
maxChars: 1000,
264266
});
265267

266-
const totalUnreadCount = useMemo(
267-
() => Object.values(unreadCountByConversationId).reduce((sum, count) => sum + count, 0),
268-
[unreadCountByConversationId],
269-
);
268+
const canSendMessage =
269+
!isSendingMessage && (input.trim().length > 0 || attachments.length > 0);
270270

271271
const globalConversations = conversations.filter((c) => c.type === "global");
272272
const privateConversations = conversations
@@ -334,6 +334,15 @@ export default function Chat({ user }: { user: User }) {
334334
.reverse();
335335
}, [messages]);
336336

337+
// Performance optimization: Memoize the filtered messages to avoid O(N) string manipulation on every keystroke
338+
// when typing a message (which triggers a re-render of Chat.tsx). This significantly improves typing latency
339+
// in conversations with many messages.
340+
const filteredMessages = useMemo(() => {
341+
if (!messageSearch) return messages;
342+
const lowerSearch = messageSearch.toLowerCase();
343+
return messages.filter((m) => (m.text || "").toLowerCase().includes(lowerSearch));
344+
}, [messages, messageSearch]);
345+
337346
return (
338347
<>
339348
<MediaViewerModal
@@ -347,14 +356,7 @@ export default function Chat({ user }: { user: User }) {
347356
<div className={`w-full md:w-[300px] flex-shrink-0 border-r border-white/5 flex flex-col bg-[#0a0a1a] md:bg-transparent z-20 absolute md:relative h-full transition-transform duration-300 ${conversationId ? '-translate-x-full md:translate-x-0' : 'translate-x-0'}`}>
348357
<div className="p-5 border-b border-white/5">
349358
<div className="flex items-center justify-between mb-4">
350-
<div className="flex items-center gap-2">
351-
<h2 className="text-lg font-bold text-gray-100 tracking-tight">Message category</h2>
352-
{totalUnreadCount > 0 && (
353-
<span className="min-w-[24px] h-6 px-2 rounded-full bg-rose-500/90 text-white text-[11px] font-bold flex items-center justify-center">
354-
{totalUnreadCount > 99 ? "99+" : totalUnreadCount}
355-
</span>
356-
)}
357-
</div>
359+
<h2 className="text-lg font-bold text-gray-100 tracking-tight">Message category</h2>
358360
<button
359361
onClick={() => setShowModal(true)}
360362
className="w-8 h-8 rounded-full bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center hover:bg-indigo-500/20 transition"
@@ -489,9 +491,7 @@ export default function Chat({ user }: { user: User }) {
489491

490492
<div className="flex-1 flex flex-col overflow-hidden z-10 min-h-0">
491493
<Messages
492-
messages={messages.filter((m) =>
493-
(m.text || "").toLowerCase().includes(messageSearch.toLowerCase())
494-
)}
494+
messages={filteredMessages}
495495
user={user}
496496
conversations={conversations}
497497
bottomRef={bottomRef}
@@ -559,6 +559,7 @@ export default function Chat({ user }: { user: User }) {
559559
>
560560
<button
561561
onClick={() => fileInputRef.current?.click()}
562+
disabled={isSendingMessage}
562563
className="w-10 h-10 mb-[2px] rounded-full bg-transparent hover:bg-white/10 flex items-center justify-center transition-all duration-300 flex-shrink-0 group"
563564
title="Attach file"
564565
>
@@ -589,15 +590,17 @@ export default function Chat({ user }: { user: User }) {
589590
<div className="mb-[2px] pr-1">
590591
<button
591592
onClick={sendMessage}
592-
disabled={!input.trim() && attachments.length === 0}
593+
disabled={!canSendMessage}
593594
className={`h-10 px-5 rounded-[20px] font-semibold text-[14px] flex items-center gap-2.5 transition-all duration-300 flex-shrink-0
594-
${(input.trim() || attachments.length > 0)
595+
${canSendMessage
595596
? "bg-gradient-to-r from-indigo-500 to-violet-500 hover:from-indigo-400 hover:to-violet-400 text-white shadow-md shadow-indigo-500/25 active:scale-95"
596597
: "bg-white/5 text-gray-500 cursor-not-allowed"}
597598
`}
598599
>
599-
<span className="hidden sm:inline">Send</span>
600-
<FontAwesomeIcon icon={faPaperPlane} className={`w-[14px] h-[14px] transition-transform ${(input.trim() || attachments.length > 0) ? "translate-x-0.5" : ""}`} />
600+
<span className="hidden sm:inline">
601+
{isSendingMessage ? "Sending..." : "Send"}
602+
</span>
603+
<FontAwesomeIcon icon={faPaperPlane} className={`w-[14px] h-[14px] transition-transform ${canSendMessage ? "translate-x-0.5" : ""}`} />
601604
</button>
602605
</div>
603606
</div>

0 commit comments

Comments
 (0)