5.8 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| features-and-modes | The full Slack-grade feature set, the two backend modes, and how to run IIOS live |
|
Two backend modes (env BFF_BACKEND in apps/web/.env.local, read server-side):
mock(default) — rich in-memory JSON workspace; every feature works & persists in-process. Best for demos. Data inapps/web/lib/mock/data.ts+store.ts.iios— proxies the live IIOS NestJS service (real Postgres). Seeded vianode scripts/seed-iios.mjswhich writesapps/web/lib/iios/seed-manifest.json(real thread ids → channel directory). Bootstrap/channels come from the manifest; messages/inbox/tickets from IIOS live. Verified: 5 channels + 6 users + messages render through the app BFF.
Running IIOS live (no Docker): native PostgreSQL 18, role iios/iios, db iios on 5432
(a separate 5434 cluster crashes under the sandbox's process limits). packages/iios-service/.env
→ DATABASE_URL=postgresql://iios:iios@localhost:5432/iios?schema=public, IIOS_DEV_TOKENS=1.
Build: allow prisma/esbuild in D:\iios\pnpm-workspace.yaml, pnpm install && pnpm -r build,
prisma generate + migrate deploy, node packages/iios-service/dist/main.js. Redis NOT needed
(in-memory socket adapter without REDIS_URL). Full steps: docs/IIOS_INTEGRATION.md.
Slack-grade features (all working, verified via Playwright): channels/DMs/threads;
WYSIWYG composer (bold/italic/code/lists); @mention + #channel autocomplete (typing pops a
picker; rendered as blue chips by lib/rich-text.tsx); full searchable emoji picker (168,
categorized, lib/emoji.ts) in composer + reactions + status; :shortcode: → emoji (:tick:
→ ✅); reactions (hover picker fixed); pins; save (+ Saved page); message edit + delete;
attachment upload (file → data-URL preview → send → image preview + Open/Download); scheduled
send; huddles; status (emoji picker + clear-after incl. custom datetime); avatar change
(color swatches + image upload); copy message link + jump-to-message (scroll); thread "also
send to channel"; command palette (⌘K, arrow-nav); notifications; sidebar collapse; inbox;
Support Center; light/dark + live theming.
AI + translation (Gemini, server-side only): apps/web/lib/gemini.ts calls Gemini
(?key= auth, model gemini-2.0-flash) and returns a discriminated result so 429/errors degrade
gracefully. Two BFF routes: POST /api/bff/ai (assistant) and POST /api/bff/translate. Client
methods client.ai(prompt, context?) / client.translate(text, lang) use reqSoft (does NOT
throw on non-2xx — returns the {ok:false,error} body so the UI shows a clean message). UI:
AiPanel right-drawer (open via openAi({context}) from a message's ✨ toolbar button; quick
actions Summarize / What-should-I-reply / Improve / Explain + free prompt); per-message Translate
(LanguageMenu searchable dropdown → inline translated card w/ "Show original"); whole-chat
Translate (header ✈ button → banner + autoTranslateTo prop on every MessageItem); composer
translate-before-send (✈ toolbar btn → pick language → preview popover → "Use translation" replaces
draft). Flags aiAssist + translation. KEY is server-side only (GEMINI_API_KEY, no
NEXT_PUBLIC_; verified not in client bundle). The provided free-tier key is quota-exhausted
(429) — wiring is proven end-to-end; real output needs a fresh/paid key.
More Slack features added: presence toggle Active/Away/DND (ProfileDrawer → Availability →
client.updateMe({presence})); typing indicator moved to a fixed bar ABOVE the composer (incl.
DMs); channel create — public adds the whole team automatically, private shows a multi-user
selector; invite modal turns emails into chips on ,/Enter; thread composer is the FULL
editor, thread replies have edit/delete; editor lists: Enter=new bullet (doesn't send), Tab/Shift-
Tab indent/outdent; cross-view live sync via notifyChannelChanged mutation bus (delete + also-
send reflect without refresh); threadRootId "Replied to a thread → open" chip; copy-message
toolbar button.
Jump-to-message flash (working in a production build): pnpm --filter @lynkd/web build +
start; StrictMode double-mount is gone so the WAAPI amber animate() on [data-mid-body] fires.
ChannelView reads ?msg via useSearchParams() (jumpMsg) and the effect is keyed on
[hasMessages, channelId, jumpMsg] — gate on the boolean hasMessages = messages.length>0, NOT
the raw count, or every send/incoming message re-fires the jump and yanks the viewport back to
the old message. As soon as the target resolves (or after ~2.5s of not finding it) the effect
strips ?msg with router.replace(pathname,{scroll:false}) so it's one-shot AND re-clicking the
same result re-fires (identical-URL push is a no-op otherwise). Search + notification results now
deep-link with ?msg= (CommandPalette.go, NotificationsPanel; Notification/mock data gained an
optional messageId; the Liam-mention notif/inbox point at m_21 in d_liam, not m_23 which is
d_ava). A search hit on a thread reply (msg with parentId) opens the thread panel
(openThread) instead of a ?msg= jump that would never find a main-list node — SearchResult
gained parentId. In dev the flash is unreliable (node churn); the scroll always works.
Editor lists: inside a list BOTH Enter (native) and Shift+Enter (execCommand insertParagraph)
make the next bullet/number; Tab/Shift-Tab indent 3 levels. Outside a list Enter sends, Shift+Enter
soft-newlines. Composer has a visible amber "✨ AI" button (flags.aiAssist) → openAi().
Gotchas: reactStrictMode:false in next.config. Bump __lynkdStore_vN in store.ts after
adding store methods (HMR singleton). See qa-and-fixes, iios-relationship, architecture-decisions.