diff --git a/e2e/stack-files.spec.ts b/e2e/stack-files.spec.ts
index 64c632b7..ecaad5c7 100644
--- a/e2e/stack-files.spec.ts
+++ b/e2e/stack-files.spec.ts
@@ -198,7 +198,7 @@ test.describe('File explorer - community (read-only)', () => {
test('upgrade pill is visible in the left pane', async ({ page }) => {
await expect(
- page.getByRole('button', { name: /upgrade to unlock/i })
+ page.getByRole('button', { name: /upgrade to unlock upload/i })
).toBeVisible({ timeout: 5_000 });
});
diff --git a/frontend/src/components/AdmiralGate.tsx b/frontend/src/components/AdmiralGate.tsx
index f99ca86d..fba8d428 100644
--- a/frontend/src/components/AdmiralGate.tsx
+++ b/frontend/src/components/AdmiralGate.tsx
@@ -25,7 +25,11 @@ export function AdmiralGate({ children, featureName = 'This feature', compact =
if (isPaid && license?.variant === 'admiral') return <>{children}>;
- if (compact || dismissed) {
+ // Inline lock for list items (single SSO provider card, etc.). Renders the
+ // children blurred behind a small pill so the surrounding list keeps its
+ // shape; the IP exposure is minor because the children are tiny inline UI,
+ // and the visual continuity is intentional. Dismissal does not apply here.
+ if (compact) {
return (
@@ -41,6 +45,30 @@ export function AdmiralGate({ children, featureName = 'This feature', compact =
);
}
+ // Post-dismissal placeholder for full-page consumers. Renders only the
+ // pill so the lazy-loaded children behind an Admiral view never mount
+ // during the dismissal window. Clicking the pill clears the dismissal
+ // flag and restores the full upsell card so users who dismissed
+ // accidentally or want to revisit pricing have a way back without
+ // clearing localStorage.
+ if (dismissed) {
+ return (
+
+
+
+ );
+ }
+
return (
diff --git a/frontend/src/components/PaidGate.tsx b/frontend/src/components/PaidGate.tsx
index 2293e832..3104458d 100644
--- a/frontend/src/components/PaidGate.tsx
+++ b/frontend/src/components/PaidGate.tsx
@@ -25,7 +25,11 @@ export function PaidGate({ children, featureName = 'This feature', compact = fal
if (isPaid) return <>{children}>;
- if (compact || dismissed) {
+ // Inline lock for list items (single SSO provider card, etc.). Renders the
+ // children blurred behind a small pill so the surrounding list keeps its
+ // shape; the IP exposure is minor because the children are tiny inline UI,
+ // and the visual continuity is intentional. Dismissal does not apply here.
+ if (compact) {
return (
@@ -41,6 +45,29 @@ export function PaidGate({ children, featureName = 'This feature', compact = fal
);
}
+ // Post-dismissal placeholder for full-page consumers. Renders only the
+ // pill so the lazy-loaded children behind a paid view never mount during
+ // the dismissal window. Clicking the pill clears the dismissal flag and
+ // restores the full upsell card so users who dismissed accidentally or
+ // want to revisit pricing have a way back without clearing localStorage.
+ if (dismissed) {
+ return (
+