Skip to content

Commit c1b823c

Browse files
Magneaclaude
andcommitted
feat(preview-features): tag unseen features NEW in drawer
Unread ids are snapshotted when the drawer opens, before markAllRead, so the tags stay visible for the session in which the user first sees the new features and disappear on the next open. Separate i18n message from the episode NEW tag so translators can gender it per noun. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent aeadd53 commit c1b823c

4 files changed

Lines changed: 46 additions & 6 deletions

File tree

projects/client/i18n/meta/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7890,6 +7890,10 @@
78907890
"default": "Preview Features",
78917891
"description": "Header for the section that allows users to enable or disable preview features."
78927892
},
7893+
"tag_text_new_preview_feature": {
7894+
"default": "New",
7895+
"description": "Text for the tag shown next to a preview feature's name when the feature was recently added and the user has not seen it yet. This should be as short as possible."
7896+
},
78937897
"button_label_unread_preview_features": {
78947898
"default": "Preview Features (new features available)",
78957899
"description": "Aria-label for the preview features button when there are new preview features the user has not seen yet."

projects/client/src/lib/features/feature-flag/FeatureFlagItems.svelte

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import CaretRightIcon from "$lib/components/icons/CaretRightIcon.svelte";
33
import Link from "$lib/components/link/Link.svelte";
4+
import StemTag from "$lib/components/tags/StemTag.svelte";
45
import Switch from "$lib/components/toggles/Switch.svelte";
56
import { m } from "$lib/features/i18n/messages";
67
import RenderFor from "$lib/guards/RenderFor.svelte";
@@ -9,7 +10,10 @@
910
import { featureFlagDefinitions } from "./models/featureFlagDefinitions";
1011
import { useFeatureFlag } from "./useFeatureFlag";
1112
12-
const { classList = "" }: { classList?: string } = $props();
13+
const {
14+
classList = "",
15+
newFeatures = [],
16+
}: { classList?: string; newFeatures?: ReadonlyArray<string> } = $props();
1317
1418
const { flags, setFlag } = useFeatureFlag();
1519
const featureFlags = Object.values(FeatureFlag);
@@ -32,7 +36,17 @@
3236
</div>
3337

3438
<div class="feature-flag-copy">
35-
<span class="feature-flag-title bold">{title}</span>
39+
<span class="feature-flag-title-row">
40+
<span class="feature-flag-title bold">{title}</span>
41+
{#if newFeatures.includes(key)}
42+
<StemTag
43+
text={m.tag_text_new_preview_feature()}
44+
classList="feature-flag-new-tag"
45+
--color-background-stem-tag="var(--color-background-purple)"
46+
--color-foreground-stem-tag="var(--color-foreground-purple)"
47+
/>
48+
{/if}
49+
</span>
3650
{#if description}
3751
<p class="feature-flag-description secondary small">
3852
{description}
@@ -121,6 +135,17 @@
121135
min-width: 0;
122136
}
123137
138+
.feature-flag-title-row {
139+
display: flex;
140+
align-items: center;
141+
gap: var(--gap-xs);
142+
min-width: 0;
143+
144+
:global(.feature-flag-new-tag) {
145+
flex-shrink: 0;
146+
}
147+
}
148+
124149
.feature-flag-description {
125150
line-height: 1.3;
126151
white-space: pre-line;

projects/client/src/lib/features/feature-flag/FeatureFlagTool.svelte

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,16 @@
1212
const isOpen = writable(false);
1313
const onClose = () => isOpen.set(false);
1414
15-
const { hasUnreadFeatures, markAllRead } = useUnreadPreviewFeatures();
15+
const { hasUnreadFeatures, unreadFeatures, markAllRead } =
16+
useUnreadPreviewFeatures();
17+
18+
// Snapshot of the unread ids taken when the drawer opens, so the NEW
19+
// tags stay visible for the session in which the user first sees them.
20+
let newFeatures = $state<ReadonlyArray<string>>([]);
1621
1722
const onToggle = () => {
1823
if (!$isOpen) {
24+
newFeatures = $unreadFeatures;
1925
markAllRead();
2026
}
2127
isOpen.set(!$isOpen);
@@ -52,7 +58,7 @@
5258
size="auto"
5359
>
5460
<div class="trakt-feature-flag-items">
55-
<FeatureFlagItems />
61+
<FeatureFlagItems {newFeatures} />
5662
</div>
5763
</Drawer>
5864
{/if}

projects/client/src/lib/features/feature-flag/useUnreadPreviewFeatures.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ export function useUnreadPreviewFeatures() {
2828
initializeReadFeatures(),
2929
);
3030

31-
const hasUnreadFeatures = readFeatures.pipe(
31+
const unreadFeatures = readFeatures.pipe(
3232
map((read) =>
33-
Object.values(FeatureFlag).some((feature) => !read.includes(feature))
33+
Object.values(FeatureFlag).filter((feature) => !read.includes(feature))
3434
),
3535
);
3636

37+
const hasUnreadFeatures = unreadFeatures.pipe(
38+
map((unread) => unread.length > 0),
39+
);
40+
3741
const markAllRead = () => {
3842
const allFeatures: ReadonlyArray<string> = Object.values(FeatureFlag);
3943
safeLocalStorage.setItem(
@@ -45,6 +49,7 @@ export function useUnreadPreviewFeatures() {
4549

4650
return {
4751
hasUnreadFeatures,
52+
unreadFeatures,
4853
markAllRead,
4954
};
5055
}

0 commit comments

Comments
 (0)