feat(tracker-web): system banners via @bytelyst/notifications-ui (UX-13.2)

Add @bytelyst/notifications-ui as a workspace dep (minimal link: lockfile entry,
avoiding the env-specific full re-normalization). New SystemBanners component
mounts at the top of the dashboard shell:
  - BannerStack renders dismissible system/maintenance notices from
    NEXT_PUBLIC_SYSTEM_NOTICE (nothing when unset)
  - Announcement shows a localStorage-dismissible "what's new" pill

Defer UX-13.1 (NotificationCenter): tracker has no notifications feed — the
/api/tracker proxy exposes only items/comments/votes/roadmap. The dep + an
import smoke test are in place so a future feed wiring starts from green.

All colors come from the bridged --bl-* tokens; no hardcoded literals.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
saravanakumardb1 2026-05-28 22:08:38 -07:00
parent 3a5196417d
commit 3d22c3031f
6 changed files with 124 additions and 5 deletions

View File

@ -219,11 +219,11 @@ pnpm build # final gate
instead of bespoke buttons; `Tooltip` on truncated titles / status dots.
(SegmentedControl now drives the roadmap board/list toggle — the app's only board↔list
toggle — replacing the bespoke `blue-600` buttons; `Tooltip` wraps the truncated board
card titles. e2e toggle selector updated button→radio. UX-12.1 verified: tc/lint/test 162 ✓/build/e2e 18 ✓)
card titles. e2e toggle selector updated button→radio. UX-12.1 `ddf25cf5`: tc/lint/test 162 ✓/build/e2e 18 ✓)
- [x] **12.2** Item detail: move row/item actions into an `ActionMenu`, and render the item's
activity/comment history with `Timeline`.
(Edit + Delete now live in an `ActionMenu` in the page header; comments render via
`Timeline`. UX-12.2 verified: tc/lint/test 162 ✓/build/e2e 18 ✓.)
`Timeline`. UX-12.2 `32dac7d4`: tc/lint/test 162 ✓/build/e2e 18 ✓.)
- [ ] **12.3** _(stretch — needs HTML-capable description/comment storage)_ Swap the plain
description/comment `<textarea>` for `RichTextEditor`, and render saved content with
`RichTextViewer`. **Only do this if** the backend stores/returns rich HTML safely;
@ -241,8 +241,16 @@ pnpm build # final gate
- [ ] **13.1** Add `@bytelyst/notifications-ui`; mount a `NotificationCenter` bell in the header
(or AppShell from UX-8) fed by the notifications API, with `InboxItem` rows.
- [ ] **13.2** Use `BannerStack` for top-of-page system/maintenance messages and `Announcement`
DEFERRED (data-gated): tracker exposes no notifications feed — the `/api/tracker/*` proxy
surfaces only items/comments/votes/roadmap, with no notifications endpoint. Per the wave
gate, `NotificationCenter`/`InboxItem` are left unbuilt until a feed exists. The dep is
added and the imports are smoke-tested so a future feed wiring starts from green.
- [x] **13.2** Use `BannerStack` for top-of-page system/maintenance messages and `Announcement`
for a dismissible "what's new" pill. **Verify:** `pnpm typecheck && pnpm lint && pnpm build`.
(Added `@bytelyst/notifications-ui` workspace dep with a minimal `link:` lockfile entry;
`SystemBanners` mounts `BannerStack` (env `NEXT_PUBLIC_SYSTEM_NOTICE`, dismissible) +
a localStorage-dismissible `Announcement` at the top of the dashboard shell. UX-13.2
verified: tc/lint/test 165 ✓/build/e2e 18 ✓.)
## Cross-cutting (run continuously)
@ -268,7 +276,7 @@ pnpm build # final gate
```
Core : UX-1 ✅ UX-2 ⬜ UX-3 ⬜ UX-4 ⬜ UX-5 ⬜ UX-6 ⬜ UX-7 ⬜ UX-8 ⬜
Expand : UX-9 ✅ UX-10 ✅ UX-11 ✅ UX-12 ✅ UX-13 ⬜ (stretch: 12.3, 13.*)
Expand : UX-9 ✅ UX-10 ✅ UX-11 ✅ UX-12 ✅ UX-13 ✅ (stretch: 12.3, 13.1 deferred — see notes)
```
**UX-1 is done** (token bridge + Primitives adapter, commit `dc01dd02`) — the `--bl-*` bridge is

View File

@ -31,6 +31,7 @@
"@bytelyst/data-table": "workspace:*",
"@bytelyst/design-tokens": "workspace:*",
"@bytelyst/errors": "workspace:*",
"@bytelyst/notifications-ui": "workspace:*",
"@bytelyst/telemetry-client": "workspace:*",
"@bytelyst/logger": "workspace:*",
"@bytelyst/ui": "workspace:*",

View File

@ -0,0 +1,36 @@
/**
* Smoke test for the @bytelyst/notifications-ui wiring used by the dashboard
* system banners (UX-13.2).
*
* Pure import test (no DOM): guards that the notifications-ui surfaces the
* SystemBanners component depends on resolve and build. NotificationCenter
* (UX-13.1) is intentionally not adopted (no notifications feed) but is asserted
* here so a future feed wiring starts from a known-good import.
*
* @see docs/roadmaps/UX_INTEGRATION_BYTELYST.md (UX-13)
*/
import { describe, it, expect } from 'vitest';
import {
BannerStack,
Announcement,
InboxItem,
NotificationCenter,
} from '@bytelyst/notifications-ui';
describe('notifications-ui wiring', () => {
it('resolves BannerStack (shipped in UX-13.2)', () => {
expect(BannerStack).toBeDefined();
expect(typeof BannerStack).toBe('function');
});
it('resolves Announcement (shipped in UX-13.2)', () => {
expect(Announcement).toBeDefined();
expect(typeof Announcement).toBe('function');
});
it('resolves InboxItem + NotificationCenter (reserved for a future feed, UX-13.1)', () => {
expect(InboxItem).toBeDefined();
expect(NotificationCenter).toBeDefined();
});
});

View File

@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { useAuth } from '@/lib/auth-context';
import { ProductSwitcher } from '@/components/product-switcher';
import { SystemBanners } from '@/components/system-banners';
const NAV_ITEMS = [
{ href: '/dashboard', label: 'Overview' },
@ -65,7 +66,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
</header>
{/* Page content */}
<main className="mx-auto max-w-7xl px-4 py-6">{children}</main>
<main className="mx-auto max-w-7xl px-4 py-6">
<SystemBanners />
{children}
</main>
</div>
);
}

View File

@ -0,0 +1,67 @@
'use client';
import { useEffect, useState } from 'react';
import { BannerStack, Announcement, type BannerItem } from '@bytelyst/notifications-ui';
/**
* Top-of-page system messaging for the dashboard (UX-13.2).
*
* Tracker has no notifications feed yet, so UX-13.1 (NotificationCenter) is
* deferred. Per the wave's data-gate we ship the client-side surfaces only:
* - `BannerStack` renders dismissible system/maintenance notices sourced from
* `NEXT_PUBLIC_SYSTEM_NOTICE` (renders nothing when unset).
* - `Announcement` shows a dismissible "what's new" pill, remembered per
* version in localStorage.
*
* All colors come from the bridged `--bl-*` tokens (no hardcoded literals).
*/
const SYSTEM_NOTICE = process.env.NEXT_PUBLIC_SYSTEM_NOTICE || '';
const WHATS_NEW_KEY = 'tracker_whatsnew_dismissed';
// Bump when the "what's new" copy changes to re-surface it once.
const WHATS_NEW_VERSION = '2026-05-ux';
export function SystemBanners() {
const [banners, setBanners] = useState<BannerItem[]>([]);
const [showWhatsNew, setShowWhatsNew] = useState(false);
useEffect(() => {
if (SYSTEM_NOTICE) {
setBanners([
{
id: 'system-notice',
kind: 'warning',
title: 'System notice',
body: SYSTEM_NOTICE,
},
]);
}
if (typeof window !== 'undefined') {
setShowWhatsNew(localStorage.getItem(WHATS_NEW_KEY) !== WHATS_NEW_VERSION);
}
}, []);
const dismissWhatsNew = () => {
if (typeof window !== 'undefined') {
localStorage.setItem(WHATS_NEW_KEY, WHATS_NEW_VERSION);
}
setShowWhatsNew(false);
};
if (banners.length === 0 && !showWhatsNew) return null;
return (
<div className="mb-6 space-y-3">
<BannerStack
banners={banners}
onDismiss={id => setBanners(prev => prev.filter(b => b.id !== id))}
/>
{showWhatsNew && (
<Announcement tag="NEW" cta={{ label: 'Got it', onSelect: dismissWhatsNew }}>
Tracker now runs on the shared ByteLyst design system consistent header band, accessible
controls, and full dark-mode parity.
</Announcement>
)}
</div>
);
}

3
pnpm-lock.yaml generated
View File

@ -264,6 +264,9 @@ importers:
'@bytelyst/logger':
specifier: workspace:*
version: link:../../packages/logger
'@bytelyst/notifications-ui':
specifier: workspace:*
version: link:../../packages/notifications-ui
'@bytelyst/telemetry-client':
specifier: workspace:*
version: link:../../packages/telemetry-client