Commit Graph

36 Commits

Author SHA1 Message Date
maaz519 272c6acd31 fix(messaging): a sent message no longer shows "Seen" instantly
Two independent defects; either alone caused it, in DMs, groups and channels.

1. useMessages reported a read of the newest NON-PENDING message regardless of
   author. Sending therefore made the client immediately mark its own message
   read, the server echoed a receipt for it, and the sender's bubble showed
   "Seen" before anyone had opened the thread. You do not read your own message:
   lastReadableId now skips your own.

2. The guard meant to catch exactly this — `e.actorId !== currentActorId` — could
   never fire: the receipt carries the reader's IIOS actor UUID while clients hold
   a userId, so the comparison was always true and every receipt, including your
   own, counted as the other side. The gateway now also emits userId on READ and
   DELIVERED receipts (matching what `annotation` already did), and ReceiptEvent
   carries it as optional so older servers still typecheck.

MockAdapter.markRead was a no-op, so it could not exercise any of this; it now
echoes a receipt like a real server. Regression test verified to fail without
the fix. SDK 0.1.14; suite 17 files / 101 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 17:57:23 +05:30
maaz519 eb0ace8ad7 fix(messaging-ui): composer height was being clobbered by a class-name collision
The chat composer reused .miu-textarea, which the INBOX MAIL composer already
owned further down the stylesheet with `resize: vertical; min-height: 90px`.
Equal specificity, later rule wins — so the mail styling applied to the chat box:
90px tall with a resize grabber, and every height fix in 0.1.10–0.1.12 was
silently overridden. That is why the box never changed.

The composer now uses its own .miu-composer-box; the inbox rule is untouched.
Adds a regression test asserting the composer does not carry .miu-textarea.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 17:57:23 +05:30
maaz519 e2f66fe622 fix(messaging-ui): composer input back to its original 38px height
0.1.11 left it at 40px with line-height 1.45, so the line (20.3px) overflowed
the 20px content box — taller than the <input> it replaced, and liable to show a
scrollbar on a single line.

Collapsed height is now one token, --miu-composer-h: 38px, shared by the
textarea, attach and send so they cannot drift apart again. Padding tightened to
8px (from .miu-input's 9px) with line-height 1.4, so a 14px line is 19.6 + 16 + 2
= 37.6px and fits exactly — matching the original single-line input.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 17:57:23 +05:30
maaz519 9a0c74cb6e fix(messaging-ui): composer no longer stretches the send/attach buttons
The textarea swap made the composer row tall and dragged the controls with it.
Three causes, all fixed:

- No box-sizing anywhere in the stylesheet, so `min-height: 38px` on a padded,
  bordered textarea rendered ~58px (content-box adds 18px padding + 2px border
  on top). The textarea is now border-box with a 40px min-height — the same
  height the old single-line input had.
- .miu-composer-row is display:flex with no align-items, so it defaulted to
  `stretch` and the buttons — neither of which declared a height — grew to the
  row. Now align-items: flex-end, so controls stay pinned to the bottom while
  the box grows upward (WhatsApp behaviour), with an explicit 40px on both.
  The send height is scoped to .miu-composer so modal/settings buttons keep
  their own sizing.
- The auto-grow effect set height = scrollHeight, which under content-box
  double-counted padding on every keystroke. It now compensates for the border
  explicitly, correct under border-box.

Bumped to 0.1.11. SDK suite: 17 files / 98 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 17:57:23 +05:30
maaz519 0c8eaaf74b fix(messaging-ui): unreadable code chip on own messages + raw markers in previews
Two bugs from the formatting work, both visible in the CRM messenger:

1. Inline code / code blocks were invisible in your OWN bubbles. `.miu-code` sets
   background: --miu-panel-2 (#1d1d26) while the is-mine override set only the
   colour to --miu-accent-text (#1a1206) — near-black on near-black, ~1.03:1
   contrast. The chip now tints the accent bubble (rgba(0,0,0,.16)) instead of
   using the panel colour, so it reads against any accent.

2. The conversation list showed the raw last message, so a strikethrough message
   previewed as "~crazy~". Adds stripMarkup() — markers off, code unwrapped,
   nesting handled, honouring the same word-boundary rule so snake_case_name and
   "5 * 3" survive — and uses it for the preview line.

Bumped to 0.1.10. SDK suite: 17 files / 98 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 17:57:23 +05:30
maaz519 c946f1e061 feat(messaging-ui): message formatting + native spellcheck in the composer
Composer becomes a <textarea> so the PLATFORM supplies text services: red
spellcheck squiggles, right-click suggestions / add-to-dictionary, and mobile
autocorrect (spellCheck + autoCorrect + autoCapitalize). Nothing shipped for it.
Previously a single-line <input>, which Firefox does not spellcheck by default
(layout.spellcheckDefault=1 checks multi-line only) and which could not hold a
multi-line message at all. Enter sends, Shift+Enter (and IME composition) makes a
newline, and the box grows with content.

WhatsApp-style markup: *bold*, _italic_, ~strike~, `code`, ```fenced blocks```,
plus bare URLs. Cmd/Ctrl+B/I/E and a small toolbar wrap the selection in markers.
Messages stay PLAIN TEXT on the wire, so stored history and older clients are
unaffected — formatting is purely a render concern.

renderRichText() returns a ReactNode tree and never uses dangerouslySetInnerHTML,
so message text cannot inject markup; links are restricted to http/https/mailto
(safeHref) to close the javascript: vector. Code is tokenized first and its
contents stay literal; markers require word boundaries so snake_case_name and
"5 * 3" are not mangled.

Bumped to 0.1.9. SDK suite: 17 files / 95 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 16:43:03 +05:30
maaz519 3d08fa42f8 feat(messaging-ui): import a channel's roster from the settings panel
Phase C of channel-roster import. Adds the optional addMembers(threadId, userIds)
adapter seam (+ BulkAddResult) and, in ConversationSettings, a '#' mode on the
existing Add-people box that lists the channels you belong to — public AND
private, since the source list is your own membership-scoped conversation list.

Picking a channel STAGES the import (reads its roster, diffs against who is
already here) and shows a confirm — 'Add 9 people from #design? (3 already here)'
— so an administrative action never fires on a stray click. The result line
reports added / already-a-member / failed. Absent addMembers => the affordance is
hidden entirely and '#' is just a search string.

Bumped to 0.1.8. SDK suite: 15 files / 78 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 16:43:03 +05:30
maaz519 745a23823a feat(messaging-ui): presence + live-unread adapter seams
Add optional MessagingAdapter.setFocus(threadId) and subscribeActivity(cb)
so hosts can report the foregrounded thread (backend suppresses push for it)
and drive live conversation-list refresh on any thread's activity. Messenger
wires focus/blur presence + activity-driven refetch. Published as 0.1.7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 16:00:49 +05:30
maaz519 b3eb027071 feat(messaging-ui): focusThreadId on Messenger + Inbox for search deep-links (0.1.6)
A host can pass focusThreadId to select/open a specific conversation — used by the
CRM's global search to jump to the exact thread. Mail switches to Open + selects the
matching item; messenger selects the thread. (Source for the already-published 0.1.6.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:52:15 +05:30
maaz519 416cf59dc2 feat(messaging-ui): message timestamps + group/channel settings panel (0.1.5)
- Every message now shows a Slack-style time (clock today, then 'Yesterday',
  weekday, else a date; full timestamp on hover).
- New ConversationSettings panel for groups AND channels (public + private):
  rename, member list, add people (from the directory), remove, and leave.
  Opened from a new thread header gear; DMs show no gear. Adapter gains
  addMember/removeMember/renameConversation (optional, OPA still gates writes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:03:06 +05:30
maaz519 973b6a77eb feat(messaging-ui): Attachment carries contentRef + sizeBytes for messenger media (0.1.4)
Extend the messaging Attachment type with optional storage fields so an adapter's
upload() can return a sendable reference (the socket needs contentRef/mimeType/
sizeBytes to persist the message part) while keeping the display url. Enables
messenger attachments end-to-end in host adapters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 03:10:31 +05:30
maaz519 3c5c6964e2 fix(messaging-ui): mail cards no longer clip + emoji picker portals above overflow (0.1.3)
- Mail reader: .miu-mail-msg gets flex:0 0 auto so cards keep their natural height
  in the scrolling column instead of being compressed + clipped by overflow:hidden.
- Reaction/emoji picker renders through a PopoverPortal to <body> (positioned +
  themed) so it floats above the message list instead of being cut by the scroll
  container's overflow. 72 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 03:10:31 +05:30
maaz519 22eaf0f654 feat(messaging-ui): Gmail-style uploading chip while an attachment uploads (0.1.2)
Picking a file now shows an immediate chip with the filename + a spinner while
the bytes upload (mail compose, mail reply, and the messenger composer), instead
of only appearing once upload finishes. Respects prefers-reduced-motion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 01:04:52 +05:30
maaz519 8878ee8c54 feat(messaging-ui): mail attachment download + scrollable reader (0.1.1)
- Attachment chips in the mail reader are now clickable: new InboxAdapter
  downloadAttachment() resolves a short-lived URL, opened in a new tab.
- Mail reader scrolls again: convert the .miu-detail/.miu-mail height:100%
  chain to flex fill so a long thread scrolls within the pane instead of
  being clipped. MockInboxAdapter implements download. Bump to 0.1.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:53:08 +05:30
maaz519 c4254749a4 fix(messaging-ui): stop Start-chat/Create buttons stretching full width
The new-conversation + channel-create forms are flex columns, so the submit
button stretched to full width and read as thin. Give it align-self:flex-start
+ a 38px height so it sizes to its label like a normal button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:21 +05:30
maaz519 4faf05fee9 feat(messaging-ui): start direct messages + groups (Slack-style people picker)
Add a 'New message' + on the Direct messages section that opens a people picker
(NewConversation): pick one person -> DM, two or more -> group with an optional
name, via the adapter's existing openThread({participantIds, membership, subject}).
Expose directory() on MessagingAdapter (org people list); MockAdapter implements
it + dedupes 1:1 DMs. 72 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:21 +05:30
maaz519 9882177c59 fix(messaging-ui): portal compose modal to <body> so it renders above host chrome
A host wrapper like .view{position:relative;z-index:1} creates a stacking context
that traps the modal's fixed overlay below a sibling sticky header, no matter its
z-index. Render the modal through a ModalPortal (createPortal to document.body) so
it escapes the host stacking context + overflow entirely; the portal root copies
the SDK theme tokens from the live surface (synchronously, no unstyled paint) and
carries dark defaults as a fallback. Adds react-dom peer dep. 67 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:21 +05:30
maaz519 2aa2893973 feat(messaging-ui): attachments in inbox mail — reply + compose upload
InboxAdapter gains uploadAttachment + attachment params on mailReply/
composeInternal/composeExternal. MailReader reply and ComposeModal grow an
attach affordance (paperclip + pending chips); mail history renders sent
attachments. MockInboxAdapter implements upload. 67 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:21 +05:30
maaz519 2f24a95ef4 feat(messaging-ui): Inbox domain — unified work items + mail (contract + UI + mock)
Second domain in the SDK, mirroring messaging's adapter/provider/hooks/components:
- InboxAdapter (listInbox unified feed + transition + mailHistory/mailReply +
  optional directory/composeInternal/composeExternal), InboxProvider/useInboxAdapter
- hooks: useInbox (filter + transition), useMailThread (history + reply), useCompose
- <Inbox> (filter tabs, unified list, detail pane), <MailReader> (HTML in a
  sandboxed iframe + reply), compose modal (in-app / email); themeable styles
- MockInboxAdapter (seeded mentions/needs-reply/alert + folded mail threads +
  directory) shipped from ./adapters/mock-inbox
- 6 inbox tests (mock unify/transition/reply + render open/reply/compose); 64 green

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:21 +05:30
maaz519 d02cd152de fix(messaging-ui): reaction picker opens downward, no longer clips
The emoji picker used bottom:100% and was clipped by the message list's
overflow when reacting to a message near the top (and could spill past the
right edge). Open it downward + edge-anchored (right for others' messages,
left for my own) with width:max-content so all emojis stay visible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:21 +05:30
maaz519 fa93173b1f feat(messaging-ui): Slack-style threaded-replies side panel
Replies (messages with parentInteractionId) now open in a right-hand ThreadPane
instead of inline quoting:
- extracted a shared Composer (draft + @mention autocomplete + attach) and
  MessageItem (bubble + mentions + attachment + reactions + reply affordance)
- Thread shows top-level messages only; a message with replies gets a
  '💬 N replies' link, and hovering shows 💬 'Reply in thread'
- ThreadPane renders the root + its replies + a composer that posts back with
  parentInteractionId; Messenger becomes 3-column (list | thread | pane),
  pane closes on conversation switch
- no contract/adapter/backend change (parentInteractionId was already wired)
- thread-pane render test (open → reply → parent shows the count); 58 green

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:21 +05:30
maaz519 7a0fb2ca97 feat(messaging-ui): reaction picker + attachments in the Thread
- useMessages exposes upload(); Thread gains a hover reaction picker (react to
  a message) and an attach button (upload → stage → send), plus inline image /
  file rendering of message attachments — all capability-degrading (hidden when
  the adapter lacks react/upload)
- themeable styles for the picker, attach button, staged chip, and attachments
- 57 tests still green

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:21 +05:30
maaz519 ade1d68015 feat: channels on the direct-IIOS path — kernel-client + KernelClientAdapter
- iios-kernel-client: RestClient.discoverThreads (GET /v1/threads/discover) +
  leaveThread (DELETE /v1/threads/:id/me); DiscoveredThread type
- KernelClientAdapter implements the four channel methods: browseChannels maps
  discovered public channels → ChannelSummary; createChannel → createThread
  (membership=channel, ADMIN, visibility/topic metadata); joinChannel →
  socket.openThread (governed public self-join); leaveChannel → rest.leaveThread
- adapter channel test over the fake transport (13 kernel-adapter tests); 57 green

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:21 +05:30
maaz519 cb9a0b9250 feat(messaging-ui): @mentions — autocomplete, resolve to ids, highlight
- contract: SendOpts.mentions (opaque userId notify-list) + optional
  listMembers(threadId) for autocomplete/highlight (capability-degrading)
- mock: listMembers; KernelClientAdapter.send forwards mentions to the socket
  (which already carries them → inbox MENTION items)
- composer: type @ → member/@channel/@here autocomplete; Enter picks the first;
  on send, mentions resolve to ids; message bodies highlight @mentions
- useMembers hook; mentions.tsx helpers (trailingMentionQuery/insertMention/
  resolveMentions/highlightMentions)
- 4 mention tests (helpers + render autocomplete→send carries id); 56 total green

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:21 +05:30
maaz519 00a2cc474a feat(messaging-ui): channels — browse, join, leave, create (contract + mock + UI)
Adds a third membership beyond dm/group: a discoverable, joinable room.

- contract: Membership += 'channel'; Conversation.topic; ChannelSummary +
  CreateChannelInput; optional adapter methods browseChannels/createChannel/
  joinChannel/leaveChannel (capability-degrading — absent hides the UI)
- MockAdapter implements them (seeds a joined public, a joinable public, and a
  private channel); listConversations now returns only threads I'm in
- useChannels hook (browse/create/join/leave + supported flag)
- UI: sidebar sections (Channels vs Direct messages), a + that opens a
  ChannelBrowser (join + create), # / lock glyphs; themeable styles
- KernelClientAdapter passes channel membership + topic through
- 4 channel tests (mock browse/join/create + render browse→join); 52 total green

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:20 +05:30
maaz519 3f1f89dfbe feat(messaging-ui): rendered Messenger components — the drop-in UI
Turns the SDK from hooks-only into a real UI SDK: <Messenger> (conversation
list + open thread + composer), plus <ConversationList> and <Thread>, all
driven by the existing useConversations/useMessages hooks over the injected
adapter — zero transport imports (boundary intact).

- themeable via --miu-* CSS variables; default theme ships as ./styles.css
- optimistic send, typing indicator, seen ticks, reactions display, unread badges
- render smoke tests (jsdom + MockAdapter): mounts, auto-selects first thread,
  sends a message, switches threads (3 tests) — 48 total green
- tsup: css bundled to dist/styles.css; dts scoped to TS entries

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:20 +05:30
maaz519 c5237a237a feat(messaging-ui): KernelClientAdapter — direct-IIOS transport for the SDK
Implements MessagingAdapter over @insignia/iios-kernel-client (browser → IIOS,
token-in): listConversations/openThread/history/send/subscribe/typing/markRead
/react, mapping kernel Message/ThreadSummary/annotations onto the SDK's neutral
types. currentActorId comes from the host's session (senderId space), never
inferred from history — the exact bug the conformance suite kills.

- lives in adapters/ (transport boundary intact — core stays transport-free)
- ships from the ./adapters/kernel-client subpath (kernel-client is an optional
  peer dep; excluded from the main bundle)
- connectKernelAdapter({serviceUrl, token, currentUserId}) convenience
- passes the shared adapter conformance suite (12 tests) over the real
  MessageSocket facade with only the socket.io layer faked
- media/attachments deferred (kernel-client has no presign yet)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:15:20 +05:30
maaz519 778e98134c feat: export messaging-ui public API and enforce transport boundary
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 00:15:20 +05:30
maaz519 99f9ac84fb feat: add useMessages hook with explicit ownership and optimistic send
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 00:15:20 +05:30
maaz519 0ffe21a0c2 feat: add useConversations hook 2026-07-23 00:15:20 +05:30
maaz519 256a5dcea0 feat: add MessagingProvider and useAdapter 2026-07-23 00:15:20 +05:30
maaz519 54f426e4f0 feat: add MockAdapter passing the conformance suite
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 00:15:20 +05:30
maaz519 0273d1c70f feat: add adapter conformance suite 2026-07-23 00:15:20 +05:30
maaz519 3e9951b3a8 feat: define MessagingAdapter contract 2026-07-23 00:15:20 +05:30
maaz519 0c9b27684b feat: add messaging-ui domain types 2026-07-23 00:15:20 +05:30
maaz519 167171a682 chore: scaffold @insignia/iios-messaging-ui with jsdom test setup
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 00:15:17 +05:30