@gntik-ai/templates 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2420 -0
- package/dist/index.js +14520 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -3
- package/src/accept-invitation/Page.test.tsx +40 -0
- package/src/accept-invitation/Page.tsx +153 -0
- package/src/accept-invitation/data.ts +22 -0
- package/src/accept-invitation/template.meta.ts +12 -0
- package/src/assistant-chat/Page.test.tsx +51 -0
- package/src/assistant-chat/Page.tsx +273 -0
- package/src/assistant-chat/data.ts +71 -0
- package/src/assistant-chat/template.meta.ts +12 -0
- package/src/audit-log/Page.test.tsx +58 -0
- package/src/audit-log/Page.tsx +284 -0
- package/src/audit-log/data.ts +133 -0
- package/src/audit-log/template.meta.ts +12 -0
- package/src/board/Lane.tsx +117 -0
- package/src/board/Page.test.tsx +68 -0
- package/src/board/Page.tsx +178 -0
- package/src/board/data.ts +127 -0
- package/src/board/template.meta.ts +12 -0
- package/src/board/useBoard.ts +146 -0
- package/src/changelog/Page.test.tsx +31 -0
- package/src/changelog/Page.tsx +174 -0
- package/src/changelog/data.ts +105 -0
- package/src/changelog/template.meta.ts +12 -0
- package/src/code-editor/Page.test.tsx +83 -0
- package/src/code-editor/Page.tsx +207 -0
- package/src/code-editor/data.ts +130 -0
- package/src/code-editor/template.meta.ts +12 -0
- package/src/contact/Page.test.tsx +33 -0
- package/src/contact/Page.tsx +272 -0
- package/src/contact/PublicFrame.tsx +84 -0
- package/src/contact/data.ts +129 -0
- package/src/contact/template.meta.ts +12 -0
- package/src/create-wizard/Overlay.test.tsx +68 -0
- package/src/create-wizard/Page.test.tsx +57 -0
- package/src/create-wizard/Page.tsx +246 -0
- package/src/create-wizard/data.ts +139 -0
- package/src/create-wizard/examples/Overlay.tsx +60 -0
- package/src/create-wizard/template.meta.ts +12 -0
- package/src/create-workspace/Page.test.tsx +41 -0
- package/src/create-workspace/Page.tsx +227 -0
- package/src/create-workspace/data.ts +61 -0
- package/src/create-workspace/template.meta.ts +12 -0
- package/src/data-explorer/Page.test.tsx +45 -0
- package/src/data-explorer/Page.tsx +247 -0
- package/src/data-explorer/data.ts +94 -0
- package/src/data-explorer/template.meta.ts +12 -0
- package/src/docs-article/ArticleFeedback.tsx +75 -0
- package/src/docs-article/Page.test.tsx +34 -0
- package/src/docs-article/Page.tsx +200 -0
- package/src/docs-article/data.ts +65 -0
- package/src/docs-article/template.meta.ts +12 -0
- package/src/evaluations/Page.test.tsx +40 -0
- package/src/evaluations/Page.tsx +238 -0
- package/src/evaluations/data.ts +194 -0
- package/src/evaluations/template.meta.ts +12 -0
- package/src/forgot-password/Page.test.tsx +199 -0
- package/src/forgot-password/Page.tsx +66 -0
- package/src/forgot-password/data.ts +5 -0
- package/src/forgot-password/examples/UsernameOrEmail.tsx +25 -0
- package/src/forgot-password/template.meta.ts +12 -0
- package/src/getting-started/Page.test.tsx +27 -0
- package/src/getting-started/Page.tsx +125 -0
- package/src/getting-started/data.ts +67 -0
- package/src/getting-started/template.meta.ts +12 -0
- package/src/help-home/Page.test.tsx +33 -0
- package/src/help-home/Page.tsx +159 -0
- package/src/help-home/data.ts +151 -0
- package/src/help-home/template.meta.ts +12 -0
- package/src/home-dashboard/Page.test.tsx +57 -0
- package/src/home-dashboard/Page.tsx +152 -0
- package/src/home-dashboard/data.ts +66 -0
- package/src/home-dashboard/template.meta.ts +12 -0
- package/src/incident-board/Page.test.tsx +77 -0
- package/src/incident-board/Page.tsx +263 -0
- package/src/incident-board/data.ts +220 -0
- package/src/incident-board/template.meta.ts +12 -0
- package/src/incident-console/Page.test.tsx +49 -0
- package/src/incident-console/Page.tsx +274 -0
- package/src/incident-console/data.ts +176 -0
- package/src/incident-console/template.meta.ts +12 -0
- package/src/index.ts +250 -0
- package/src/keyboard-shortcuts/Page.test.tsx +45 -0
- package/src/keyboard-shortcuts/Page.tsx +94 -0
- package/src/keyboard-shortcuts/ShortcutSheet.tsx +80 -0
- package/src/keyboard-shortcuts/data.ts +84 -0
- package/src/keyboard-shortcuts/template.meta.ts +12 -0
- package/src/landing/Page.test.tsx +27 -0
- package/src/landing/Page.tsx +110 -0
- package/src/landing/data.ts +17 -0
- package/src/landing/template.meta.ts +11 -0
- package/src/live-event-stream/Page.test.tsx +251 -0
- package/src/live-event-stream/Page.tsx +307 -0
- package/src/live-event-stream/data.ts +44 -0
- package/src/live-event-stream/examples/Connecting.tsx +5 -0
- package/src/live-event-stream/examples/Error.tsx +5 -0
- package/src/live-event-stream/examples/LiveEmpty.tsx +5 -0
- package/src/live-event-stream/examples/LiveWithEvents.tsx +6 -0
- package/src/live-event-stream/examples/Stopped.tsx +6 -0
- package/src/live-event-stream/template.meta.ts +74 -0
- package/src/messaging/Page.test.tsx +34 -0
- package/src/messaging/Page.tsx +236 -0
- package/src/messaging/data.ts +118 -0
- package/src/messaging/template.meta.ts +12 -0
- package/src/meta.ts +24 -0
- package/src/mfa/Page.test.tsx +22 -0
- package/src/mfa/Page.tsx +45 -0
- package/src/mfa/data.ts +6 -0
- package/src/mfa/template.meta.ts +12 -0
- package/src/model-providers/Page.test.tsx +58 -0
- package/src/model-providers/Page.tsx +198 -0
- package/src/model-providers/data.ts +101 -0
- package/src/model-providers/template.meta.ts +12 -0
- package/src/notification-inbox/Page.test.tsx +49 -0
- package/src/notification-inbox/Page.tsx +237 -0
- package/src/notification-inbox/data.ts +129 -0
- package/src/notification-inbox/template.meta.ts +12 -0
- package/src/pending-activation/Page.test.tsx +390 -0
- package/src/pending-activation/Page.tsx +145 -0
- package/src/pending-activation/data.ts +25 -0
- package/src/pending-activation/template.meta.ts +12 -0
- package/src/pricing/ComparisonTable.tsx +75 -0
- package/src/pricing/Page.test.tsx +29 -0
- package/src/pricing/Page.tsx +83 -0
- package/src/pricing/PublicFrame.tsx +84 -0
- package/src/pricing/data.ts +104 -0
- package/src/pricing/template.meta.ts +12 -0
- package/src/prompt-playground/Page.test.tsx +72 -0
- package/src/prompt-playground/Page.tsx +254 -0
- package/src/prompt-playground/data.ts +68 -0
- package/src/prompt-playground/template.meta.ts +12 -0
- package/src/public-hub/Page.test.tsx +176 -0
- package/src/public-hub/Page.tsx +125 -0
- package/src/public-hub/Source.test.ts +54 -0
- package/src/public-hub/data.ts +24 -0
- package/src/public-hub/examples/Dark.tsx +11 -0
- package/src/public-hub/examples/Full.tsx +6 -0
- package/src/public-hub/examples/HighContrast.tsx +11 -0
- package/src/public-hub/examples/Light.tsx +11 -0
- package/src/public-hub/examples/Loading.tsx +6 -0
- package/src/public-hub/examples/Minimal.tsx +5 -0
- package/src/public-hub/examples/MinimalLoading.tsx +5 -0
- package/src/public-hub/template.meta.ts +60 -0
- package/src/reset-password/Page.test.tsx +27 -0
- package/src/reset-password/Page.tsx +46 -0
- package/src/reset-password/data.ts +5 -0
- package/src/reset-password/template.meta.ts +12 -0
- package/src/resource-detail/Page.test.tsx +260 -0
- package/src/resource-detail/Page.tsx +262 -0
- package/src/resource-detail/data.ts +55 -0
- package/src/resource-detail/examples/postgres-table-detail.tsx +28 -0
- package/src/resource-detail/examples/realtime-configuration.tsx +26 -0
- package/src/resource-detail/examples/runtime-with-transition.tsx +30 -0
- package/src/resource-detail/examples/runtime-without-transition.tsx +33 -0
- package/src/resource-detail/examples/shell.ts +12 -0
- package/src/resource-detail/template.meta.ts +21 -0
- package/src/resource-form/Page.test.tsx +40 -0
- package/src/resource-form/Page.tsx +186 -0
- package/src/resource-form/data.ts +63 -0
- package/src/resource-form/template.meta.ts +12 -0
- package/src/resource-gallery/Page.test.tsx +42 -0
- package/src/resource-gallery/Page.tsx +125 -0
- package/src/resource-gallery/data.ts +44 -0
- package/src/resource-gallery/template.meta.ts +20 -0
- package/src/resource-index/Page.test.tsx +120 -0
- package/src/resource-index/Page.tsx +216 -0
- package/src/resource-index/data.ts +84 -0
- package/src/resource-index/template.meta.ts +21 -0
- package/src/resource-tree/Page.test.tsx +31 -0
- package/src/resource-tree/Page.tsx +232 -0
- package/src/resource-tree/data.ts +180 -0
- package/src/resource-tree/template.meta.ts +12 -0
- package/src/run-trace/Page.test.tsx +56 -0
- package/src/run-trace/Page.tsx +242 -0
- package/src/run-trace/data.ts +171 -0
- package/src/run-trace/template.meta.ts +12 -0
- package/src/scorecard/Page.test.tsx +38 -0
- package/src/scorecard/Page.tsx +160 -0
- package/src/scorecard/TargetsTable.tsx +92 -0
- package/src/scorecard/data.ts +118 -0
- package/src/scorecard/template.meta.ts +12 -0
- package/src/search-results/Page.test.tsx +43 -0
- package/src/search-results/Page.tsx +243 -0
- package/src/search-results/data.ts +130 -0
- package/src/search-results/template.meta.ts +12 -0
- package/src/settings-api-keys/Page.test.tsx +39 -0
- package/src/settings-api-keys/Page.tsx +171 -0
- package/src/settings-api-keys/data.ts +58 -0
- package/src/settings-api-keys/template.meta.ts +12 -0
- package/src/settings-appearance/Page.test.tsx +44 -0
- package/src/settings-appearance/Page.tsx +92 -0
- package/src/settings-appearance/data.ts +21 -0
- package/src/settings-appearance/template.meta.ts +11 -0
- package/src/settings-billing/Page.test.tsx +31 -0
- package/src/settings-billing/Page.tsx +71 -0
- package/src/settings-billing/data.ts +15 -0
- package/src/settings-billing/template.meta.ts +19 -0
- package/src/settings-integrations/Page.test.tsx +31 -0
- package/src/settings-integrations/Page.tsx +185 -0
- package/src/settings-integrations/data.ts +116 -0
- package/src/settings-integrations/template.meta.ts +11 -0
- package/src/settings-members/Page.test.tsx +32 -0
- package/src/settings-members/Page.tsx +140 -0
- package/src/settings-members/data.ts +28 -0
- package/src/settings-members/template.meta.ts +12 -0
- package/src/settings-notifications/Page.test.tsx +31 -0
- package/src/settings-notifications/Page.tsx +71 -0
- package/src/settings-notifications/data.ts +12 -0
- package/src/settings-notifications/template.meta.ts +11 -0
- package/src/settings-profile/Baseline.test.tsx +9 -0
- package/src/settings-profile/Editable.test.tsx +21 -0
- package/src/settings-profile/Page.test.tsx +41 -0
- package/src/settings-profile/Page.tsx +133 -0
- package/src/settings-profile/ReadOnly.test.tsx +143 -0
- package/src/settings-profile/ReadOnlyProfile.tsx +85 -0
- package/src/settings-profile/SettingsFrame.test.tsx +55 -0
- package/src/settings-profile/SettingsFrame.tsx +88 -0
- package/src/settings-profile/__tests__/dom.ts +8 -0
- package/src/settings-profile/data.ts +41 -0
- package/src/settings-profile/examples/ReadOnly.tsx +31 -0
- package/src/settings-profile/fixtures/editable.snap +1 -0
- package/src/settings-profile/settings-nav.ts +98 -0
- package/src/settings-profile/template.meta.ts +21 -0
- package/src/settings-security/MfaSetupDialog.tsx +96 -0
- package/src/settings-security/Page.test.tsx +44 -0
- package/src/settings-security/Page.tsx +158 -0
- package/src/settings-security/data.ts +11 -0
- package/src/settings-security/template.meta.ts +11 -0
- package/src/settings-workspace/Page.test.tsx +44 -0
- package/src/settings-workspace/Page.tsx +120 -0
- package/src/settings-workspace/data.ts +44 -0
- package/src/settings-workspace/template.meta.ts +19 -0
- package/src/shared/ConsoleShell.test.tsx +81 -0
- package/src/shared/ConsoleShell.tsx +98 -0
- package/src/shell-sidebar/Page.test.tsx +21 -0
- package/src/shell-sidebar/Page.tsx +85 -0
- package/src/shell-sidebar/data.ts +24 -0
- package/src/shell-sidebar/template.meta.ts +12 -0
- package/src/shell-topnav/Page.test.tsx +25 -0
- package/src/shell-topnav/Page.tsx +94 -0
- package/src/shell-topnav/data.ts +25 -0
- package/src/shell-topnav/template.meta.ts +12 -0
- package/src/sign-in/BrandPanel.tsx +34 -0
- package/src/sign-in/Page.test.tsx +41 -0
- package/src/sign-in/Page.tsx +73 -0
- package/src/sign-in/data.ts +11 -0
- package/src/sign-in/template.meta.ts +12 -0
- package/src/sign-in-card/Page.test.tsx +182 -0
- package/src/sign-in-card/Page.tsx +82 -0
- package/src/sign-in-card/data.ts +8 -0
- package/src/sign-in-card/examples/Username.tsx +28 -0
- package/src/sign-in-card/template.meta.ts +12 -0
- package/src/sign-up/Page.test.tsx +33 -0
- package/src/sign-up/Page.tsx +70 -0
- package/src/sign-up/data.ts +15 -0
- package/src/sign-up/template.meta.ts +12 -0
- package/src/sso-login/Page.test.tsx +41 -0
- package/src/sso-login/Page.tsx +151 -0
- package/src/sso-login/data.ts +10 -0
- package/src/sso-login/template.meta.ts +12 -0
- package/src/status-403/Page.test.tsx +23 -0
- package/src/status-403/Page.tsx +92 -0
- package/src/status-403/data.ts +13 -0
- package/src/status-403/template.meta.ts +12 -0
- package/src/status-404/Page.test.tsx +23 -0
- package/src/status-404/Page.tsx +60 -0
- package/src/status-404/PageHeading.test.tsx +52 -0
- package/src/status-404/data.ts +12 -0
- package/src/status-404/examples/PageHeader.tsx +28 -0
- package/src/status-404/template.meta.ts +12 -0
- package/src/status-500/Page.test.tsx +24 -0
- package/src/status-500/Page.tsx +70 -0
- package/src/status-500/data.ts +20 -0
- package/src/status-500/template.meta.ts +12 -0
- package/src/status-maintenance/Page.test.tsx +24 -0
- package/src/status-maintenance/Page.tsx +84 -0
- package/src/status-maintenance/data.ts +14 -0
- package/src/status-maintenance/template.meta.ts +12 -0
- package/src/styles.css +11 -0
- package/src/test/a11y.ts +18 -0
- package/src/test/setup.ts +36 -0
- package/src/upgrade-required/Page.test.tsx +28 -0
- package/src/upgrade-required/Page.tsx +79 -0
- package/src/upgrade-required/data.ts +19 -0
- package/src/upgrade-required/template.meta.ts +12 -0
- package/src/usage-analytics/Page.test.tsx +83 -0
- package/src/usage-analytics/Page.tsx +184 -0
- package/src/usage-analytics/data.ts +161 -0
- package/src/usage-analytics/template.meta.ts +21 -0
- package/src/verify-email/Page.test.tsx +32 -0
- package/src/verify-email/Page.tsx +118 -0
- package/src/verify-email/data.ts +7 -0
- package/src/verify-email/template.meta.ts +12 -0
- package/src/workflow-builder/Page.test.tsx +97 -0
- package/src/workflow-builder/Page.tsx +163 -0
- package/src/workflow-builder/data.ts +13 -0
- package/src/workflow-builder/template.meta.ts +12 -0
- package/README.md +0 -3
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ErrorPanel,
|
|
3
|
+
FormSection,
|
|
4
|
+
InlineCallout,
|
|
5
|
+
ListSkeleton,
|
|
6
|
+
LogViewer,
|
|
7
|
+
PageHeader,
|
|
8
|
+
type LogLine,
|
|
9
|
+
} from '@gntik-ai/blocks';
|
|
10
|
+
import {
|
|
11
|
+
Button,
|
|
12
|
+
EmptyState,
|
|
13
|
+
Field,
|
|
14
|
+
FieldLabel,
|
|
15
|
+
HStack,
|
|
16
|
+
Input,
|
|
17
|
+
InspectorLayout,
|
|
18
|
+
InspectorToggle,
|
|
19
|
+
JsonViewer,
|
|
20
|
+
Page,
|
|
21
|
+
StatusDot,
|
|
22
|
+
Toggle,
|
|
23
|
+
ToggleGroup,
|
|
24
|
+
useInspectorLayout,
|
|
25
|
+
} from '@gntik-ai/ui';
|
|
26
|
+
import { useMemo, useState, type ReactNode } from 'react';
|
|
27
|
+
import { ConsoleShell, type ConsoleShellProps } from '../shared/ConsoleShell';
|
|
28
|
+
import {
|
|
29
|
+
emptyStreamEvents,
|
|
30
|
+
streamDescription,
|
|
31
|
+
streamEventTypes,
|
|
32
|
+
streamSource,
|
|
33
|
+
streamTitle,
|
|
34
|
+
type LiveEvent,
|
|
35
|
+
} from './data';
|
|
36
|
+
|
|
37
|
+
export type { LiveEvent } from './data';
|
|
38
|
+
|
|
39
|
+
export type LiveEventStreamStatus = 'idle' | 'connecting' | 'live' | 'error' | 'stopped';
|
|
40
|
+
|
|
41
|
+
const CONNECTION = {
|
|
42
|
+
idle: { label: 'Not subscribed', tone: 'neutral', pulse: false },
|
|
43
|
+
connecting: { label: 'Connecting', tone: 'info', pulse: true },
|
|
44
|
+
live: { label: 'Live', tone: 'success', pulse: true },
|
|
45
|
+
error: { label: 'Error', tone: 'destructive', pulse: false },
|
|
46
|
+
stopped: { label: 'Stopped', tone: 'warning', pulse: false },
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Controlled subscription form, quiet windowed stream and pinnable JSON inspector.
|
|
51
|
+
* LiveEventStreamPage accepts Partial<LiveEventStreamProps>; omitted props use fixtures.
|
|
52
|
+
* No fetching, transport or subscription lifecycle is implemented here. Events must have
|
|
53
|
+
* unique ids and arrive oldest first. The newest maxEvents entries are retained.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* ARIA: one PageHeader h1; labelled FormSection; visible StatusDot text; a single polite,
|
|
57
|
+
* atomic connection status region. Only status changes and explicit consumer-throttled
|
|
58
|
+
* countAnnouncement updates change that region, never events.length. LogViewer uses
|
|
59
|
+
* announceLines=false (role=log, aria-live=off). Connecting replaces the log with decorative
|
|
60
|
+
* skeletons in an aria-busy section. ErrorPanel is wrapped in role=alert; validation uses
|
|
61
|
+
* InlineCallout. InspectorLayout supplies the complementary panel on desktop and a Drawer
|
|
62
|
+
* on small screens. Selection exposes aria-pressed on the row's inspect button.
|
|
63
|
+
* Pinning docks the panel without freezing selection. Eviction retains the last inspected
|
|
64
|
+
* payload and displays "This event is no longer in the buffer."
|
|
65
|
+
*
|
|
66
|
+
* Keyboard table:
|
|
67
|
+
* | Key | Behavior |
|
|
68
|
+
* | --- | --- |
|
|
69
|
+
* | Tab / Shift+Tab | Move through form, action, filters, search, Follow, inspect and copy. |
|
|
70
|
+
* | Enter / Space | Activate buttons, select an event, toggle Follow or pinning. |
|
|
71
|
+
* | Arrow keys | Navigate event-type and log-level ToggleGroups and the JSON tree. |
|
|
72
|
+
* | Escape | Close an unpinned inspector from its panel or toggle; return focus to the toggle. Pinned panels ignore Escape; use Close panel. |
|
|
73
|
+
* Scrolling up more than one row disables Follow and preserves the scroll offset.
|
|
74
|
+
* Enabling Follow pins the viewport to the bottom, where new events are appended.
|
|
75
|
+
*
|
|
76
|
+
* Themes: dark (default), light and high_contrast use only kit tokens. StatusDot pulses
|
|
77
|
+
* for connecting/live are disabled by prefers-reduced-motion. Sensitive fields in form
|
|
78
|
+
* must use the kit PasswordInput; the fixture contains only a non-sensitive Source field.
|
|
79
|
+
*/
|
|
80
|
+
export interface LiveEventStreamProps {
|
|
81
|
+
/** PageHeader h1. */
|
|
82
|
+
title: string;
|
|
83
|
+
/** Supporting text below the page title. */
|
|
84
|
+
description: string;
|
|
85
|
+
/** Consumer's subscription fields. Sensitive fields must use the kit PasswordInput. */
|
|
86
|
+
form: ReactNode;
|
|
87
|
+
/** Controlled connection state; defaults to idle. No transport is started by the template. */
|
|
88
|
+
status: LiveEventStreamStatus;
|
|
89
|
+
/** Controlled events in arrival order, oldest first. Defaults to the empty fixture. */
|
|
90
|
+
events: LiveEvent[];
|
|
91
|
+
/** Newest events retained (default 500). Nonnegative integers; invalid values use 500. */
|
|
92
|
+
maxEvents: number;
|
|
93
|
+
/** Type toggles, initially all enabled. Unlisted types remain visible. Defaults to fixture types. */
|
|
94
|
+
eventTypes: string[];
|
|
95
|
+
/** Optional consumer-throttled count text. Never inferred from events.length. */
|
|
96
|
+
countAnnouncement: string;
|
|
97
|
+
/** Called by Subscribe in idle, stopped or error. */
|
|
98
|
+
onSubscribe: () => void;
|
|
99
|
+
/** Called by Stop while live. */
|
|
100
|
+
onStop: () => void;
|
|
101
|
+
/** Called with the selected event after mouse or keyboard inspection. */
|
|
102
|
+
onSelectEvent: (event: LiveEvent) => void;
|
|
103
|
+
/** Stream error summary, shown in an ErrorPanel with role=alert when status is error. */
|
|
104
|
+
error: string;
|
|
105
|
+
/** Form validation feedback, shown in a destructive InlineCallout. */
|
|
106
|
+
validationMessage: string;
|
|
107
|
+
/** Explicit console navigation, workspace, user and topbar slots. */
|
|
108
|
+
shell: Omit<ConsoleShellProps, 'children'>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function EventLog({
|
|
112
|
+
lines,
|
|
113
|
+
selectedLineId,
|
|
114
|
+
onSelectLine,
|
|
115
|
+
}: {
|
|
116
|
+
lines: LogLine[];
|
|
117
|
+
selectedLineId?: string;
|
|
118
|
+
onSelectLine: (line: LogLine) => void;
|
|
119
|
+
}) {
|
|
120
|
+
const { setOpen, panelId, docked, pinned } = useInspectorLayout();
|
|
121
|
+
return (
|
|
122
|
+
<LogViewer
|
|
123
|
+
title="Events"
|
|
124
|
+
titleAs="h2"
|
|
125
|
+
lines={lines}
|
|
126
|
+
announceLines={false}
|
|
127
|
+
selectedLineId={selectedLineId}
|
|
128
|
+
onSelectLine={(line) => {
|
|
129
|
+
onSelectLine(line);
|
|
130
|
+
setOpen(true);
|
|
131
|
+
if (docked && !pinned) requestAnimationFrame(() => document.getElementById(panelId)?.focus());
|
|
132
|
+
}}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default function LiveEventStreamPage({
|
|
138
|
+
title = streamTitle,
|
|
139
|
+
description = streamDescription,
|
|
140
|
+
form = (
|
|
141
|
+
<Field>
|
|
142
|
+
<FieldLabel>Source</FieldLabel>
|
|
143
|
+
<Input defaultValue={streamSource} />
|
|
144
|
+
</Field>
|
|
145
|
+
),
|
|
146
|
+
shell,
|
|
147
|
+
status = 'idle',
|
|
148
|
+
onSubscribe,
|
|
149
|
+
onStop,
|
|
150
|
+
onSelectEvent,
|
|
151
|
+
error = 'The subscription failed. Check the parameters and try again.',
|
|
152
|
+
validationMessage,
|
|
153
|
+
events = emptyStreamEvents,
|
|
154
|
+
maxEvents = 500,
|
|
155
|
+
eventTypes = streamEventTypes,
|
|
156
|
+
countAnnouncement,
|
|
157
|
+
}: Partial<LiveEventStreamProps>) {
|
|
158
|
+
const [inspectorOpen, setInspectorOpen] = useState(false);
|
|
159
|
+
const [excludedTypes, setExcludedTypes] = useState<string[]>([]);
|
|
160
|
+
const [selected, setSelected] = useState<LiveEvent | null>(null);
|
|
161
|
+
const connection = CONNECTION[status];
|
|
162
|
+
const limit = Number.isInteger(maxEvents) && maxEvents >= 0 ? maxEvents : 500;
|
|
163
|
+
const buffer = useMemo(() => events.slice(Math.max(0, events.length - limit)), [events, limit]);
|
|
164
|
+
const eventsById = useMemo(() => new Map(buffer.map((event) => [event.id, event])), [buffer]);
|
|
165
|
+
const lines = useMemo<LogLine[]>(
|
|
166
|
+
() =>
|
|
167
|
+
buffer
|
|
168
|
+
.filter((event) => !excludedTypes.includes(event.type))
|
|
169
|
+
.map((event) => ({
|
|
170
|
+
id: event.id,
|
|
171
|
+
timestamp: event.timestamp,
|
|
172
|
+
level: 'info',
|
|
173
|
+
source: event.type,
|
|
174
|
+
message: event.summary ?? event.type,
|
|
175
|
+
})),
|
|
176
|
+
[buffer, excludedTypes],
|
|
177
|
+
);
|
|
178
|
+
return (
|
|
179
|
+
<ConsoleShell {...shell}>
|
|
180
|
+
<InspectorLayout
|
|
181
|
+
open={inspectorOpen}
|
|
182
|
+
onOpenChange={setInspectorOpen}
|
|
183
|
+
defaultPinned={false}
|
|
184
|
+
panelTitle="Event inspector"
|
|
185
|
+
panel={
|
|
186
|
+
selected ? (
|
|
187
|
+
<>
|
|
188
|
+
<JsonViewer
|
|
189
|
+
key={selected.id}
|
|
190
|
+
data={selected.payload}
|
|
191
|
+
label="Event payload"
|
|
192
|
+
defaultExpandDepth={2}
|
|
193
|
+
toolbar={false}
|
|
194
|
+
/>
|
|
195
|
+
{!eventsById.has(selected.id) && (
|
|
196
|
+
<p className="mt-4 text-sm text-muted-foreground">This event is no longer in the buffer.</p>
|
|
197
|
+
)}
|
|
198
|
+
</>
|
|
199
|
+
) : (
|
|
200
|
+
<p className="text-sm text-muted-foreground">Select an event to inspect its payload.</p>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
>
|
|
204
|
+
<Page
|
|
205
|
+
width="wide"
|
|
206
|
+
header={
|
|
207
|
+
<PageHeader
|
|
208
|
+
title={title}
|
|
209
|
+
description={description}
|
|
210
|
+
breadcrumbs={null}
|
|
211
|
+
status=""
|
|
212
|
+
meta={[]}
|
|
213
|
+
tabs={null}
|
|
214
|
+
actions={[]}
|
|
215
|
+
/>
|
|
216
|
+
}
|
|
217
|
+
>
|
|
218
|
+
<FormSection
|
|
219
|
+
title="Subscription parameters"
|
|
220
|
+
description="Choose the source to subscribe to."
|
|
221
|
+
actions={
|
|
222
|
+
<Button disabled={status === 'connecting'} onClick={status === 'live' ? onStop : onSubscribe}>
|
|
223
|
+
{status === 'live' ? 'Stop' : 'Subscribe'}
|
|
224
|
+
</Button>
|
|
225
|
+
}
|
|
226
|
+
>
|
|
227
|
+
{form}
|
|
228
|
+
</FormSection>
|
|
229
|
+
{validationMessage && (
|
|
230
|
+
<InlineCallout
|
|
231
|
+
tone="destructive"
|
|
232
|
+
title="Check subscription parameters"
|
|
233
|
+
description={validationMessage}
|
|
234
|
+
actions={[]}
|
|
235
|
+
/>
|
|
236
|
+
)}
|
|
237
|
+
<HStack justify="between">
|
|
238
|
+
<StatusDot {...connection} />
|
|
239
|
+
<InspectorToggle label="Toggle event inspector" />
|
|
240
|
+
</HStack>
|
|
241
|
+
<p className="text-sm text-muted-foreground">
|
|
242
|
+
{buffer.length} of {events.length} events buffered
|
|
243
|
+
</p>
|
|
244
|
+
{eventTypes.length > 0 && (
|
|
245
|
+
<ToggleGroup
|
|
246
|
+
aria-label="Event types"
|
|
247
|
+
multiple
|
|
248
|
+
value={eventTypes.filter((type) => !excludedTypes.includes(type))}
|
|
249
|
+
onValueChange={(value) => setExcludedTypes(eventTypes.filter((type) => !value.includes(type)))}
|
|
250
|
+
>
|
|
251
|
+
{eventTypes.map((type) => (
|
|
252
|
+
<Toggle key={type} value={type}>
|
|
253
|
+
{type}
|
|
254
|
+
</Toggle>
|
|
255
|
+
))}
|
|
256
|
+
</ToggleGroup>
|
|
257
|
+
)}
|
|
258
|
+
<p role="status" aria-label="Connection status" aria-live="polite" aria-atomic="true" className="sr-only">
|
|
259
|
+
{connection.label}
|
|
260
|
+
{countAnnouncement ? `. ${countAnnouncement}` : ''}
|
|
261
|
+
</p>
|
|
262
|
+
{status === 'error' && (
|
|
263
|
+
<div role="alert" aria-label="Subscription error">
|
|
264
|
+
<ErrorPanel
|
|
265
|
+
title="Subscription error"
|
|
266
|
+
message={error}
|
|
267
|
+
code={null}
|
|
268
|
+
requestId=""
|
|
269
|
+
details=""
|
|
270
|
+
secondaryAction={null}
|
|
271
|
+
titleAs="h2"
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
)}
|
|
275
|
+
{status === 'connecting' ? (
|
|
276
|
+
<section aria-label="Events" aria-busy="true">
|
|
277
|
+
<div aria-hidden="true">
|
|
278
|
+
<ListSkeleton rows={5} label="Connecting" />
|
|
279
|
+
</div>
|
|
280
|
+
</section>
|
|
281
|
+
) : buffer.length > 0 ? (
|
|
282
|
+
<EventLog
|
|
283
|
+
lines={lines}
|
|
284
|
+
selectedLineId={selected?.id}
|
|
285
|
+
onSelectLine={(line) => {
|
|
286
|
+
const event = eventsById.get(line.id);
|
|
287
|
+
if (!event) return;
|
|
288
|
+
setSelected(event);
|
|
289
|
+
onSelectEvent?.(event);
|
|
290
|
+
}}
|
|
291
|
+
/>
|
|
292
|
+
) : (
|
|
293
|
+
<div role="log" aria-label="Events" aria-live="off">
|
|
294
|
+
<EmptyState
|
|
295
|
+
titleAs="h2"
|
|
296
|
+
title={status === 'live' ? 'Listening for events' : 'No events yet'}
|
|
297
|
+
description={
|
|
298
|
+
status === 'live' ? 'New events will appear here.' : 'Subscribe to start receiving events.'
|
|
299
|
+
}
|
|
300
|
+
/>
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
303
|
+
</Page>
|
|
304
|
+
</InspectorLayout>
|
|
305
|
+
</ConsoleShell>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Generic, transport-free subscription fixture. */
|
|
2
|
+
export const streamTitle = 'Live event stream';
|
|
3
|
+
export const streamDescription = 'Subscribe to a source and inspect the events it emits.';
|
|
4
|
+
export const streamSource = 'activity';
|
|
5
|
+
|
|
6
|
+
export interface LiveEvent {
|
|
7
|
+
/** Stable, unique event identity. */
|
|
8
|
+
id: string;
|
|
9
|
+
/** Consumer-formatted display timestamp. */
|
|
10
|
+
timestamp: string;
|
|
11
|
+
/** Event type used for filtering. */
|
|
12
|
+
type: string;
|
|
13
|
+
/** Optional concise row text; defaults to the event type. */
|
|
14
|
+
summary?: string;
|
|
15
|
+
/** Inspected only on selection; never serialised into log lines. */
|
|
16
|
+
payload: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const emptyStreamEvents: LiveEvent[] = [];
|
|
20
|
+
export const streamEventTypes = ['created', 'updated', 'deleted'];
|
|
21
|
+
|
|
22
|
+
export const streamEvents: LiveEvent[] = [
|
|
23
|
+
{
|
|
24
|
+
id: 'event-1',
|
|
25
|
+
timestamp: '12:04:30.000',
|
|
26
|
+
type: 'created',
|
|
27
|
+
summary: 'Record created',
|
|
28
|
+
payload: { id: 'record-42', name: 'Sample record', revision: 1 },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'event-2',
|
|
32
|
+
timestamp: '12:04:31.000',
|
|
33
|
+
type: 'updated',
|
|
34
|
+
summary: 'Record updated',
|
|
35
|
+
payload: { id: 'record-42', name: 'Updated record', revision: 2 },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'event-3',
|
|
39
|
+
timestamp: '12:04:32.000',
|
|
40
|
+
type: 'deleted',
|
|
41
|
+
summary: 'Record deleted',
|
|
42
|
+
payload: { id: 'record-42', revision: 2 },
|
|
43
|
+
},
|
|
44
|
+
];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { TemplateMeta } from '../meta';
|
|
2
|
+
|
|
3
|
+
export const meta = {
|
|
4
|
+
name: 'Live event stream',
|
|
5
|
+
family: 'Builders',
|
|
6
|
+
priority: 'P3',
|
|
7
|
+
status: 'experimental',
|
|
8
|
+
description: `Controlled subscription form inside FormSection, a visible StatusDot connection indicator, a quiet role=log stream with LogViewer windowing, search, follow-tail and copy, and a pinnable JSON inspector on InspectorLayout. No fetching or transport; no trace waterfall, token cost or timer.
|
|
9
|
+
|
|
10
|
+
## Props
|
|
11
|
+
|
|
12
|
+
LiveEventStreamPage accepts Partial<LiveEventStreamProps>; omitted props use generic fixtures.
|
|
13
|
+
|
|
14
|
+
| Prop | Type | Default / behavior |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| title | string | Live event stream; PageHeader h1 |
|
|
17
|
+
| description | string | Subscribe to a source and inspect the events it emits. |
|
|
18
|
+
| form | ReactNode | Non-sensitive Source field inside FormSection; sensitive fields must use the kit PasswordInput |
|
|
19
|
+
| status | idle / connecting / live / error / stopped | idle; fully controlled |
|
|
20
|
+
| events | LiveEvent[] | Empty fixture; unique id, timestamp, type, optional summary and unknown payload; oldest first |
|
|
21
|
+
| maxEvents | number | 500; nonnegative integer; retains newest entries, evicts oldest; invalid values use 500 |
|
|
22
|
+
| eventTypes | string[] | created, updated, deleted; multiple ToggleGroup, initially all enabled; unlisted types remain visible |
|
|
23
|
+
| countAnnouncement | string | Omitted; optional count text throttled by the consumer, never inferred from events.length |
|
|
24
|
+
| onSubscribe | () => void | Subscribe callback in idle, stopped and error; disabled while connecting |
|
|
25
|
+
| onStop | () => void | Stop callback while live |
|
|
26
|
+
| onSelectEvent | (event: LiveEvent) => void | Mouse or keyboard selection callback |
|
|
27
|
+
| error | string | Generic subscription error; ErrorPanel shown when status=error, inside role=alert |
|
|
28
|
+
| validationMessage | string | Omitted; destructive InlineCallout |
|
|
29
|
+
| shell | Omit<ConsoleShellProps, 'children'> | ConsoleShell fixture defaults; explicit consumer navigation, workspace, user and topbar slots |
|
|
30
|
+
|
|
31
|
+
Status mapping: idle = neutral Not subscribed; connecting = info Connecting with pulse; live = success Live with pulse; error = destructive Error; stopped = warning Stopped. Subscribe is shown in idle/connecting/error/stopped and is disabled for connecting; live shows Stop. Idle has No events yet, live-empty has Listening for events, connecting replaces the log with decorative skeletons, and stopped keeps supplied buffered events visible.
|
|
32
|
+
|
|
33
|
+
## ARIA pattern
|
|
34
|
+
|
|
35
|
+
PageHeader h1 and labelled FormSection. One polite atomic live region announces connection status and explicit consumer-throttled count text only. LogViewer uses announceLines=false: role=log and aria-live=off; event additions are silent. Connecting uses an aria-busy section. Stream errors use ErrorPanel inside role=alert; validation uses InlineCallout. Inspect buttons have the name Inspect event <timestamp> and aria-pressed. InspectorLayout owns the complementary landmark on desktop, Drawer on small screens, docking, pinning and focus return. Pinning does not freeze the payload: subsequent selection updates it; eviction preserves the last inspected payload with the note This event is no longer in the buffer.
|
|
36
|
+
|
|
37
|
+
## Keyboard table
|
|
38
|
+
|
|
39
|
+
| Key | Behavior |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| Tab / Shift+Tab | Form fields, Subscribe/Stop, filters, search, Follow, row inspect/copy and panel controls |
|
|
42
|
+
| Enter / Space | Activate buttons; select an event; toggle Follow and pinning |
|
|
43
|
+
| Arrow keys | Navigate ToggleGroups and the JsonViewer tree |
|
|
44
|
+
| Escape | From unpinned panel or toggle, close and return focus to inspector toggle; pinned panel ignores Escape, Close panel closes it |
|
|
45
|
+
|
|
46
|
+
Follow pins the bottom, where the newest row is added. Scrolling up by more than one row turns Follow off and preserves the viewport offset; switching Follow on returns to the bottom.
|
|
47
|
+
|
|
48
|
+
## Themes and examples
|
|
49
|
+
|
|
50
|
+
Dark (default), light and high_contrast use only kit tokens. Connecting/live pulses stop under prefers-reduced-motion. State previews: live-event-stream--connecting, --live-empty, --live-with-events, --error and --stopped. Every state and the default template are covered by axe in all three themes.`,
|
|
51
|
+
layout: 'InspectorLayout',
|
|
52
|
+
blocks: [
|
|
53
|
+
'app-sidebar',
|
|
54
|
+
'app-topbar',
|
|
55
|
+
'page-header',
|
|
56
|
+
'form-section',
|
|
57
|
+
'log-viewer',
|
|
58
|
+
'empty-states',
|
|
59
|
+
'skeletons',
|
|
60
|
+
'inline-callout',
|
|
61
|
+
'error-panel',
|
|
62
|
+
],
|
|
63
|
+
pattern:
|
|
64
|
+
'PageHeader h1, labelled FormSection, one polite atomic connection live region, quiet role=log stream (aria-live=off), alert errors and complementary JSON inspector. Selected inspect buttons expose aria-pressed.',
|
|
65
|
+
keyboard: [
|
|
66
|
+
['Tab / Shift+Tab', 'Navigate form, action, filters, search, Follow, row inspect/copy and panel controls.'],
|
|
67
|
+
['Enter / Space', 'Activate buttons, select an event and toggle Follow or pinning.'],
|
|
68
|
+
['Arrow keys', 'Navigate ToggleGroups and the JSON tree.'],
|
|
69
|
+
[
|
|
70
|
+
'Escape',
|
|
71
|
+
'Close an unpinned inspector from panel or toggle and return focus to the toggle. Pinned panels ignore Escape.',
|
|
72
|
+
],
|
|
73
|
+
],
|
|
74
|
+
} satisfies TemplateMeta & { pattern: string; keyboard: [string, string][] };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { render, screen, within } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expectNoAxeViolations } from '../test/a11y';
|
|
4
|
+
import MessagingPage from './Page';
|
|
5
|
+
import { conversations } from './data';
|
|
6
|
+
|
|
7
|
+
describe('MessagingPage', () => {
|
|
8
|
+
it('renders the conversation list and the empty thread', { timeout: 15000 }, async () => {
|
|
9
|
+
render(<MessagingPage />);
|
|
10
|
+
expect(screen.getByRole('main')).toBeInTheDocument();
|
|
11
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Messages' })).toBeInTheDocument();
|
|
12
|
+
const list = screen.getByRole('list', { name: 'Conversations' });
|
|
13
|
+
expect(within(list).getAllByRole('button')).toHaveLength(conversations.length);
|
|
14
|
+
expect(within(list).getByText('2')).toBeInTheDocument();
|
|
15
|
+
expect(screen.getByRole('heading', { name: 'Select a conversation' })).toBeInTheDocument();
|
|
16
|
+
await expectNoAxeViolations();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('opens a thread with attachments and sends a message', { timeout: 15000 }, async () => {
|
|
20
|
+
const onSend = vi.fn();
|
|
21
|
+
render(<MessagingPage onSend={onSend} />);
|
|
22
|
+
await userEvent.click(
|
|
23
|
+
within(screen.getByRole('list', { name: 'Conversations' })).getByRole('button', { name: /Release 2\.14/ }),
|
|
24
|
+
);
|
|
25
|
+
const log = screen.getByRole('log', { name: 'Messages in Release 2.14' });
|
|
26
|
+
expect(within(log).getAllByRole('article')).toHaveLength(4);
|
|
27
|
+
expect(within(log).getByText('rollback-runbook.pdf')).toBeInTheDocument();
|
|
28
|
+
await userEvent.type(screen.getByRole('textbox', { name: 'Message Release 2.14' }), 'Ship it.{Enter}');
|
|
29
|
+
expect(onSend).toHaveBeenCalledWith('c-release', 'Ship it.', []);
|
|
30
|
+
expect(within(log).getAllByRole('article')).toHaveLength(5);
|
|
31
|
+
expect(within(log).getByText('Ship it.')).toBeInTheDocument();
|
|
32
|
+
await expectNoAxeViolations();
|
|
33
|
+
});
|
|
34
|
+
});
|