Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/features/messages/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Messages UI (feature docs)

Documentation for the Meshflow SPA **text message history** experience (Meshtastic and MeshCore): routes, realtime, and navigation unread badges. Use this tree for feature-level behaviour; older narrative docs also live under [`docs/messages/`](../../messages/README.md) (architecture, roadmap).

**Backend counterpart:** [meshflow-api `docs/features/text-messages/`](https://github.com/pskillen/meshflow-api/blob/main/docs/features/text-messages/README.md)

**Epic:** [meshflow-api#341](https://github.com/pskillen/meshflow-api/issues/341) — messages UI rework ([#277](https://github.com/pskillen/meshflow-ui/issues/277), [#278](https://github.com/pskillen/meshflow-ui/issues/278), [#281](https://github.com/pskillen/meshflow-ui/issues/281)).

## Implementation status

| Area | Status | Notes |
| --------------------------------------------------------- | --------------- | --------------------------------------------------------------------------------------------------- |
| Protocol-scoped pages (`/messages`, `/meshcore/messages`) | Shipped | Shared `ProtocolMessageHistoryPage` |
| REST history + pagination | Shipped | `useMessages` / `useMessagesSuspense` |
| WS live prepend on active channel | Shipped | `useMessagesWithWebSocket` |
| Sidebar unread badge per protocol | Shipped | Requires API WS `protocol` ([#279](https://github.com/pskillen/meshflow-ui/issues/279)) |
| Per-channel unread on messages page | Shipped | Active constellation only — [meshflow-api#396](https://github.com/pskillen/meshflow-api/issues/396) |
| Channel selector button row | Shipped | Interim until [#281](https://github.com/pskillen/meshflow-ui/issues/281) |
| Persisted unread | Not implemented | In-memory session only |

## Documentation map

| Doc | Purpose |
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| [unread-count.md](unread-count.md) | Nav badges, `WebSocketProvider`, mark-as-read, [#279](https://github.com/pskillen/meshflow-ui/issues/279) |
| [../../messages/architecture.md](../../messages/architecture.md) | Routes, components, hooks (broader) |
| [../../messages/current-features.md](../../messages/current-features.md) | List layout, grouping, heard dialog |
| [../../messages/gaps-and-roadmap.md](../../messages/gaps-and-roadmap.md) | Issue mapping, planned UX |

## Concepts

- **Protocol slug** — `'meshtastic' | 'meshcore'` in UI; maps from API `protocol` string or legacy numeric `1`/`2`.
- **Unread** — in-memory list in `WebSocketProvider`; nav sums per protocol; channel buttons per `channel` id.
- **On messages page** — unread skipped only for the **active channel** (`setActiveMessagesView`); other channels still badge; toast suppressed for that protocol’s route only.

## Source map

| Area | Path |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Pages | `src/pages/messages/MessageHistory.tsx`, `src/pages/meshcore/MeshCoreMessages.tsx`, `src/pages/protocol/ProtocolMessageHistoryPage.tsx` |
| List / item | `src/components/messages/MessageList.tsx`, `MessageItem.tsx` |
| Protocol helpers | `src/lib/message-protocol.ts`, `src/lib/message-channels.ts` |
| Data | `src/hooks/api/useMessages.ts`, `src/hooks/useMessagesWithWebSocket.ts` |
| Realtime / nav | `src/providers/WebSocketProvider.tsx`, `src/components/nav-main.tsx` |
| WS client | `src/lib/websocket/websocketService.ts` |

## Related API

- `GET /api/messages/text/` — `protocol`, `channel_id`, `constellation_id`
- `WS /ws/messages/?token=…` — see [API unread-count doc](https://github.com/pskillen/meshflow-api/blob/main/docs/features/text-messages/unread-count.md)
90 changes: 90 additions & 0 deletions docs/features/messages/unread-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Messages UI — unread count

**Purpose:** Sidebar **Messages** badges (per protocol), per-channel badges on the messages page, and mark-as-read behaviour.

**API counterpart:** [meshflow-api `docs/features/text-messages/unread-count.md`](https://github.com/pskillen/meshflow-api/blob/main/docs/features/text-messages/unread-count.md)

**Broader UI context:** [architecture.md](../../messages/architecture.md), [gaps-and-roadmap.md](../../messages/gaps-and-roadmap.md).

**Tracking:** Fixed in [#279](https://github.com/pskillen/meshflow-ui/issues/279). Deferred rollup: [meshflow-api#396](https://github.com/pskillen/meshflow-api/issues/396).

---

## Product behaviour

| Surface | Count | Clear when |
| ---------------------------------- | ------------------------------------ | ------------------------------------------------ |
| Sidebar Messages link (MT / MC) | All unread for that **protocol** | Nav click: `markAsReadForProtocol` |
| Channel button row (messages page) | Unread for **protocol + channel id** | User clicks channel (`markAsReadForChannel`) |
| Active channel while on page | No unread increment | `setActiveMessagesView({ protocol, channelId })` |

Unread is **in-memory only** (lost on refresh). Not synced to the API.

**Toast:** protocol-level only — fires when the user is **not** on that protocol’s messages route (`/messages` or `/meshcore/messages`). Sibling channels on the same page do not toast; unread badges still increment. Toast visibility in production has been unreliable; verify manually after deploy.

**Channel picker:** button row (same styling as constellation buttons), interim until [#281](https://github.com/pskillen/meshflow-ui/issues/281) / epic [#341](https://github.com/pskillen/meshflow-api/issues/341).

---

## Code anchors

| Piece | Path |
| ------------------------------ | --------------------------------------------------- |
| Unread state + helpers | `src/providers/WebSocketProvider.tsx` |
| Messages page + channel badges | `src/pages/protocol/ProtocolMessageHistoryPage.tsx` |
| Protocol derivation | `src/lib/message-protocol.ts` |
| Nav badges | `src/components/nav-main.tsx` |
| WS client | `src/lib/websocket/websocketService.ts` |
| On-page realtime | `src/hooks/useMessagesWithWebSocket.ts` |
| Tests | `src/providers/WebSocketProvider.test.tsx` |

---

## Data flow

```mermaid
sequenceDiagram
participant API as meshflow_api_WS
participant WSP as WebSocketProvider
participant Nav as nav_main
participant Page as ProtocolMessageHistoryPage

API->>WSP: JSON with protocol and channel
WSP->>WSP: skip unread if active channel view
WSP->>Nav: unreadCountForProtocol
WSP->>Page: unreadCountForChannel on buttons
Page->>WSP: setActiveMessagesView on channel change
```

### `WebSocketProvider`

- `unreadMessages: TextMessage[]` — deduped by `message.id` on append.
- `activeMessagesViewRef` — set by messages page; cleared on unmount or when leaving messages routes.
- **Increment:** unless viewing that protocol’s messages page **and** the message’s `channel` matches the active view.
- **Toast:** only when `!isOnMessagesPage(pathname, proto)` (other protocol still toasts while on messages page).
- **Removed:** pathname effect that cleared entire protocol on entering messages pages.

Helpers: `unreadCountForProtocol`, `hasUnreadForProtocol`, `unreadCountForChannel`, `hasUnreadForChannel`, `markAsReadForProtocol`, `markAsReadForChannel`, `setActiveMessagesView`.

### `ProtocolMessageHistoryPage`

- Registers `setActiveMessagesView` when `selectedChannel` changes.
- Channel buttons call `markAsReadForChannel` on user click (not on auto-select; see #396).

---

## Known gaps

| Gap | Notes |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| [meshflow-api#396](https://github.com/pskillen/meshflow-api/issues/396) | Per-channel badges only for **active constellation**; constellation-level rollup; auto-select mark-as-read nuance |
| Toast reliability | May not appear in practice — verify; log separate issue if dead |
| `localStorage` | Unread not persisted across reload |
| `hasUnreadMessages` / `markAllAsRead` | Exposed but unused by nav |

---

## Related

- [README.md](README.md) — messages feature hub
- [meshflow-api unread-count.md](https://github.com/pskillen/meshflow-api/blob/main/docs/features/text-messages/unread-count.md)
17 changes: 11 additions & 6 deletions docs/messages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

Documentation for the Meshflow UI **text message history** experience (Meshtastic and MeshCore). Use this as the baseline before implementing navigation, channel picker, and layout upgrades ([#277](https://github.com/pskillen/meshflow-ui/issues/277), [#278](https://github.com/pskillen/meshflow-ui/issues/278), [#281](https://github.com/pskillen/meshflow-ui/issues/281); parent epic [meshflow-api#341](https://github.com/pskillen/meshflow-api/issues/341)).

**Feature docs (new):** [docs/features/messages/](../features/messages/README.md) — hub including dedicated [unread-count.md](../features/messages/unread-count.md) ([#279](https://github.com/pskillen/meshflow-ui/issues/279)).

## Contents

| Doc | Purpose |
| ----------------------------------------- | ----------------------------------------------------------------------- |
| [Architecture](./architecture.md) | Routes, components, hooks, API/WS integration |
| [Current features](./current-features.md) | Reverse-engineered behaviour today (grouping, layout, unread, realtime) |
| [Gaps and roadmap](./gaps-and-roadmap.md) | Known gaps, issue mapping, design directions |
| Doc | Purpose |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| [Feature hub](../features/messages/README.md) | Unread badges, cross-links to API |
| [Unread count](../features/messages/unread-count.md) | Nav badges, `WebSocketProvider`, [#279](https://github.com/pskillen/meshflow-ui/issues/279) |
| [Architecture](./architecture.md) | Routes, components, hooks, API/WS integration |
| [Current features](./current-features.md) | Reverse-engineered behaviour today (grouping, layout, unread, realtime) |
| [Gaps and roadmap](./gaps-and-roadmap.md) | Known gaps, issue mapping, design directions |

## Product summary

Expand All @@ -29,6 +33,7 @@ Documentation for the Meshflow UI **text message history** experience (Meshtasti

## Related API / backend docs

- [meshflow-api text-messages feature](https://github.com/pskillen/meshflow-api/blob/main/docs/features/text-messages/README.md) — REST + WS; [unread-count](https://github.com/pskillen/meshflow-api/blob/main/docs/features/text-messages/unread-count.md) (WS `protocol` gap)
- OpenAPI: `GET /api/messages/text/` (`channel_id`, `constellation_id`, `protocol`, `sender_node_id`, pagination)
- WebSocket: `/ws/messages/?token=…` — pushes `TextMessage` payloads
- WebSocket: `/ws/messages/?token=…` — pushes `TextMessage` payloads (narrower than REST today)
- Constellations/channels: `Constellation.protocol`, `MessageChannel.protocol`, `display_label`, `mc_channel_type` ([meshflow-api text-message-channels](https://github.com/pskillen/meshflow-api/blob/main/docs/features/meshcore/text-message-channels.md))
2 changes: 2 additions & 0 deletions docs/messages/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ Query keys include `protocol`, `channelId`, `constellationId`, `nodeId`, `pageSi

## WebSocket and unread

Canonical detail: [docs/features/messages/unread-count.md](../features/messages/unread-count.md) and [meshflow-api text-messages/unread-count.md](https://github.com/pskillen/meshflow-api/blob/main/docs/features/text-messages/unread-count.md).

### Connection

- `websocketService` → `ws…/ws/messages/?token=…` (from `config.apis.meshBot.baseUrl`).
Expand Down
21 changes: 10 additions & 11 deletions docs/messages/current-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,20 @@ WS handler does not check `constellation_id` on the payload (only channel id). R

## Unread / notification counts

### Implemented today
See **[docs/features/messages/unread-count.md](../features/messages/unread-count.md)**.

- **Protocol-scoped** unread array in `WebSocketProvider`.
- Sidebar **Messages** link per protocol: red badge with count (max display `9+`).
- Clearing: navigate to that protocol’s messages URL, or click nav link (explicit `markAsReadForProtocol` before navigate).
- **Global** `hasUnreadMessages` / `markAllAsRead` exist but nav uses per-protocol helpers.
### Implemented today

### Not implemented
- **Protocol-scoped** nav badges (`unreadCountForProtocol`); requires API WS `protocol` field.
- **Per-channel** badges on channel button row; active channel suppressed via `setActiveMessagesView`.
- Channel picker: **button row** (not dropdown).
- Clearing: nav link → `markAsReadForProtocol`; channel click → `markAsReadForChannel`.
- Toast: protocol-level only (not per channel).

- Per-**channel** unread badges.
- Per-**constellation** aggregates.
- `localStorage` persistence of unread across reloads (unread is in-memory only).
- Distinction between “unread since last visit” vs total — current model is “messages received while not on that protocol’s page” until cleared.
### Deferred / not implemented

User request: channel-level counts beside a future channel list, plus total on protocol nav link — aligns with extending `unreadMessages` indexing (channel + protocol keys).
- Constellation-level unread rollup — [meshflow-api#396](https://github.com/pskillen/meshflow-api/issues/396).
- `localStorage` persistence across reload.

## Default selection UX

Expand Down
5 changes: 3 additions & 2 deletions docs/messages/gaps-and-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ Likely layout: **constellation tabs** (row 1) + **channel sidebar or chip list**

### Unread model

- **Done:** protocol-separated sidebar badges.
- **Needed:** channel-level counts; optional constellation rollup; persist or rehydrate strategy; define whether counts increment for messages on inactive channels while user stays on messages page.
- **Shipped ([#279](https://github.com/pskillen/meshflow-ui/issues/279)):** protocol nav badges, per-channel badges on messages page, channel button row — [docs/features/messages/unread-count.md](../features/messages/unread-count.md).
- **Deferred ([meshflow-api#396](https://github.com/pskillen/meshflow-api/issues/396)):** constellation-level unread rollup; per-channel badges across constellations; auto-select mark-as-read nuance.
- **Future:** persist or rehydrate unread across reload.

### Layout

Expand Down
5 changes: 5 additions & 0 deletions src/components/nav-main.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ vi.mock('@/providers/WebSocketProvider', () => ({
unreadMessages: [],
markAllAsRead: vi.fn(),
markAsReadForProtocol: vi.fn(),
markAsReadForChannel: vi.fn(),
takeUnreadForChannel: () => [],
setActiveMessagesView: vi.fn(),
unreadCountForProtocol: () => 0,
hasUnreadForProtocol: () => false,
unreadCountForChannel: () => 0,
hasUnreadForChannel: () => false,
}),
}));

Expand Down
Loading
Loading