first commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# Project memory index
|
||||
|
||||
Durable, project-local notes for this repo. Loaded by Claude Code sessions and useful for
|
||||
any developer. One line per note; full content lives in the linked file.
|
||||
|
||||
- [project-overview.md](project-overview.md) — what this repo is and its non-obvious goals
|
||||
- [architecture-decisions.md](architecture-decisions.md) — SDK+BFF+workspace choices and why
|
||||
- [design-system.md](design-system.md) — LynkedUp Pro tokens sampled from the live site
|
||||
- [conventions.md](conventions.md) — coding conventions and gotchas specific to this repo
|
||||
- [iios-relationship.md](iios-relationship.md) — how this maps to the IIOS service at D:\iios
|
||||
- [qa-and-fixes.md](qa-and-fixes.md) — the deep-QA pass and what was fixed (reactions, WYSIWYG composer, dead controls, responsive, backend seam)
|
||||
- [features-and-modes.md](features-and-modes.md) — full feature set, mock/iios modes, how to run IIOS live (no Docker), seed script
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: architecture-decisions
|
||||
description: Key architecture choices for the SDK + BFF + workspace and the reasons
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
**pnpm workspace, two packages.** `packages/messaging-inbox-sdk` (the shareable SDK) and
|
||||
`apps/web` (the demo). The app depends on the SDK via `workspace:*` and Next `transpilePackages`
|
||||
transpiles the SDK's TS source directly (SDK `main`/`types` point at `src/index.ts`, no build
|
||||
step). This mirrors the IIOS layout (packages + apps) and keeps the SDK genuinely reusable.
|
||||
|
||||
**BFF pattern (explicit client requirement).** The UI/SDK only ever calls `BffClient`, which
|
||||
hits `/api/bff/**` (Next route handlers). Every route calls `getBackend()` (a formal `Backend`
|
||||
interface in `apps/web/lib/backend/`), never the store directly. `BFF_BACKEND` env picks the impl:
|
||||
`mock` (default, wraps `lib/mock/store.ts`) or `iios` (`lib/backend/iios.ts` → real IIOS via
|
||||
`lib/iios/client.ts` + `map.ts`). Messages/inbox/tickets are wired to IIOS; bootstrap/channels/
|
||||
reactions/search/notifications delegate to mock until IIOS exposes those endpoints. Flipping to
|
||||
live IIOS is one env var (needs the IIOS service + Docker/Postgres running — see
|
||||
`docs/IIOS_INTEGRATION.md`). This is the single swap seam; the SDK and components never change.
|
||||
|
||||
**Config resolved on the server.** `getServerConfig()` runs in the root layout (server
|
||||
component), reads `NEXT_PUBLIC_*` from `process.env`, returns a plain `SdkConfig`, and passes
|
||||
it to the client `<MessagingInboxProvider config=…>`. Avoids the Next.js gotcha that dynamic
|
||||
`process.env[key]` doesn't work in the browser bundle. Theme vars are also inlined into
|
||||
`<head>` on first paint to prevent a flash.
|
||||
|
||||
**Theme = CSS variables + Tailwind mapping.** All Tailwind colors resolve to `--c-*` vars set
|
||||
on `<html>` at runtime by the provider. Light/dark and brand re-skin are just different
|
||||
variable values; nothing is hard-coded. `darkMode: 'class'`.
|
||||
|
||||
**Feature flags are data, not conditionals scattered around.** One `FeatureFlags` interface;
|
||||
`NEXT_PUBLIC_FEATURE_<UPPER_SNAKE>` overrides each. Components call `useFeature("x")`.
|
||||
|
||||
**Why not build the NestJS/DB backend?** The ask was a static demo with everything working.
|
||||
A NestJS BFF + Postgres adds setup cost with no demo benefit; it's documented as the
|
||||
production path instead (`docs/IIOS_INTEGRATION.md`, `docs/BFF.md`).
|
||||
|
||||
Related: [[design-system]], [[conventions]], [[iios-relationship]].
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: conventions
|
||||
description: Repo-specific coding conventions and gotchas
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
- **Client boundary:** any component using SDK hooks or `useUi` must start with `"use client"`.
|
||||
Route `page.tsx` files stay thin server components that render a client view.
|
||||
- **Never hard-code colors/radii.** Use semantic Tailwind tokens (`bg-panel`, `text-muted`,
|
||||
`border-border`, `bg-accent`, `rounded-card`, `rounded-control`) so light/dark + live theming
|
||||
keep working. Tokens are defined in `apps/web/tailwind.config.ts` → CSS vars.
|
||||
- **Time formatting** is client-only via `apps/web/lib/format.ts` to avoid SSR hydration drift;
|
||||
data (messages/tickets) is fetched client-side through the BFF, not server-rendered.
|
||||
- **BFF params are async in Next 15:** handlers use `{ params }: { params: Promise<…> }` and
|
||||
`await params`.
|
||||
- **Next config** sets `typescript.ignoreBuildErrors` + `eslint.ignoreDuringBuilds` so the demo
|
||||
runs even with strict-mode nits; run `pnpm typecheck` for a clean check.
|
||||
- **No `packageManager` pin** in root `package.json` on purpose — a pin triggered corepack to
|
||||
fetch a non-installed pnpm version and fail offline. Use the local pnpm.
|
||||
- **Mock store is in-memory** and resets on restart; persistence isn't a goal for the demo.
|
||||
- Adding a data feature touches 4 files in order: `store.ts` → `bff route` → `client.ts` →
|
||||
`hooks.ts` → then the component. See [[architecture-decisions]].
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: design-system
|
||||
description: LynkedUp Pro design tokens sampled from the live dashboard (defaults for this app)
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
Tokens sampled live from `https://lynkeduppro-crmnew.vercel.app/dashboard` (computed styles),
|
||||
used as the **default theme**. All are overridable via `NEXT_PUBLIC_THEME_*` or the in-app
|
||||
Appearance panel. Source of truth in code: `packages/messaging-inbox-sdk/src/config.ts`.
|
||||
|
||||
**Accent:** `#FDA913` (golden amber). Solid usage ~0.92 opacity; soft tints at 0.12
|
||||
(`rgba(253,169,19,0.12)`).
|
||||
|
||||
**Dark surfaces:** app bg `#060608`, sidebar `#08080b`, panel/card `#0e0e13`, elevated
|
||||
`#15151c`. Borders `rgba(255,255,255,0.08)`. Text: ink `#ededf1`, muted `#8c8c94`, dim `#63636b`.
|
||||
|
||||
**Light surfaces:** app bg `#f6f7f9`, surface/panel `#ffffff`, borders `rgba(9,10,14,0.10)`,
|
||||
ink `#14151a`, muted `#5c6069`. Accent nudged to `#e8920a` for contrast on white.
|
||||
|
||||
**Radii:** cards **20px**, controls/buttons **10px**, pills 999px.
|
||||
**Font:** Inter (system-ui fallback). **Layout:** dark sectioned sidebar + user card footer;
|
||||
top header with search, theme toggle, notifications, user menu (matches the reference exactly).
|
||||
|
||||
**Brand gradient:** `linear-gradient(135deg,#FDA913,#ff7a3d,#ff4d6d)`.
|
||||
|
||||
If the reference site restyles, re-sample with the browser devtools/`getComputedStyle` and
|
||||
update the token blocks in `config.ts` + defaults in `app/globals.css`.
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
name: features-and-modes
|
||||
description: The full Slack-grade feature set, the two backend modes, and how to run IIOS live
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
**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 in `apps/web/lib/mock/data.ts` + `store.ts`.
|
||||
- `iios` — proxies the live IIOS NestJS service (real Postgres). Seeded via
|
||||
`node scripts/seed-iios.mjs` which writes `apps/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]].
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: iios-relationship
|
||||
description: How this demo relates to the IIOS service/SDKs at D:\iios (reference only)
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
The **IIOS** monorepo lives at `D:\iios` — a NestJS service (`packages/iios-service`, port
|
||||
3200) plus React SDKs (`iios-message-web`, `iios-inbox-web`, `iios-support-web`,
|
||||
`iios-community-web`, `iios-ai-web`, `iios-meeting-web`) over a low-level `iios-kernel-client`.
|
||||
It treats every interaction (message, ticket, route decision, AI proposal, meeting) as one
|
||||
governed `Interaction` behind fail-closed policy/consent gates.
|
||||
|
||||
**This repo is standalone and does NOT import or call IIOS.** IIOS was used only to model the
|
||||
domain vocabulary and the surfaces to build. Deliberate parallels so a later swap is easy:
|
||||
|
||||
| This repo | IIOS analogue |
|
||||
|---|---|
|
||||
| `Message` / `Channel` / thread | `Message` / `Conversation`(`IiosThread`) / parent-child interactions |
|
||||
| `InboxItem` (NEEDS_REPLY, MENTION…) | inbox projector items (`iios-inbox-web`) |
|
||||
| `Ticket` (state/priority, escalate, callback) | `iios-support-web` tickets/escalate/callbacks |
|
||||
| `BffClient` → `/api/bff/**` | `RestClient` → `iios-service` `/v1/**` |
|
||||
| feature flags / theming | app-level concerns (IIOS is backend-governed) |
|
||||
|
||||
**Live wiring is built AND proven.** The BFF uses a `Backend` interface (`apps/web/lib/backend/`);
|
||||
`BFF_BACKEND=iios` swaps in `iios.ts` → real `iios-service` via `lib/iios/client.ts` (dev-token
|
||||
auth) + `map.ts` (shape mappers). This was run end-to-end: IIOS service on :3200 against a native
|
||||
Postgres (role `iios`/`iios`, db `iios` on **:5432** — no Docker needed; a separate :5434 cluster
|
||||
crashed under the sandbox's process/shared-memory limits, so the existing service on 5432 was used),
|
||||
9 migrations applied, an ingested message read back through this app's BFF. Default stays mock
|
||||
(rich demo data; fresh IIOS DB is empty and has no channel-directory endpoint). Exact steps:
|
||||
`docs/IIOS_INTEGRATION.md`. Related: [[architecture-decisions]], [[qa-and-fixes]].
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: project-overview
|
||||
description: What this repo is, who it's for, and the non-obvious constraints
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
This repo (`message-inbox-web-frontend-sdk`) is a **static frontend demo** of a Slack-grade
|
||||
team-messaging + inbox + support product, delivered as a reusable **React SDK**
|
||||
(`@lynkd/messaging-inbox-sdk`) plus a **Next.js demo app** (`apps/web`).
|
||||
|
||||
**Non-obvious goals (from the requesting stakeholder):**
|
||||
- It must **look like** the LynkedUp Pro CRM dashboard (dark + amber accent `#FDA913`), and
|
||||
support **light AND dark** modes. The "Support Center" page especially should feel native
|
||||
to that CRM.
|
||||
- It must match **Slack's feature depth** (channels, DMs, threads, reactions, pins, mentions,
|
||||
presence, huddles, rich composer, scheduled send, search, saved) — as a **static** demo
|
||||
first (mock data, no real backend).
|
||||
- **Every feature toggled by an env flag** (`NEXT_PUBLIC_FEATURE_*`), and the **UI fully
|
||||
themeable** (accent/radius/gradient) via env or a live in-app panel, defaulting to the
|
||||
LynkedUp look.
|
||||
- Must use the **BFF pattern** (client requested this explicitly). Here the BFF = Next.js
|
||||
route handlers under `app/api/bff/**`.
|
||||
- Stack the stakeholder named: Next.js (frontend), NestJS (backend), Postgres/Supabase.
|
||||
For the static demo we use Next.js + the Next route-handler BFF only; NestJS/DB are a
|
||||
documented later swap, **not** built.
|
||||
- The real IIOS SDK/service lives at `D:\iios` and was used **only as reference** — this app
|
||||
is deliberately standalone and does not import or call it. See [[iios-relationship]].
|
||||
|
||||
Deadline context: originally requested as a ~2-hour demo. Prioritize a working, good-looking
|
||||
demo over completeness. See [[architecture-decisions]] and [[design-system]].
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: qa-and-fixes
|
||||
description: The deep-QA pass and what was fixed (reactions, composer, dead controls, responsive)
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
A deep QA pass (a 5-agent static audit → 68 findings, plus live Playwright driving) fixed the
|
||||
issues the stakeholder reported and more. All verified in-browser + typecheck clean.
|
||||
|
||||
**Headline fixes:**
|
||||
- **Reactions** — the hover toolbar's emoji picker used to unmount on `mouseleave` (it floats in a
|
||||
gap outside the row), so reacting was impossible for messages without existing reactions. Fixed:
|
||||
toolbar stays while the picker/menu is open; picker also works in the thread panel (which had
|
||||
no handlers wired). `useThread` now exposes react/pin/save.
|
||||
- **Composer** — was a plain textarea inserting literal `**` markers. Now a **contentEditable
|
||||
WYSIWYG** (`Composer.tsx`) where Bold/Italic/Strike/Code/lists format live and serialize to
|
||||
markdown on send; `lib/rich-text.tsx` renders it (added `~~strike~~` + `[label](url)`).
|
||||
- **Dead controls → real** — create-channel (real BFF create + navigate + appears in sidebar),
|
||||
new-message, invite, add-workspace, callback, channel-details are modals (`overlays/Modals.tsx`
|
||||
+ `ui/Modal.tsx`); huddle mute/video/share toggle + live timer; profile Message/Huddle; palette
|
||||
person/file; workspace switcher; a toast system (`Toaster.tsx`, `useUi().toast`).
|
||||
- **Layout/scroll "not fit"** — responsive down to ~400px: sidebar drawer closes on nav; thread
|
||||
panel is a mobile overlay; Support master-detail stacks (list ↔ detail + back button); no
|
||||
horizontal overflow.
|
||||
- **Support state** — list + detail + stats now share one `useTickets` source, so Resolve/reply
|
||||
update everywhere at once.
|
||||
- **Theme flash** — pre-paint script in `layout.tsx` + light tokens under `html:not(.dark)` in
|
||||
`globals.css`; ThemingPanel System mode works (matchMedia).
|
||||
|
||||
See [[architecture-decisions]] and [[conventions]].
|
||||
Reference in New Issue
Block a user