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:
parent
3a5196417d
commit
3d22c3031f
@ -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
|
||||
|
||||
@ -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:*",
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
67
dashboards/tracker-web/src/components/system-banners.tsx
Normal file
67
dashboards/tracker-web/src/components/system-banners.tsx
Normal 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
3
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user