mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-22 19:36:46 +00:00
Remove v6 RC banner from app shell
This commit is contained in:
parent
4e4fcb5dbe
commit
00da144dd8
12 changed files with 101 additions and 190 deletions
|
|
@ -237,7 +237,11 @@ they must remain presentation-only. Prerelease banners, billing callouts, or
|
|||
other header-adjacent notices must not fork assistant open state, gate on AI
|
||||
runtime fetches, or move assistant availability logic out of
|
||||
`frontend-modern/src/stores/aiChat.ts` and `frontend-modern/src/useAppRuntimeState.ts`
|
||||
just because they share the same authenticated shell.
|
||||
just because they share the same authenticated shell. The remaining
|
||||
prerelease-shell treatment is the compact `Preview` badge on rc-channel
|
||||
builds; `frontend-modern/src/AppLayout.tsx` must not revive a standalone
|
||||
release-candidate banner, release-notes CTA, or feedback CTA that starts
|
||||
participating in assistant-shell state or modal ownership.
|
||||
That same shared shell boundary must respect blocking modal ownership.
|
||||
`frontend-modern/src/App.tsx` and `frontend-modern/src/AppLayout.tsx` may use
|
||||
the shared dialog runtime to hide the closed assistant launcher and close the
|
||||
|
|
|
|||
|
|
@ -625,7 +625,11 @@ That same shell framing also owns user-facing prerelease labeling for
|
|||
rc-channel builds. `frontend-modern/src/AppLayout.tsx` may still key off the
|
||||
canonical `rc` channel metadata internally, but the visible badge must frame
|
||||
those builds as a preview/prerelease experience rather than implying a
|
||||
near-ready release candidate.
|
||||
near-ready release candidate. That authenticated shell must not pair the
|
||||
preview label with a second top-of-shell release-candidate warning banner or
|
||||
release-feedback CTA in `frontend-modern/src/AppLayout.tsx`; prerelease cloud
|
||||
posture stays a subtle shell label, not a public-RC callout inside the paid
|
||||
runtime chrome.
|
||||
The shared trial-start runtime is part of that same cloud-paid boundary.
|
||||
Commercial relay, onboarding, setup, Pro settings, and shared paywall
|
||||
surfaces may customize success copy, but they must route hosted handoff,
|
||||
|
|
|
|||
|
|
@ -644,15 +644,15 @@ paywall surfaces navigate those destinations. Feature shells may request a
|
|||
commercial destination, but they must not re-decide whether that destination
|
||||
opens in-app or in a new tab once the shared primitive exists.
|
||||
That same shared-primitive floor now also owns prerelease shell guidance.
|
||||
`frontend-modern/src/components/shared/ReleaseCandidateBanner.tsx` is the
|
||||
canonical low-key release-candidate callout for authenticated chrome, and
|
||||
`frontend-modern/src/AppLayout.tsx` may mount it only from resolved release
|
||||
metadata. Feature pages, settings panels, and route-local shells must not add
|
||||
duplicate RC modals, hardcoded GitHub release or feedback links, or page-local
|
||||
prerelease banners once that shared primitive exists. The shared banner copy
|
||||
must stay version-aware but RC-order-agnostic: later builds like `rc.2` and
|
||||
beyond may identify the current version, but they must not keep claiming to be
|
||||
the first public v6 RC once the release line has moved on.
|
||||
`frontend-modern/src/AppLayout.tsx` is the canonical authenticated-shell owner
|
||||
for prerelease presentation, and the remaining user-facing treatment is the
|
||||
compact `Preview` badge keyed from resolved release metadata. Feature pages,
|
||||
settings panels, shared components, and route-local shells must not add a
|
||||
second release-candidate banner, hardcoded GitHub release or feedback links,
|
||||
or page-local prerelease notices once that shared shell contract exists.
|
||||
Browser proof for that shell rule now lives in
|
||||
`tests/integration/tests/57-release-candidate-shell.spec.ts`, which must keep
|
||||
rc-channel builds banner-free while preserving the compact preview badge.
|
||||
|
||||
The subsystem registry now also requires explicit proof-policy coverage for all
|
||||
shared runtime files, and shared-component guardrails fail if raw table
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import SettingsIcon from 'lucide-solid/icons/settings';
|
|||
import Maximize2Icon from 'lucide-solid/icons/maximize-2';
|
||||
import Minimize2Icon from 'lucide-solid/icons/minimize-2';
|
||||
import { MobileNavBar } from '@/components/shared/MobileNavBar';
|
||||
import { ReleaseCandidateBanner } from '@/components/shared/ReleaseCandidateBanner';
|
||||
import { dialogStackHasBlockingDialog } from '@/components/shared/useDialogState';
|
||||
import { OrgSwitcher } from '@/components/OrgSwitcher';
|
||||
import { PulsePatrolLogo } from '@/components/Brand/PulsePatrolLogo';
|
||||
|
|
@ -646,12 +645,6 @@ export function AppLayout(props: AppLayoutProps) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={!kioskMode()}>
|
||||
<Show when={props.versionInfo()?.channel === 'rc'}>
|
||||
<ReleaseCandidateBanner version={props.versionInfo()?.version} />
|
||||
</Show>
|
||||
</Show>
|
||||
|
||||
<Show when={!kioskMode()}>
|
||||
<div
|
||||
class="tabs mb-2 hidden md:flex items-end gap-2 overflow-x-auto overflow-y-hidden whitespace-nowrap border-b border-border scrollbar-hide"
|
||||
|
|
|
|||
|
|
@ -70,9 +70,6 @@ describe('App architecture', () => {
|
|||
expect(appLayoutSource).toContain(
|
||||
"import { dialogStackHasBlockingDialog } from '@/components/shared/useDialogState';",
|
||||
);
|
||||
expect(appLayoutSource).toContain(
|
||||
"import { ReleaseCandidateBanner } from '@/components/shared/ReleaseCandidateBanner';",
|
||||
);
|
||||
expect(appLayoutSource).toContain('<OrgSwitcher');
|
||||
expect(appLayoutSource).toContain('const status = () => props.connectionStatus();');
|
||||
expect(appLayoutSource).toContain("status().kind === 'sync-reconnecting' || status().kind === 'reconnecting'");
|
||||
|
|
@ -81,7 +78,10 @@ describe('App architecture', () => {
|
|||
);
|
||||
expect(appLayoutSource).toContain("props.versionInfo()?.channel === 'rc'");
|
||||
expect(appLayoutSource).toContain('Preview');
|
||||
expect(appLayoutSource).toContain(
|
||||
expect(appLayoutSource).not.toContain(
|
||||
"import { ReleaseCandidateBanner } from '@/components/shared/ReleaseCandidateBanner';",
|
||||
);
|
||||
expect(appLayoutSource).not.toContain(
|
||||
'<ReleaseCandidateBanner version={props.versionInfo()?.version} />',
|
||||
);
|
||||
expect(appLayoutSource).toContain(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
buildIssueTemplateUrl,
|
||||
buildDockerImageTag,
|
||||
buildLinuxAmd64DownloadCommand,
|
||||
buildLinuxAmd64TarballName,
|
||||
buildReleaseNotesUrl,
|
||||
buildV6RcFeedbackUrl,
|
||||
formatReleaseTag,
|
||||
normalizeReleaseVersion,
|
||||
} from '@/components/updateVersion';
|
||||
|
|
@ -42,13 +40,4 @@ describe('updateVersion helpers', () => {
|
|||
'sudo tar -xzf pulse-v5.1.0-linux-amd64.tar.gz -C /usr/local/bin pulse',
|
||||
);
|
||||
});
|
||||
|
||||
it('builds GitHub issue template links for prerelease feedback', () => {
|
||||
expect(buildIssueTemplateUrl('v6_rc_feedback.yml')).toBe(
|
||||
'https://github.com/rcourtman/Pulse/issues/new?template=v6_rc_feedback.yml',
|
||||
);
|
||||
expect(buildV6RcFeedbackUrl()).toBe(
|
||||
'https://github.com/rcourtman/Pulse/issues/new?template=v6_rc_feedback.yml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
import type { Component } from 'solid-js';
|
||||
import {
|
||||
buildReleaseNotesUrl,
|
||||
buildV6RcFeedbackUrl,
|
||||
normalizeReleaseVersion,
|
||||
} from '@/components/updateVersion';
|
||||
|
||||
export interface ReleaseCandidateBannerProps {
|
||||
version?: string | null;
|
||||
}
|
||||
|
||||
export const ReleaseCandidateBanner: Component<ReleaseCandidateBannerProps> = (props) => {
|
||||
const versionLabel = () => normalizeReleaseVersion(props.version);
|
||||
const title = () =>
|
||||
versionLabel()
|
||||
? `Pulse ${versionLabel()} is a public v6 release candidate.`
|
||||
: 'You’re running a Pulse v6 release candidate build.';
|
||||
|
||||
return (
|
||||
<div
|
||||
class="mb-3 rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-amber-950 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-100"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="inline-flex rounded-full bg-amber-600 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.18em] text-white">
|
||||
RC
|
||||
</span>
|
||||
<span class="text-sm font-medium">{title()}</span>
|
||||
</div>
|
||||
<p class="mt-1 text-xs leading-relaxed text-amber-900/85 dark:text-amber-100/85">
|
||||
Start in a staging or non-critical environment first, then send feedback on bugs,
|
||||
regressions, or rough edges before general availability.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs font-medium">
|
||||
<a
|
||||
href={buildReleaseNotesUrl(props.version)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex min-h-9 items-center rounded px-1 py-1 underline underline-offset-2 hover:opacity-90"
|
||||
>
|
||||
View release notes
|
||||
</a>
|
||||
<a
|
||||
href={buildV6RcFeedbackUrl()}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex min-h-9 items-center rounded px-1 py-1 underline underline-offset-2 hover:opacity-90"
|
||||
>
|
||||
Send feedback
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -72,7 +72,6 @@ import infrastructureSummaryTableModelSource from '@/components/shared/infrastru
|
|||
import infrastructureSummaryTableStateSource from '@/components/shared/useInfrastructureSummaryTableState.ts?raw';
|
||||
import monitoredSystemLimitWarningBannerSource from '@/components/shared/MonitoredSystemLimitWarningBanner.tsx?raw';
|
||||
import monitoredSystemLimitWarningBannerModelSource from '@/components/shared/monitoredSystemLimitWarningBannerModel.ts?raw';
|
||||
import releaseCandidateBannerSource from '@/components/shared/ReleaseCandidateBanner.tsx?raw';
|
||||
import selectionCardGroupSource from '@/components/shared/SelectionCardGroup.tsx?raw';
|
||||
import selectionCardGroupModelSource from '@/components/shared/selectionCardGroupModel.ts?raw';
|
||||
import summaryMetricCardSource from '@/components/shared/SummaryMetricCard.tsx?raw';
|
||||
|
|
@ -692,19 +691,6 @@ describe('shared primitive guardrails', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('keeps release-candidate shell guidance on shared link helpers', () => {
|
||||
expect(releaseCandidateBannerSource).toContain('buildReleaseNotesUrl');
|
||||
expect(releaseCandidateBannerSource).toContain('buildV6RcFeedbackUrl');
|
||||
expect(releaseCandidateBannerSource).toContain('normalizeReleaseVersion');
|
||||
expect(releaseCandidateBannerSource).toContain('public v6 release candidate');
|
||||
expect(releaseCandidateBannerSource).not.toContain('first public v6 RC');
|
||||
expect(releaseCandidateBannerSource).not.toContain('useNavigate');
|
||||
expect(releaseCandidateBannerSource).not.toContain('createSignal');
|
||||
expect(releaseCandidateBannerSource).not.toContain('loadCommercialPosture');
|
||||
expect(releaseCandidateBannerSource).not.toContain('/api/license/');
|
||||
expect(releaseCandidateBannerSource).not.toContain('window.location');
|
||||
});
|
||||
|
||||
it('keeps infrastructure summary table on shell, runtime, and model owners', () => {
|
||||
expect(infrastructureSummaryTableSource).toContain('useInfrastructureSummaryTableState');
|
||||
expect(infrastructureSummaryTableSource).toContain('InfrastructureSummaryTableRow');
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
import { cleanup, render, screen } from '@solidjs/testing-library';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { ReleaseCandidateBanner } from '@/components/shared/ReleaseCandidateBanner';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe('ReleaseCandidateBanner', () => {
|
||||
it('renders RC guidance with release-note and feedback links', () => {
|
||||
render(() => <ReleaseCandidateBanner version="6.0.0-rc.2" />);
|
||||
|
||||
expect(
|
||||
screen.getByText('Pulse 6.0.0-rc.2 is a public v6 release candidate.'),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Start in a staging or non-critical environment first, then send feedback/i,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: 'View release notes' })).toHaveAttribute(
|
||||
'href',
|
||||
'https://github.com/rcourtman/Pulse/releases/tag/v6.0.0-rc.2',
|
||||
);
|
||||
expect(screen.getByRole('link', { name: 'Send feedback' })).toHaveAttribute(
|
||||
'href',
|
||||
'https://github.com/rcourtman/Pulse/issues/new?template=v6_rc_feedback.yml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
const GITHUB_RELEASES_BASE_URL = 'https://github.com/rcourtman/Pulse/releases';
|
||||
const GITHUB_ISSUES_BASE_URL = 'https://github.com/rcourtman/Pulse/issues/new';
|
||||
|
||||
export const normalizeReleaseVersion = (version?: string | null): string => {
|
||||
const trimmed = (version ?? '').trim();
|
||||
|
|
@ -19,16 +18,6 @@ export const buildReleaseNotesUrl = (version?: string | null): string => {
|
|||
return tag ? `${GITHUB_RELEASES_BASE_URL}/tag/${tag}` : GITHUB_RELEASES_BASE_URL;
|
||||
};
|
||||
|
||||
export const buildIssueTemplateUrl = (template?: string | null): string => {
|
||||
const trimmed = (template ?? '').trim();
|
||||
if (!trimmed) {
|
||||
return GITHUB_ISSUES_BASE_URL;
|
||||
}
|
||||
return `${GITHUB_ISSUES_BASE_URL}?template=${encodeURIComponent(trimmed)}`;
|
||||
};
|
||||
|
||||
export const buildV6RcFeedbackUrl = (): string => buildIssueTemplateUrl('v6_rc_feedback.yml');
|
||||
|
||||
export const buildDockerImageTag = (version?: string | null): string => {
|
||||
const normalized = normalizeReleaseVersion(version);
|
||||
return normalized || 'latest';
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
import { expect, test } from '@playwright/test';
|
||||
import { ensureAuthenticated } from './helpers';
|
||||
|
||||
const RC_VERSION_INFO = {
|
||||
version: '6.0.0-rc.2',
|
||||
build: '',
|
||||
runtime: 'unknown',
|
||||
channel: 'rc',
|
||||
isDocker: false,
|
||||
isSourceBuild: true,
|
||||
isDevelopment: false,
|
||||
deploymentType: 'source',
|
||||
};
|
||||
|
||||
test.describe('Release candidate banner', () => {
|
||||
test('renders generic RC copy with version-specific release links', async ({ page }, testInfo) => {
|
||||
test.skip(testInfo.project.name.startsWith('mobile-'), 'Desktop runtime proof');
|
||||
|
||||
await page.route('**/api/version', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(RC_VERSION_INFO),
|
||||
}),
|
||||
);
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
await page.goto('/dashboard', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
const banner = page.getByRole('status').filter({
|
||||
hasText: 'Pulse 6.0.0-rc.2 is a public v6 release candidate.',
|
||||
});
|
||||
await expect(banner).toContainText('Pulse 6.0.0-rc.2 is a public v6 release candidate.');
|
||||
await expect(page.getByRole('link', { name: 'View release notes' })).toHaveAttribute(
|
||||
'href',
|
||||
'https://github.com/rcourtman/Pulse/releases/tag/v6.0.0-rc.2',
|
||||
);
|
||||
await expect(page.getByRole('link', { name: 'Send feedback' })).toHaveAttribute(
|
||||
'href',
|
||||
'https://github.com/rcourtman/Pulse/issues/new?template=v6_rc_feedback.yml',
|
||||
);
|
||||
});
|
||||
});
|
||||
78
tests/integration/tests/57-release-candidate-shell.spec.ts
Normal file
78
tests/integration/tests/57-release-candidate-shell.spec.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { expect, test } from '@playwright/test';
|
||||
import { waitForPulseReady } from './helpers';
|
||||
|
||||
const RC_VERSION_INFO = {
|
||||
version: '6.0.0-rc.2',
|
||||
build: '',
|
||||
runtime: 'unknown',
|
||||
channel: 'rc',
|
||||
isDocker: false,
|
||||
isSourceBuild: true,
|
||||
isDevelopment: false,
|
||||
deploymentType: 'source',
|
||||
};
|
||||
|
||||
test.describe('Release candidate shell', () => {
|
||||
test('keeps RC builds free of the public release-candidate banner', async ({ page }, testInfo) => {
|
||||
test.skip(testInfo.project.name.startsWith('mobile-'), 'Desktop runtime proof');
|
||||
|
||||
await waitForPulseReady(page);
|
||||
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem('pulse_whats_new_v2_shown', 'true');
|
||||
});
|
||||
|
||||
await page.route('**/api/security/status', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
hasAuthentication: true,
|
||||
hideLocalLogin: false,
|
||||
hasProxyAuth: false,
|
||||
proxyAuthUsername: '',
|
||||
proxyAuthLogoutURL: '',
|
||||
publicAccess: false,
|
||||
requiresAuth: true,
|
||||
ssoEnabled: false,
|
||||
ssoProviders: [],
|
||||
ssoSessionUsername: '',
|
||||
sessionCapabilities: {
|
||||
assistantEnabled: true,
|
||||
demoMode: false,
|
||||
},
|
||||
presentationPolicy: {
|
||||
demoMode: false,
|
||||
readOnly: false,
|
||||
hideCommercial: false,
|
||||
hideUpgrade: false,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.route('**/api/state', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.route('**/api/version', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(RC_VERSION_INFO),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.goto('/dashboard', { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForURL(/\/dashboard/, { timeout: 15_000 });
|
||||
|
||||
await expect(
|
||||
page.getByRole('status').filter({ hasText: 'public v6 release candidate' }),
|
||||
).toHaveCount(0);
|
||||
await expect(page.getByText('Preview', { exact: true })).toBeVisible();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue