Skip to content

Commit 37213b0

Browse files
committed
add beta button
1 parent a971adc commit 37213b0

4 files changed

Lines changed: 123 additions & 3 deletions

File tree

FINDINGS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
- The redesign can be A/B tested without forking the app logic by restoring the old UI into side-by-side legacy components and switching only the top-level rendered component tree.
133133
- For this experiment, `new-redesign = test` maps to the redesigned UI and the control/default path maps to the restored legacy UI.
134134
- A lightweight query/localStorage override is useful for QA because feature-flag experiments are otherwise awkward to verify locally.
135+
- A small shared beta toggle works better than duplicating experiment controls into both headers because it keeps the opt-in behavior consistent across stable and redesigned variants.
135136

136137
## Functional Parity Findings
137138
- The following capabilities should be preserved unless intentionally changed later:

PROGRESS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
- [x] Restored the pre-redesign UI from commit `c1a3998dfd2e6b4d5501d19f77ffe670a919eff2` into side-by-side legacy components for experiment use.
3737
- [x] Added a PostHog-powered app variant switch using the `new-redesign` feature flag, with `test` routed to the redesigned UI and control/default routed to the legacy UI.
3838
- [x] Added local `uiVariant` overrides for browser verification of both branches without changing the PostHog flag.
39+
- [x] Added a persistent beta toggle so users can opt into the redesigned UI or return to the stable UI without relying on query parameters.
3940

4041
## Immediate Next Steps
4142
- [x] Apply the system to editor, mapping, selection, and preview.
@@ -51,3 +52,4 @@
5152
- The redesign roadmap in `PLAN.md` has now been implemented end to end, with verification completed after the final pass.
5253
- The mobile mapping redesign preserves the same mapping model and controls as desktop; only the narrow-screen presentation changed.
5354
- The A/B test implementation keeps the same application state and business logic underneath both variants; only the UI component tree changes.
55+
- The beta toggle is implemented on top of the same override mechanism as the experiment QA tools, so user opt-in does not require any separate routing or deployment.

src/AppExperiment.tsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React from "react";
22
import { useFeatureFlagVariantKey } from "posthog-js/react";
33
import { App as RedesignedApp } from "./App";
44
import { App as LegacyApp } from "./AppLegacy";
5+
import { posthog } from "./lib/posthog";
6+
import styles from "./styles/AppExperiment.module.css";
57

68
type UiVariant = "legacy" | "redesign";
79

@@ -37,6 +39,7 @@ export const AppExperiment: React.FC = () => {
3739
const [override, setOverride] = React.useState<UiVariant | null>(() =>
3840
readOverride(),
3941
);
42+
const [isPending, startTransition] = React.useTransition();
4043

4144
React.useEffect(() => {
4245
const handlePopState = () => {
@@ -47,8 +50,49 @@ export const AppExperiment: React.FC = () => {
4750
return () => window.removeEventListener("popstate", handlePopState);
4851
}, []);
4952

50-
const uiVariant =
51-
override ?? (import.meta.env.DEV ? "redesign" : resolveFlagVariant(flagVariant));
53+
const assignedVariant =
54+
import.meta.env.DEV ? "redesign" : resolveFlagVariant(flagVariant);
55+
const uiVariant = override ?? assignedVariant;
5256

53-
return uiVariant === "redesign" ? <RedesignedApp /> : <LegacyApp />;
57+
const handleVariantChange = (nextVariant: UiVariant) => {
58+
if (typeof window === "undefined") return;
59+
60+
startTransition(() => {
61+
const nextUrl = new URL(window.location.href);
62+
nextUrl.searchParams.set(QUERY_PARAM, nextVariant);
63+
window.history.replaceState({}, "", nextUrl);
64+
window.localStorage.setItem(STORAGE_KEY, nextVariant);
65+
setOverride(nextVariant);
66+
posthog.capture("ui variant manually selected", {
67+
from_variant: uiVariant,
68+
to_variant: nextVariant,
69+
assigned_variant: assignedVariant,
70+
});
71+
});
72+
};
73+
74+
return (
75+
<>
76+
{uiVariant === "redesign" ? <RedesignedApp /> : <LegacyApp />}
77+
<div className={styles.betaSwitcher}>
78+
<span className={styles.betaLabel}>
79+
{uiVariant === "redesign" ? "Beta active" : "New interface"}
80+
</span>
81+
<button
82+
type="button"
83+
className={`${styles.betaButton} ${
84+
uiVariant === "redesign" ? styles.betaButtonSecondary : ""
85+
}`}
86+
onClick={() =>
87+
handleVariantChange(
88+
uiVariant === "redesign" ? "legacy" : "redesign",
89+
)
90+
}
91+
disabled={isPending}
92+
>
93+
{uiVariant === "redesign" ? "Leave beta" : "Try beta"}
94+
</button>
95+
</div>
96+
</>
97+
);
5498
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
.betaSwitcher {
2+
position: fixed;
3+
right: 1rem;
4+
bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
5+
z-index: 40;
6+
display: inline-flex;
7+
align-items: center;
8+
gap: 0.45rem;
9+
padding: 0.32rem 0.36rem 0.32rem 0.62rem;
10+
border-radius: 999px;
11+
border: 1px solid color-mix(in srgb, var(--line-subtle, var(--border-color)) 88%, transparent);
12+
background: color-mix(in srgb, var(--surface-panel, var(--bg-primary)) 92%, transparent);
13+
box-shadow: 0 10px 28px rgb(var(--shadow-rgb) / 0.16);
14+
backdrop-filter: blur(10px);
15+
}
16+
17+
.betaLabel {
18+
font-size: 0.72rem;
19+
font-weight: 700;
20+
color: var(--text-secondary);
21+
white-space: nowrap;
22+
}
23+
24+
.betaButton {
25+
appearance: none;
26+
border: 1px solid transparent;
27+
border-radius: 999px;
28+
min-height: 2rem;
29+
padding: 0.35rem 0.72rem;
30+
font-size: 0.74rem;
31+
font-weight: 800;
32+
color: #ffffff;
33+
background: color-mix(in srgb, var(--accent-cyan) 88%, black 5%);
34+
cursor: pointer;
35+
transition:
36+
transform 160ms ease,
37+
filter 160ms ease,
38+
opacity 160ms ease;
39+
}
40+
41+
.betaButton:hover:enabled {
42+
transform: translateY(-1px);
43+
filter: saturate(1.06);
44+
}
45+
46+
.betaButton:disabled {
47+
opacity: 0.5;
48+
cursor: wait;
49+
}
50+
51+
.betaButtonSecondary {
52+
color: var(--text-primary);
53+
background: color-mix(in srgb, var(--surface-panel-strong, var(--bg-secondary)) 90%, transparent);
54+
border-color: color-mix(in srgb, var(--line-subtle, var(--border-color)) 86%, transparent);
55+
}
56+
57+
@media (max-width: 640px) {
58+
.betaSwitcher {
59+
right: 0.75rem;
60+
left: 0.75rem;
61+
justify-content: space-between;
62+
}
63+
64+
.betaButton {
65+
flex-shrink: 0;
66+
}
67+
}
68+
69+
@media print {
70+
.betaSwitcher {
71+
display: none;
72+
}
73+
}

0 commit comments

Comments
 (0)