/** * Dev seed — заполняет store фейковыми контактами и сообщениями для UI-теста. * * Запускается один раз при монтировании layout'а если store пустой * (useDevSeed). Реальные контакты через WS/HTTP приходят позже — * `upsertContact` перезаписывает mock'и по address'у. * * Цели seed'а: * 1. Показать все три типа чатов (direct / group / channel) с разным * поведением sender-meta. * 2. Наполнить список чатов до скролла (15+ контактов). * 3. В каждом чате — ≥15 сообщений для скролла в chat view. * 4. Продемонстрировать "staircase" (run'ы одного отправителя * внутри 1h-окна) и переключения между отправителями. */ import { useEffect } from 'react'; import { useStore } from './store'; import type { Contact, Message } from './types'; // ─── Детерминированные «pubkey» (64 hex символа) ─────────────────── function fakeHex(seed: number): string { let h = ''; let x = seed; for (let i = 0; i < 32; i++) { x = (x * 1103515245 + 12345) & 0xffffffff; h += (x & 0xff).toString(16).padStart(2, '0'); } return h; } const now = () => Math.floor(Date.now() / 1000); const MINE = fakeHex(9999); // ─── Контакты ────────────────────────────────────────────────────── // 16 штук: 5 DM + 6 групп + 5 каналов. Поле `addedAt` задаёт порядок в // списке когда нет messages — ordering-fallback. const mockContacts: Contact[] = [ // ── DM ────────────────────────────────────────────────────────── { address: fakeHex(1001), x25519Pub: fakeHex(2001), username: 'jordan', addedAt: Date.now() - 60 * 60 * 1_000, kind: 'direct' }, { address: fakeHex(1002), x25519Pub: fakeHex(2002), alias: 'Myles Wagner', addedAt: Date.now() - 2 * 60 * 60 * 1_000, kind: 'direct' }, { address: fakeHex(1010), x25519Pub: fakeHex(2010), username: 'sarah_k', addedAt: Date.now() - 3 * 60 * 60 * 1_000, kind: 'direct', unread: 2 }, { address: fakeHex(1011), x25519Pub: fakeHex(2011), alias: 'Mom', addedAt: Date.now() - 5 * 60 * 60 * 1_000, kind: 'direct' }, { address: fakeHex(1012), x25519Pub: fakeHex(2012), username: 'alex_dev', addedAt: Date.now() - 6 * 60 * 60 * 1_000, kind: 'direct' }, // ── Groups ───────────────────────────────────────────────────── { address: fakeHex(1003), x25519Pub: fakeHex(2003), alias: 'Tahoe weekend 🌲', addedAt: Date.now() - 4 * 60 * 60 * 1_000, kind: 'group' }, { address: fakeHex(1004), x25519Pub: fakeHex(2004), alias: 'Knicks tickets', addedAt: Date.now() - 5 * 60 * 60 * 1_000, kind: 'group', unread: 3 }, { address: fakeHex(1020), x25519Pub: fakeHex(2020), alias: 'Family', addedAt: Date.now() - 8 * 60 * 60 * 1_000, kind: 'group' }, { address: fakeHex(1021), x25519Pub: fakeHex(2021), alias: 'Work eng', addedAt: Date.now() - 12 * 60 * 60 * 1_000, kind: 'group', unread: 7 }, { address: fakeHex(1022), x25519Pub: fakeHex(2022), alias: 'Book club', addedAt: Date.now() - 24 * 60 * 60 * 1_000, kind: 'group' }, { address: fakeHex(1023), x25519Pub: fakeHex(2023), alias: 'Tuesday D&D 🎲', addedAt: Date.now() - 30 * 60 * 60 * 1_000, kind: 'group' }, // (Channel seeds removed in v2.0.0 — channels replaced by the social feed.) ]; // ─── Генератор сообщений ─────────────────────────────────────────── // Альт-отправители для group-чатов — нужны только как идентификатор `from`. const P_TYRA = fakeHex(3001); const P_MYLES = fakeHex(3002); const P_NATE = fakeHex(3003); const P_TYLER = fakeHex(3004); const P_MOM = fakeHex(3005); const P_DAD = fakeHex(3006); const P_SIS = fakeHex(3007); const P_LEAD = fakeHex(3008); const P_PM = fakeHex(3009); const P_QA = fakeHex(3010); const P_DESIGN= fakeHex(3011); const P_ANNA = fakeHex(3012); const P_DM_PEER = fakeHex(3013); type Msg = Omit; function list(prefix: string, list: Msg[]): Message[] { return list.map((m, i) => ({ ...m, id: `${prefix}_${i}` })); } function mockMessagesFor(contact: Contact): Message[] { const peer = contact.x25519Pub; // ── DM: @jordan ──────────────────────────────────────────────── if (contact.username === 'jordan') { // Важно: id'ы сообщений используются в replyTo.id, поэтому // указываем их явно где нужно сшить thread. const msgs: Message[] = list('jordan', [ { from: peer, mine: false, timestamp: now() - 60 * 60 * 22, text: 'Hey, have a sec later today?' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 21, read: true, text: 'yep around 4pm' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 20, text: 'cool, coffee at the corner spot?' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 19, read: true, text: 'works' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 4, text: 'just parked 🚗' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 4, text: 'see you in 5' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 3, read: true, text: "that was a great catchup" }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 3, text: "totally — thanks for the book rec" }, { from: peer, mine: false, timestamp: now() - 60 * 40, text: 'Hey Jordan - Got tickets to the Knicks game tomorrow, let me know if you want to come!' }, { from: peer, mine: false, timestamp: now() - 60 * 39, text: "we've got floor seats 🔥" }, { from: peer, mine: false, timestamp: now() - 60 * 38, text: "starts at 7, pregame at the bar across the street" }, { from: MINE, mine: true, timestamp: now() - 60 * 14, read: true, edited: true, text: 'Ah sadly I already have plans' }, { from: MINE, mine: true, timestamp: now() - 60 * 13, read: true, text: 'maybe next time?' }, { from: peer, mine: false, timestamp: now() - 60 * 5, text: "no worries — enjoy whatever you're up to" }, { from: peer, mine: false, timestamp: now() - 60 * 2, text: "wish you could make it tho 🏀" }, ]); // Пришьём reply: MINE-сообщение "Ah sadly…" отвечает на "Hey Jordan - Got tickets…" const target = msgs.find(m => m.text.startsWith('Hey Jordan - Got tickets')); const mine = msgs.find(m => m.text === 'Ah sadly I already have plans'); if (target && mine) { mine.replyTo = { id: target.id, author: '@jordan', text: target.text, }; } return msgs; } // ── DM: Myles Wagner ─────────────────────────────────────────── if (contact.alias === 'Myles Wagner') { return list('myles', [ { from: peer, mine: false, timestamp: now() - 60 * 60 * 30, text: 'saw the draft, left a bunch of comments' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 29, read: true, text: 'thx, going through them now' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 29, text: 'no rush — tomorrow is fine' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 5, text: 'lunch today?' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 4, read: true, text: "can't, stuck in reviews" }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 4, read: true, text: 'tomorrow?' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 4, text: '✅ tomorrow' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 2, text: '', attachment: { kind: 'voice', uri: 'voice-demo://myles-1', duration: 17, }, }, { from: peer, mine: false, timestamp: now() - 60 * 25, text: 'the dchain repo finally built for me' }, { from: peer, mine: false, timestamp: now() - 60 * 25, text: 'docker weirdness was the issue' }, { from: MINE, mine: true, timestamp: now() - 60 * 21, read: true, text: "nice, told you the WSL path would do it" }, { from: peer, mine: false, timestamp: now() - 60 * 20, text: 'So good!' }, ]); } // ── DM: @sarah_k (с unread=2) ────────────────────────────────── if (contact.username === 'sarah_k') { return list('sarah', [ { from: peer, mine: false, timestamp: now() - 60 * 60 * 30, text: "hey! been a while" }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 28, read: true, text: 'yeah, finally surfaced after the launch crunch' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 27, text: 'how did it go?' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 27, read: true, text: "pretty well actually 🙏" }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 2, text: 'btw drinks on friday?' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 2, text: 'that new wine bar' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 2, text: 'around 7 if you can make it' }, ]); } // ── DM: Mom ──────────────────────────────────────────────────── if (contact.alias === 'Mom') { return list('mom', [ { from: peer, mine: false, timestamp: now() - 60 * 60 * 48, text: 'Did you see the photos from the trip?' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 47, read: true, text: 'not yet, send them again?' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 47, text: 'ok' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 46, text: '', attachment: { kind: 'image', uri: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800', width: 800, height: 533, mime: 'image/jpeg', }, }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 46, text: '', attachment: { kind: 'image', uri: 'https://images.unsplash.com/photo-1519681393784-d120267933ba?w=800', width: 800, height: 533, mime: 'image/jpeg', }, }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 30, read: true, text: 'wow, grandma looks great' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 30, text: 'she asked about you!' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 7, text: 'call later?' }, ]); } // ── DM: @alex_dev ────────────────────────────────────────────── if (contact.username === 'alex_dev') { return list('alex', [ { from: peer, mine: false, timestamp: now() - 60 * 60 * 12, text: 'did you try the new WASM build?' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 11, read: true, text: 'yeah, loader error on start' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 11, text: 'path encoding issue again?' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 10, read: true, text: 'probably, checking now' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 8, read: true, text: 'yep, was the trailing slash' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 8, text: 'classic 😅' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 7, text: 'PR for that incoming tomorrow' }, ]); } // ── Group: Tahoe weekend 🌲 ──────────────────────────────────── if (contact.alias === 'Tahoe weekend 🌲') { const msgs: Message[] = list('tahoe', [ { from: P_TYRA, mine: false, timestamp: now() - 60 * 60 * 50, text: "who's in for Tahoe this weekend?" }, { from: P_MYLES, mine: false, timestamp: now() - 60 * 60 * 49, text: "me!" }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 48, read: true, text: "count me in" }, { from: P_TYRA, mine: false, timestamp: now() - 60 * 60 * 48, text: "woohoo 🎉" }, { from: P_ANNA, mine: false, timestamp: now() - 60 * 60 * 47, text: "planning friday night → sunday evening yeah?" }, { from: P_TYRA, mine: false, timestamp: now() - 60 * 60 * 46, text: "yep, maybe leave friday after lunch" }, { from: P_MYLES, mine: false, timestamp: now() - 60 * 60 * 30, text: "I made this itinerary with Grok, what do you think?" }, { from: P_MYLES, mine: false, timestamp: now() - 60 * 60 * 30, text: "Day 1: Eagle Falls hike" }, { from: P_MYLES, mine: false, timestamp: now() - 60 * 60 * 30, text: "Day 2: Emerald bay kayak" }, { from: P_MYLES, mine: false, timestamp: now() - 60 * 60 * 30, text: "Day 3: lazy breakfast then drive back" }, { from: P_MYLES, mine: false, timestamp: now() - 60 * 60 * 30, text: '', attachment: { kind: 'file', uri: 'https://example.com/Lake_Tahoe_Itinerary.pdf', name: 'Lake_Tahoe_Itinerary.pdf', size: 97_280, // ~95 KB mime: 'application/pdf', }, }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 24, read: true, edited: true, text: "Love it — Eagle falls looks insane" }, { from: P_ANNA, mine: false, timestamp: now() - 60 * 60 * 24, text: "Eagle falls was stunning last year!" }, { from: P_TYRA, mine: false, timestamp: now() - 60 * 31, text: "who's excited for Tahoe this weekend?" }, { from: P_TYRA, mine: false, timestamp: now() - 60 * 30, text: "I've been checking the forecast — sun all weekend 🌞" }, { from: P_MYLES, mine: false, timestamp: now() - 60 * 22, text: "I made this itinerary with Grok, what do you think?" }, { from: P_MYLES, mine: false, timestamp: now() - 60 * 21, text: "Day 1 we can hit Eagle Falls" }, { from: MINE, mine: true, timestamp: now() - 60 * 14, read: true, edited: true, text: "Love it — Eagle falls looks insane" }, { from: P_TYRA, mine: false, timestamp: now() - 60 * 3, text: 'pic from my last trip 😍', attachment: { kind: 'image', uri: 'https://images.unsplash.com/photo-1505245208761-ba872912fac0?w=800', width: 800, height: 1000, mime: 'image/jpeg', }, }, ]); // Thread: mine "Love it — Eagle falls looks insane" — ответ на // Myles'овский itinerary-PDF. Берём ПЕРВЫЙ match "Day 1 we can hit // Eagle Falls" и пришиваем его к первому mine-bubble'у. const target = msgs.find(m => m.text === 'Day 1 we can hit Eagle Falls'); const reply = msgs.find(m => m.text === 'Love it — Eagle falls looks insane' && m.mine); if (target && reply) { reply.replyTo = { id: target.id, author: 'Myles Wagner', text: target.text, }; } return msgs; } // ── Group: Knicks tickets ────────────────────────────────────── if (contact.alias === 'Knicks tickets') { return list('knicks', [ { from: P_NATE, mine: false, timestamp: now() - 60 * 60 * 20, text: "quick group update — got 5 tickets for thursday" }, { from: P_TYLER, mine: false, timestamp: now() - 60 * 60 * 19, text: 'wow nice' }, { from: P_TYLER, mine: false, timestamp: now() - 60 * 60 * 19, text: 'where are we seated?' }, { from: P_NATE, mine: false, timestamp: now() - 60 * 60 * 19, text: 'section 102, row 12' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 18, read: true, text: 'thats a great spot' }, { from: P_ANNA, mine: false, timestamp: now() - 60 * 60 * 18, text: "can someone venmo nate 🙏" }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 17, read: true, text: 'sending now' }, { from: P_NATE, mine: false, timestamp: now() - 60 * 32, text: "Ok who's in for tomorrow's game?" }, { from: P_NATE, mine: false, timestamp: now() - 60 * 31, text: 'Got 2 extra tickets, first-come-first-served' }, { from: P_TYLER, mine: false, timestamp: now() - 60 * 27, text: "I'm in!" }, { from: P_TYLER, mine: false, timestamp: now() - 60 * 26, text: 'What time does it start?' }, { from: MINE, mine: true, timestamp: now() - 60 * 20, read: true, text: "Let's meet at the bar around 6?" }, { from: P_NATE, mine: false, timestamp: now() - 60 * 15, text: 'Sounds good' }, ]); } // ── Group: Family ────────────────────────────────────────────── if (contact.alias === 'Family') { return list('family', [ { from: P_MOM, mine: false, timestamp: now() - 60 * 60 * 36, text: 'remember grandma birthday on sunday' }, { from: P_DAD, mine: false, timestamp: now() - 60 * 60 * 36, text: 'noted 🎂' }, { from: P_SIS, mine: false, timestamp: now() - 60 * 60 * 35, text: 'who is bringing the cake?' }, { from: P_MOM, mine: false, timestamp: now() - 60 * 60 * 35, text: "I'll get it from the bakery" }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 34, read: true, text: 'I can pick up flowers' }, { from: P_SIS, mine: false, timestamp: now() - 60 * 60 * 34, text: 'perfect' }, { from: P_DAD, mine: false, timestamp: now() - 60 * 60 * 8, text: 'forecast is rain sunday — backup plan?' }, { from: P_MOM, mine: false, timestamp: now() - 60 * 60 * 8, text: "we'll move indoors, the living room works" }, { from: P_SIS, mine: false, timestamp: now() - 60 * 60 * 7, text: 'works!' }, ]); } // ── Group: Work eng (unread=7) ───────────────────────────────── if (contact.alias === 'Work eng') { return list('work', [ { from: P_LEAD, mine: false, timestamp: now() - 60 * 60 * 16, text: 'standup at 10 moved to 11 today btw' }, { from: P_PM, mine: false, timestamp: now() - 60 * 60 * 16, text: 'thanks!' }, { from: P_QA, mine: false, timestamp: now() - 60 * 60 * 15, text: "the staging deploy broke again 🙃" }, { from: P_LEAD, mine: false, timestamp: now() - 60 * 60 * 15, text: "ugh, looking" }, { from: P_LEAD, mine: false, timestamp: now() - 60 * 60 * 14, text: 'fixed — migration was stuck' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 13, read: true, text: 'Worked for me now 👍' }, { from: P_PM, mine: false, timestamp: now() - 60 * 60 * 5, text: 'reminder: demo tomorrow, slides by eod' }, { from: P_LEAD, mine: false, timestamp: now() - 60 * 60 * 4, text: 'Ill handle the technical half' }, { from: P_DESIGN,mine: false,timestamp: now() - 60 * 60 * 4, text: 'just posted the v2 mocks in figma' }, { from: P_PM, mine: false, timestamp: now() - 60 * 60 * 3, text: 'chatting with sales — 3 new trials this week' }, { from: P_QA, mine: false, timestamp: now() - 60 * 60 * 2, text: 'flaky test on CI — investigating' }, { from: P_LEAD, mine: false, timestamp: now() - 60 * 30, text: 'okay seems like CI is green now' }, { from: P_LEAD, mine: false, timestamp: now() - 60 * 28, text: 'retry passed' }, { from: P_PM, mine: false, timestamp: now() - 60 * 20, text: "we're good for release" }, ]); } // ── Group: Book club ─────────────────────────────────────────── if (contact.alias === 'Book club') { return list('book', [ { from: P_ANNA, mine: false, timestamp: now() - 60 * 60 * 96, text: 'next month: "Project Hail Mary"?' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 95, read: true, text: '👍' }, { from: P_SIS, mine: false, timestamp: now() - 60 * 60 * 94, text: 'yes please' }, { from: P_TYRA, mine: false, timestamp: now() - 60 * 60 * 48, text: 'halfway through — so good' }, { from: P_ANNA, mine: false, timestamp: now() - 60 * 60 * 48, text: 'love the linguistics angle' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 30, read: true, text: "rocky is my favourite character in years" }, { from: P_SIS, mine: false, timestamp: now() - 60 * 60 * 28, text: 'agreed' }, { from: P_ANNA, mine: false, timestamp: now() - 60 * 60 * 24, text: "let's meet sunday 4pm?" }, ]); } // ── Group: Tuesday D&D 🎲 ────────────────────────────────────── if (contact.alias === 'Tuesday D&D 🎲') { return list('dnd', [ { from: P_LEAD, mine: false, timestamp: now() - 60 * 60 * 72, text: 'Session 14 recap up on the wiki' }, { from: P_ANNA, mine: false, timestamp: now() - 60 * 60 * 72, text: '🙏' }, { from: P_TYRA, mine: false, timestamp: now() - 60 * 60 * 50, text: 'can we start 30min late next tuesday? commute issue' }, { from: P_LEAD, mine: false, timestamp: now() - 60 * 60 * 50, text: 'sure' }, { from: MINE, mine: true, timestamp: now() - 60 * 60 * 49, read: true, text: 'works for me' }, { from: P_LEAD, mine: false, timestamp: now() - 60 * 60 * 32, text: 'we pick up where we left — in the dragons cave' }, { from: P_ANNA, mine: false, timestamp: now() - 60 * 60 * 32, text: 'excited 🐉' }, ]); } // ── Channel: dchain_updates ──────────────────────────────────── if (contact.username === 'dchain_updates') { return list('dchain_updates', [ { from: peer, mine: false, timestamp: now() - 60 * 60 * 96, text: '🔨 v0.0.1-alpha tagged on Gitea' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 72, text: 'PBFT equivocation-detection тесты зелёные' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 60, text: 'New: /api/peers теперь включает peer-version info' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 48, text: '📘 Docs overhaul merged: docs/README.md' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 36, text: 'Schema migration scaffold landed (no-op для текущей версии)' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 20, text: '🚀 v0.0.1 released' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 20 + 10, text: 'Includes: auto-update from Gitea, peer-version gossip, schema migrations' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 20 + 20, text: 'Check /api/well-known-version for the full feature list' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 12, text: 'Thanks to all testers — feedback drives the roadmap 🙏' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 3, text: 'v0.0.2 roadmap published: https://git.vsecoder.vodka/vsecoder/dchain' }, { from: peer, mine: false, timestamp: now() - 60 * 30, text: 'quick heads-up: nightly builds switching to new docker-slim base' }, ]); } // ── Channel: Relay broadcasts ────────────────────────────────── if (contact.alias === '⚡ Relay broadcasts') { return list('relay_bc', [ { from: peer, mine: false, timestamp: now() - 60 * 60 * 48, text: 'Relay fleet snapshot: 12 active, 3 inactive' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 40, text: 'Relay #3 came online in US-east' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 30, text: 'Validator set updated: 3→4' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 20, text: 'PBFT view-change детектирован и отработан на блоке 184120' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 15, text: 'Mailbox eviction ran — 42 stale envelopes' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 5, text: 'Relay #8 slashed for equivocation — evidence at block 184202' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 2, text: 'Relay #12 came online in EU-west, registering now…' }, ]); } // ── Channel: Tech news ──────────────────────────────────────── if (contact.alias === '📰 Tech news') { return list('tech_news', [ { from: peer, mine: false, timestamp: now() - 60 * 60 * 120, text: 'Rust 1.78 released — new lints for raw pointers' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 100, text: 'Go 1.23 ships range-over-func officially' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 80, text: 'Expo SDK 54 drops — new-architecture default' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 60, text: 'CVE-2026-1337 patched in libsodium (update your keys)' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 40, text: 'Matrix protocol adds post-quantum handshakes' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 30, text: 'Data-center aerial view — new hyperscaler in Iceland', attachment: { kind: 'image', uri: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=800', width: 800, height: 533, mime: 'image/jpeg', }, }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 20, text: 'IETF draft: "DNS-over-blockchain"' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 6, text: 'GitHub tightens 2FA defaults for orgs' }, ]); } // ── Channel: Design inspo (unread=12) ────────────────────────── if (contact.alias === '🎨 Design inspo') { return list('design_inspo', [ { from: peer, mine: false, timestamp: now() - 60 * 60 * 160, text: 'Weekly pick: Linear UI v3 breakdown' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 140, text: 'Figma file of the week: "Command bar patterns"' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 120, text: 'Motion study: Stripe checkout shake-error animation' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 100, text: "10 great empty-state illustrations (blogpost)" }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 80, text: 'Tool: Hatch — colour-palette extractor from photos' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 60, text: '🔮 Trend watch: glassmorphism is back (again)' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 40, text: 'Twitter thread: why rounded buttons are the default' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 20, text: 'Framer templates — black friday sale' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 3, text: 'New typeface: "Grotesk Pro" — free for personal use' }, ]); } // ── Channel: NBA scores ──────────────────────────────────────── if (contact.alias === '🏀 NBA scores') { return list('nba', [ { from: peer, mine: false, timestamp: now() - 60 * 60 * 160, text: 'Lakers 112 — Warriors 108 (OT)' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 130, text: 'Celtics 128 — Heat 115' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 100, text: 'Nuggets 119 — Thunder 102' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 70, text: "Knicks 101 — Bulls 98" }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 48, text: 'Mavericks 130 — Kings 127' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 24, text: 'Bucks 114 — Sixers 110' }, { from: peer, mine: false, timestamp: now() - 60 * 60 * 4, text: 'Live: Lakers leading 78-72 at half' }, ]); } return []; } // ─── Hook ────────────────────────────────────────────────────────── export function useDevSeed() { const contacts = useStore(s => s.contacts); const setContacts = useStore(s => s.setContacts); const setMessages = useStore(s => s.setMessages); useEffect(() => { if (contacts.length > 0) return; setContacts(mockContacts); for (const c of mockContacts) { const msgs = mockMessagesFor(c); if (msgs.length > 0) setMessages(c.address, msgs); } }, [contacts.length, setContacts, setMessages]); }