# tracker-web — Backend Enablers

> Follow-ups that **cannot ship from `dashboards/tracker-web` alone** because they require a change
> to the shared `services/platform-service`. They are the only remaining items on the
> [UX integration roadmap](UX_INTEGRATION_BYTELYST.md) and are **excluded from its ✅ count** until
> the backend enabler lands.
>
> **Hard constraint for every item below:** `platform-service` is **shared by 9 products**
> (LysnrAI, MindLyst, ChronoMind, JarvisJr, NomGap, PeakPulse, FlowMonk, NoteLett, ActionTrail,
> EffoRise, LocalMemGPT — see `AGENTS.md`). Every change here **must be additive and
> backward-compatible**: no behavioural change for products that do not opt in, existing rows/reads
> keep working, and every persisted document keeps its `productId`.

| ID   | Title                                                 | Blocks                         | Target module                                                             | Status         |
| ---- | ----------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------- | -------------- |
| BE-1 | Server-side HTML sanitization for item/comment bodies | UX-12.3 (rich-text)            | `services/platform-service` — items + comments write paths                | 🔒 Not started |
| BE-2 | Tracker-event notifications feed                      | UX-13.1 (`NotificationCenter`) | `services/platform-service` — notifications module + `/api/tracker` proxy | 🔒 Not started |

---

## BE-1 — Server-side HTML sanitization for `items.description` + `comments.body`

- **Title:** Sanitize rich HTML on the item-description and comment-body write paths.
- **Blocking roadmap item:** [UX-12.3](UX_INTEGRATION_BYTELYST.md#ux-12--detail--board-richness-tabs--tooltip--drawer--timeline--rich-text)
  — adopt `@bytelyst/rich-text` `RichTextEditor` / `RichTextViewer` in tracker-web.
- **Target module:** `services/platform-service` — the **items** module (`items.description`) and the
  **comments** module (`comments.body`), applied **server-side before persist** (create + update).
- **Why it's blocked:** Today `TrackerItem.description` and `Comment.body` are plain `string`s
  rendered with `whitespace-pre-wrap`; the `/api/tracker/*` proxy neither stores nor sanitizes rich
  HTML. Adopting a rich-text editor client-side would persist attacker-controlled HTML with no
  server-side sanitization (stored-XSS), so it must not be done until the backend guarantees safety.

### Acceptance criteria

- HTML is sanitized **on the server** (never trust the client) on every write to `items.description`
  and `comments.body` (create and update).
- **Allowlist** of formatting tags only — e.g. `p`, `br`, `strong`, `em`, `u`, `s`, `a`,
  `ul`/`ol`/`li`, `blockquote`, `code`, `pre`, `h1`–`h3`. Everything else is stripped/escaped.
- **Attribute allowlist:** only safe attributes survive; `a[href]` is restricted to
  `http:` / `https:` / `mailto:` schemes (and gets `rel="noopener noreferrer"`).
- **Stripped unconditionally:** `<script>` / `<style>` / `<iframe>` / `<object>`, all inline
  event-handler attributes (`on*`), and `javascript:` / `data:` URLs.
- Output is idempotent (sanitizing already-sanitized content is a no-op) and length-bounded as today.
- **Backward-compatible:** existing plain-text rows still read correctly; products that send plain
  text are unaffected; the response shape is unchanged. **No behavioural change for the other 8
  products** sharing `platform-service`.
- Unit tests cover the XSS vectors above (script injection, `onerror=`, `javascript:` href,
  `data:` URI, mismatched/oversized tags).

### Unblocks (tracker-web side, once shipped)

- Swap the description/comment `<textarea>` for `RichTextEditor` (compose) and render saved content
  with `RichTextViewer` — no client-side `dangerouslySetInnerHTML` of unsanitized content.

---

## BE-2 — Tracker-event notifications feed

- **Title:** Emit + expose a per-user notifications feed for tracker events.
- **Blocking roadmap item:** [UX-13.1](UX_INTEGRATION_BYTELYST.md#ux-13--notifications-surface-via-bytelystnotifications-ui-stretch--data-gated)
  — mount `NotificationCenter` (bell + `InboxItem` rows) in tracker-web.
- **Target module:** `services/platform-service` — its **existing notifications module** (event
  emission) + the `/api/tracker` proxy (read surface).
- **Why it's blocked:** tracker exposes **no notifications feed** — the `/api/tracker/*` proxy
  surfaces only items / comments / votes / roadmap. `NotificationCenter`/`InboxItem` have nothing to
  bind to, so only the client-side `BannerStack`/`Announcement` (UX-13.2) shipped.

### Acceptance criteria

- Tracker events emit notifications into the **existing** notifications module — at minimum:
  **new comment**, **status change**, and **vote milestone** (e.g. crossing 10/25/50 votes).
- Each notification is **fanned out to the item author and any subscribers/watchers** of the item,
  and is **stamped with `productId`** so the feed is product-scoped.
- A read API (list with pagination, unread count, mark-as-read / mark-all-read) is **exposed through
  the `/api/tracker` proxy** so the dashboard reads it with the existing auth token.
- Payload shape is compatible with `@bytelyst/notifications-ui` `InboxItem` (id, title/body, type,
  timestamp, read flag, deep-link to the item).
- **Additive / backward-compatible:** emitting these notifications causes **no behavioural change for
  the 9 products that do not subscribe**; the notifications module contract is extended, not broken;
  every notification document carries `productId`.
- Tests cover emission on each event type, author/subscriber targeting, product scoping, and the
  unread-count / mark-as-read transitions.

### Unblocks (tracker-web side, once shipped)

- Bind `NotificationCenter` to the feed (bell + `InboxItem` rows) in the AppShell sidebar/header,
  reading via the `/api/tracker` proxy.
