Converge GitHub star banner on shared action primitives

This commit is contained in:
rcourtman
2026-06-13 20:24:17 +01:00
parent 5330ce40c7
commit 51c47a2279
6 changed files with 123 additions and 17 deletions
@@ -145,6 +145,7 @@ work extends shared components instead of creating new local variants.
115. `frontend-modern/src/utils/upgradeNavigation.ts`
116. `frontend-modern/src/components/DemoBanner.tsx`
116a. `frontend-modern/src/components/CommercialMigrationBanner.tsx`
116b. `frontend-modern/src/components/GitHubStarBanner.tsx`
117. `frontend-modern/src/components/Login.tsx`
118. `frontend-modern/src/stores/sessionCapabilities.ts`
119. `frontend-modern/src/stores/sessionPresentationPolicy.ts`
@@ -548,6 +549,11 @@ not a replacement status card, CTA band, or page-local nested card.
disabled treatment, title fallback, and accessible name wiring must come
from that shared primitive rather than page-local `<button>` plus inline SVG
shells.
Global app-shell prompts are part of the same action boundary.
`frontend-modern/src/components/GitHubStarBanner.tsx` may own its display
timing, product copy, and GitHub destination, but its primary, defer, and
dismiss controls must compose `Button` and `ActionIconButton` instead of
carrying local floating-prompt button shells.
Settings selection helpers such as `ResourcePicker` must use the same
`Button` primitive for select-all, clear, and chip remove actions instead of
restoring footer-local action shells.
@@ -3124,6 +3124,7 @@
"frontend-modern/scripts/shared-template-audit.mjs",
"frontend-modern/scripts/shared-template-registry.json",
"frontend-modern/src/components/CommercialMigrationBanner.tsx",
"frontend-modern/src/components/GitHubStarBanner.tsx",
"frontend-modern/src/components/Login.tsx",
"frontend-modern/src/components/Settings/AgentIntegrationsPanel.tsx",
"frontend-modern/src/components/Settings/AIChatMaintenanceSection.tsx",
@@ -3261,6 +3262,21 @@
"frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts"
]
},
{
"id": "global-github-star-banner",
"label": "global GitHub star banner primitive proof",
"match_prefixes": [],
"match_files": [
"frontend-modern/src/components/GitHubStarBanner.tsx"
],
"allow_same_subsystem_tests": false,
"test_prefixes": [],
"exact_files": [
"frontend-modern/src/components/__tests__/GitHubStarBanner.test.tsx",
"frontend-modern/src/components/shared/Button.test.tsx",
"frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts"
]
},
{
"id": "page-controls-and-filters",
"label": "page controls and filter proof",
@@ -574,6 +574,32 @@
"scripts/shared-template-audit.mjs"
]
},
{
"id": "github-star-banner-action-shell",
"category": "action-button",
"summary": "The GitHub star prompt must compose Button and ActionIconButton for its primary, defer, and dismiss actions instead of carrying local floating-prompt button shells.",
"canonical": {
"path": "src/components/shared/Button.tsx",
"export": "Button"
},
"requiredConsumers": [{ "path": "src/components/GitHubStarBanner.tsx" }],
"forbiddenPatterns": [
{
"path": "src/components/GitHubStarBanner.tsx",
"patterns": [
"inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content",
"inline-flex min-h-9 items-center justify-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700",
"inline-flex min-h-9 items-center justify-center rounded-md px-3 py-2 text-sm text-muted transition-colors hover:bg-surface-hover hover:text-base-content"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/__tests__/GitHubStarBanner.test.tsx",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "status-dot-shell",
"category": "status-indicator",
@@ -5,6 +5,7 @@ import {
STORAGE_KEYS,
} from '@/utils/localStorage';
import { useWebSocket } from '@/contexts/appRuntime';
import { ActionIconButton, Button } from '@/components/shared/Button';
import GithubIcon from 'lucide-solid/icons/github';
import StarIcon from 'lucide-solid/icons/star';
import XIcon from 'lucide-solid/icons/x';
@@ -137,36 +138,35 @@ export function GitHubStarBanner() {
Enjoying Pulse?
</h2>
<p class="mt-1 text-xs leading-5 text-muted">
Pulse is built and maintained by an independent developer. If it's been useful
for monitoring your infrastructure, a GitHub star helps more than you'd think.
Pulse is built and maintained by an independent developer. If it's been useful for
monitoring your infrastructure, a GitHub star helps more than you'd think.
</p>
</div>
<button
<ActionIconButton
onClick={handleDismiss}
class="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content"
label="Close and don't show again"
title="Don't show again"
aria-label="Close and don't show again"
tone="muted"
size="sm"
type="button"
>
<XIcon class="h-4 w-4" />
</button>
<XIcon class="h-4 w-4" aria-hidden="true" />
</ActionIconButton>
</div>
<div class="mt-3 flex flex-wrap gap-2">
<button
<Button
onClick={handleStarClick}
class="inline-flex min-h-9 items-center justify-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700"
variant="primary"
size="mdCompact"
class="gap-2"
type="button"
>
<StarIcon class="h-4 w-4" />
<StarIcon class="h-4 w-4" aria-hidden="true" />
Star on GitHub
</button>
<button
onClick={handleMaybeLater}
class="inline-flex min-h-9 items-center justify-center rounded-md px-3 py-2 text-sm text-muted transition-colors hover:bg-surface-hover hover:text-base-content"
type="button"
>
</Button>
<Button onClick={handleMaybeLater} variant="ghost" size="mdCompact" type="button">
Maybe later
</button>
</Button>
</div>
</div>
</div>
@@ -30,6 +30,12 @@ vi.mock('@/utils/logger', () => ({
const DISMISSED_KEY = 'pulse-github-star-dismissed';
const FIRST_SEEN_KEY = 'pulse-github-star-first-seen';
const SNOOZED_KEY = 'pulse-github-star-snoozed-until';
const LOCAL_DISMISS_BUTTON_CLASS =
'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content';
const LOCAL_PRIMARY_BUTTON_CLASS =
'inline-flex min-h-9 items-center justify-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700';
const LOCAL_DEFER_BUTTON_CLASS =
'inline-flex min-h-9 items-center justify-center rounded-md px-3 py-2 text-sm text-muted transition-colors hover:bg-surface-hover hover:text-base-content';
async function renderBanner() {
const mod = await import('../GitHubStarBanner');
@@ -84,6 +90,15 @@ describe('GitHubStarBanner', () => {
expect(gitHubStarBannerSource).toContain('z-30');
});
it('routes prompt action chrome through shared Button primitives', () => {
expect(gitHubStarBannerSource).toContain('@/components/shared/Button');
expect(gitHubStarBannerSource).toContain('<ActionIconButton');
expect(gitHubStarBannerSource).toContain('<Button');
expect(gitHubStarBannerSource).not.toContain(LOCAL_DISMISS_BUTTON_CLASS);
expect(gitHubStarBannerSource).not.toContain(LOCAL_PRIMARY_BUTTON_CLASS);
expect(gitHubStarBannerSource).not.toContain(LOCAL_DEFER_BUTTON_CLASS);
});
it('does not render on the first day infrastructure is seen (records first-seen date)', async () => {
vi.setSystemTime(new Date('2026-03-01T12:00:00Z'));
setResourceCount(3);
@@ -5,6 +5,7 @@ import calloutCardSource from '@/components/shared/CalloutCard.tsx?raw';
import inlineNoticeSource from '@/components/shared/InlineNotice.tsx?raw';
import demoBannerSource from '@/components/DemoBanner.tsx?raw';
import commercialMigrationBannerSource from '@/components/CommercialMigrationBanner.tsx?raw';
import gitHubStarBannerSource from '@/components/GitHubStarBanner.tsx?raw';
import assistantCommandHelpDialogSource from '@/components/AI/Chat/AssistantCommandHelpDialog.tsx?raw';
import chatMessagesSource from '@/components/AI/Chat/ChatMessages.tsx?raw';
import aiChatSource from '@/components/AI/Chat/index.tsx?raw';
@@ -4033,6 +4034,48 @@ describe('shared primitive guardrails', () => {
);
});
it('routes GitHub star prompt actions through Button primitives', () => {
const registry = JSON.parse(sharedTemplateRegistrySource) as {
rules?: Array<{
id: string;
canonical?: { path?: string; export?: string };
requiredConsumers?: Array<{ path?: string }>;
forbiddenPatterns?: Array<{ path?: string; patterns?: string[] }>;
}>;
};
const registeredRule = registry.rules?.find(
(rule) => rule.id === 'github-star-banner-action-shell',
);
expect(registeredRule?.canonical?.path).toBe('src/components/shared/Button.tsx');
expect(registeredRule?.canonical?.export).toBe('Button');
expect(registeredRule?.requiredConsumers?.map((consumer) => consumer.path)).toEqual([
'src/components/GitHubStarBanner.tsx',
]);
expect(registeredRule?.forbiddenPatterns).toEqual([
{
path: 'src/components/GitHubStarBanner.tsx',
patterns: [
'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content',
'inline-flex min-h-9 items-center justify-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700',
'inline-flex min-h-9 items-center justify-center rounded-md px-3 py-2 text-sm text-muted transition-colors hover:bg-surface-hover hover:text-base-content',
],
},
]);
expect(gitHubStarBannerSource).toContain('@/components/shared/Button');
expect(gitHubStarBannerSource).toContain('<ActionIconButton');
expect(gitHubStarBannerSource).toContain('<Button');
expect(gitHubStarBannerSource).not.toContain(
'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content',
);
expect(gitHubStarBannerSource).not.toContain(
'inline-flex min-h-9 items-center justify-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700',
);
expect(gitHubStarBannerSource).not.toContain(
'inline-flex min-h-9 items-center justify-center rounded-md px-3 py-2 text-sm text-muted transition-colors hover:bg-surface-hover hover:text-base-content',
);
});
it('keeps TLS verification warnings in the shared primitive boundary', () => {
expect(tlsVerificationWarningBannerSource).toContain('role="alert"');
expect(tlsVerificationWarningBannerSource).toContain('TLS verification disabled.');