From 1aa76ddd33f5c1c3d97336f99c43f7d3d0db076f Mon Sep 17 00:00:00 2001
From: azizu06
Date: Sun, 12 Jul 2026 15:31:03 -0400
Subject: [PATCH] Keep archived demo interfaces visible
---
README.md | 4 +--
src/app/analyze/page.tsx | 11 +------
src/app/globals.css | 22 -------------
src/app/page.tsx | 7 +---
src/components/capcheck-app.test.tsx | 17 ++++++++++
src/components/capcheck-app.tsx | 8 +++--
src/components/intake-panel.tsx | 16 ++++++----
src/components/portfolio-demo-notice.test.tsx | 19 -----------
src/components/portfolio-demo-notice.tsx | 32 -------------------
src/components/refresh-feed-button.test.tsx | 25 +++++++++++++++
src/components/refresh-feed-button.tsx | 16 +++++++---
11 files changed, 73 insertions(+), 104 deletions(-)
delete mode 100644 src/components/portfolio-demo-notice.test.tsx
delete mode 100644 src/components/portfolio-demo-notice.tsx
create mode 100644 src/components/refresh-feed-button.test.tsx
diff --git a/README.md b/README.md
index 2d6d0ec..39a0bd2 100644
--- a/README.md
+++ b/README.md
@@ -180,8 +180,8 @@ duplicate) with no credentials. Live mode additionally requires a server-only
The hosted portfolio deployment intentionally omits those private credentials.
It reads the persisted catalog through the public Supabase anon key under RLS,
-while live refresh and analysis render an archived-demo state instead of
-calling external providers.
+while the original refresh and analysis interfaces remain visible but disabled
+instead of calling external providers.
Opt-in live smoke test (never part of CI):
diff --git a/src/app/analyze/page.tsx b/src/app/analyze/page.tsx
index 2712b35..380f19a 100644
--- a/src/app/analyze/page.tsx
+++ b/src/app/analyze/page.tsx
@@ -1,7 +1,6 @@
import type { Metadata } from "next";
import { CapCheckApp } from "@/components/capcheck-app";
-import { PortfolioDemoNotice } from "@/components/portfolio-demo-notice";
import { isPortfolioDemoMode } from "@/lib/portfolio-mode";
export const metadata: Metadata = {
@@ -10,13 +9,5 @@ export const metadata: Metadata = {
};
export default function AnalyzePage() {
- if (isPortfolioDemoMode()) {
- return (
-
-
-
- );
- }
-
- return ;
+ return ;
}
diff --git a/src/app/globals.css b/src/app/globals.css
index fe6d938..34dfd4b 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1453,28 +1453,6 @@ input:disabled {
font-size: 0.92rem;
}
-.portfolio-demo-notice {
- margin-top: 24px;
-}
-
-.portfolio-demo-notice > svg {
- color: var(--ink);
-}
-
-.portfolio-demo-notice .kicker {
- margin-bottom: 6px;
-}
-
-.portfolio-demo-notice h2 {
- margin: 0 0 6px;
- color: var(--ink);
- font: 600 clamp(1.2rem, 2.5vw, 1.6rem)/1.2 var(--display);
-}
-
-.portfolio-demo-page {
- max-width: 760px;
-}
-
.feed-state-action {
display: inline-flex;
align-items: center;
diff --git a/src/app/page.tsx b/src/app/page.tsx
index e5e4da0..8f7be3e 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -2,7 +2,6 @@ import { TriangleAlert } from "lucide-react";
import Link from "next/link";
import { FeedExplorer } from "@/components/feed/feed-explorer";
-import { PortfolioDemoNotice } from "@/components/portfolio-demo-notice";
import { RefreshFeedButton } from "@/components/refresh-feed-button";
import type { CatalogItem } from "@/domain/feed";
import { isPortfolioDemoMode } from "@/lib/portfolio-mode";
@@ -58,11 +57,7 @@ export default async function FeedHome({
only if its claims held up. Open one to see the Cap Score, the
evidence, and the citations behind it.
- {portfolioDemo ? (
-
- ) : (
-
- )}
+
{failed ? (
diff --git a/src/components/capcheck-app.test.tsx b/src/components/capcheck-app.test.tsx
index 526b384..ed3af92 100644
--- a/src/components/capcheck-app.test.tsx
+++ b/src/components/capcheck-app.test.tsx
@@ -52,6 +52,23 @@ describe("CapCheckApp", () => {
expect(screen.getByText(/choose a video file/i)).toBeInTheDocument();
});
+ it("preserves the analyzer UI while disabling retired production actions", () => {
+ const fetchMock = vi.fn();
+ vi.stubGlobal("fetch", fetchMock);
+ const { container } = render();
+
+ expect(
+ screen.getByRole("heading", { name: /is that stock tip/i }),
+ ).toBeVisible();
+ expect(landingUrlInput()).toBeDisabled();
+ expect(checkItButton()).toBeDisabled();
+ expect(screen.getByLabelText(/choose a video file/i)).toBeDisabled();
+ expect(screen.getByText(/live analysis is disabled/i)).toBeVisible();
+
+ fireEvent.submit(container.querySelector(".intake form")!);
+ expect(fetchMock).not.toHaveBeenCalled();
+ });
+
it("submits a valid URL through the public stream and renders the scorecard", async () => {
const fetchMock = vi.fn().mockResolvedValue(
sseResponse(
diff --git a/src/components/capcheck-app.tsx b/src/components/capcheck-app.tsx
index e71c2a9..396c73a 100644
--- a/src/components/capcheck-app.tsx
+++ b/src/components/capcheck-app.tsx
@@ -18,7 +18,7 @@ const allowedScenarios = new Set([
"fatal",
]);
-export function CapCheckApp() {
+export function CapCheckApp({ readOnly = false }: { readOnly?: boolean } = {}) {
const [url, setUrl] = useState("");
const [file, setFile] = useState(null);
const [nextUrl, setNextUrl] = useState("");
@@ -52,6 +52,7 @@ export function CapCheckApp() {
}, [uploadPreviewUrl]);
const performAnalysis = async (submitUrl: string, submitFile: File | null) => {
+ if (readOnly) return;
setValidation("");
setMiniError("");
setError(null);
@@ -110,7 +111,7 @@ export function CapCheckApp() {
};
const analyze = () => {
- if (loading) return;
+ if (readOnly || loading) return;
const validationMessage = validateSubmission(url, file);
if (validationMessage) {
setError(null);
@@ -122,7 +123,7 @@ export function CapCheckApp() {
const analyzeNext = (event: FormEvent) => {
event.preventDefault();
- if (loading) return;
+ if (readOnly || loading) return;
const validationMessage = validateSubmission(nextUrl, null);
if (validationMessage) {
setMiniError(validationMessage);
@@ -209,6 +210,7 @@ export function CapCheckApp() {
url={url}
file={file}
loading={loading}
+ readOnly={readOnly}
error={error}
validation={validation}
onUrlChange={changeUrl}
diff --git a/src/components/intake-panel.tsx b/src/components/intake-panel.tsx
index 22128a0..5f62bf6 100644
--- a/src/components/intake-panel.tsx
+++ b/src/components/intake-panel.tsx
@@ -41,6 +41,7 @@ type Props = {
url: string;
file: File | null;
loading: boolean;
+ readOnly?: boolean;
error: ErrorEvent["error"] | null;
validation: string;
onUrlChange(value: string): void;
@@ -59,6 +60,7 @@ export function IntakePanel({
url,
file,
loading,
+ readOnly = false,
error,
validation,
onUrlChange,
@@ -116,18 +118,20 @@ export function IntakePanel({
id={inputId}
type="url"
value={url}
- disabled={loading || Boolean(file)}
+ disabled={readOnly || loading || Boolean(file)}
aria-invalid={Boolean(validation)}
aria-describedby={describedBy}
placeholder="https://www.youtube.com/shorts/…"
onChange={(event) => onUrlChange(event.target.value)}
/>
-