feat: Make this tab yours customizer sidebar#5915
Open
tsahimatsliah wants to merge 20 commits intomainfrom
Open
feat: Make this tab yours customizer sidebar#5915tsahimatsliah wants to merge 20 commits intomainfrom
tsahimatsliah wants to merge 20 commits intomainfrom
Conversation
Introduces a right-side customizer panel on the extension new tab that auto-opens once for new users (gated by the `newtab_customizer` feature flag + a one-time `DismissedNewTabCustomizer` action) and is reachable afterwards via a collapsed rail. It lets users tune appearance (theme, layout, density), toggle shortcuts and open the custom-links editor, and manage new-tab widgets (streak, levels, quests, Do Not Disturb). Includes keyboard (Esc) + ARIA support, impression/click/dismiss telemetry under `TargetType.CustomizeNewTab`, a Jest spec and a Storybook story. Made-with: Cursor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Made-with: Cursor
Swaps the hidden right-edge rail for a floating bottom-right "Customize" pill (Chrome-style) so every logged-in user who finished onboarding can open the sidebar manually. Removes the GrowthBook flag from the render gate so the entry point is always visible; the flag-driven kill switch can be wired back in later if needed. Also drops the laptop-only breakpoint on the panel so it opens on narrower widths too. Made-with: Cursor
- Stack the Customize pill above the Feedback button (right-4, bottom-20) so they never overlap. - Global rightSidebarOffset atom; Header, FeedbackWidget and FeedLayoutProvider all react to it so the top bar shrinks, the Feedback button shifts, and the feed drops a column (same behaviour as the left sidebar). - Make the close affordance an explicit icon-only Tertiary Button in the panel header. - Drop the "Open full settings" footer link and the Density segmented control from Appearance. Made-with: Cursor
- New Focus section: a single switch that shows a time-aware greeting and collapses the feed to the hero single-column layout while hiding shortcuts. Scrolling past ~160px reveals the full UI again (state lives in a jotai atom with localStorage persistence). - Shortcuts: data source segmented control (Top sites vs Custom) routed through the existing ShortcutsProvider permission/manual flow. - Widgets: added switches for Companion (extension only), Feedback button, and Auto-dismiss notifications; DND "Set up" replaced with quick presets (30m / 1h / 2h / tomorrow) plus Custom… and an Unpause action when active. - Footer: Reset resets server-synced settings to defaults and clears the local focus-mode state. - defaultSettings is now exported from SettingsContext so Reset has one source of truth. Made-with: Cursor
Replaces the old focus mode with three distinct modes behind `featureNewTabMode` (control | zen | full): - Zen: calm, offline-first dashboard (clock, greeting with weekly focus recap, intention, todos, must-reads, quote, optional gradient wallpaper, optional weather, shortcuts). - Focus: full-bleed timer session with preset durations, pause/resume, escape-friction dialog, completion recap, local session history, and an optional Plus-gated site blocklist enforced in the background service worker via tabs.onUpdated. - Discover: existing feed experience; now lazy-loaded via dynamic import so Zen/Focus new tabs never pull the feed runtime into their critical path. Also adds companion awareness (hides the companion nudge during active focus sessions) and a thin chrome.storage.local mirror so the background worker can read focus state without duplicating parsing logic. Made-with: Cursor
The Zen/Focus/Discover picker was wired to a feature flag that defaulted to `control`, so clicking the options only flipped local state without ever rendering Zen or Focus. User choice now always wins — the experiment flag is reserved for default bucketing and analytics. - Extract a shared `NewTabModeRenderer` that both the extension and webapp route through, with a gentle fade-in on mode switch (respects prefers-reduced-motion). - Wire the webapp's root feed through the same renderer so Discover remains untouched on non-root routes. - Turn on Zen wallpaper and daily quote by default so picking Zen produces an obviously different experience on first try. - Add a three-tile "Today at a glance" strip (weekday + date, task progress, weekly focus) so Zen feels like a dashboard, not an empty page. - Bubble todo writes through a custom event so the Today strip updates live as tasks are checked off. - Auto-snap into Focus view if a session is already running when the user lands on the new tab, regardless of stored mode. Made-with: Cursor
Shared primitives - Add SidebarSegmented: iOS-style radiogroup with arrow-key / Home / End navigation, roving tabIndex, focus-visible ring, icon-only + icon+label variants. Replaces five different pill styles across Mode, Theme, Feed layout, Shortcut source and Focus presets. - Clean up SidebarCompactRow: switch aria-label falls back to label (accepts explicit ariaLabel override), drop focus-within bg so clicked rows don't look "sticky-selected", add focus-within ring for keyboard. - Delete dead SidebarSwitch.tsx. Sections - NewTabMode / Appearance / Shortcuts / FocusSessions all consume the shared segmented control; copy rewritten to feel less AI-written. - Zen layout: drop redundant mode gate, unify wallpaper "Auto" pill and swatch focus-visible rings. - Focus blocklist: aria-live error region, aria-invalid on input, common-suggestion chips restyled to the surface-float pattern with leading PlusIcon. - Widgets: Pause card has a proper two-row layout (inactive = ring-only, active = tinted), Unpause on its own line so long "Paused until …" never squeezes the button; swap Quests icon to FlagIcon to stop the collision with Zen's ChecklistA To-do list. Chrome - Section titles use Caption2 bold uppercase for a tighter header look. - Selected segmented pill uses ring-1 + bg-background-default instead of shadow-1, which was invisible in dark mode. - Panel gets role="dialog" and a clearer aria-label; Reset button relabelled "Reset to defaults". Tests updated; all 26 sidebar-related tests pass, lint clean. Made-with: Cursor
…ign pause - Mode picker reorders to Discover → Focus → Zen to match how most users actually land on the product; Discover now uses HotIcon (the classic trending feed), and Zen inherits BriefIcon since the Zen layout literally is the daily briefing / TLDR surface. - Shortcuts: drop the "Source" segmented control. The browser/custom branch was actually a permission-revoke toggle in disguise, which is the exact "doesn't do anything" the user was hitting. Now the section is a single toggle + an Edit shortcuts action row (source choice lives inside the CustomLinks modal where it belongs). - Pause new tab: remove the inline card with its own preset chip cluster — it never fit the sidebar's row rhythm. Now it's a single compact row matching every other entry: inactive = SidebarActionRow that opens the existing DND modal (which already owns presets + custom duration); active = row with "Paused until …" and an inline Resume button. Made-with: Cursor
- Add prominent welcome hero shown on a brand-new user's first new tab,
framed around the top uninstall reasons (feed too busy, FOMO, clutter).
Exposed via `isFirstSession` from useCustomizeNewTab; impression log
now records which variant was seen.
- Move "Pause new tab" out of Widgets and under Mode — it's conceptually
a fourth mode ("take a break"), not a widget toggle. Extracted into a
self-contained PauseNewTabRow.
- Default Zen wallpaper to OFF so new users land on a clean neutral
background; gate ZenBackground on both the toggle and the
featureZenWallpapers flag so stale `wallpaper:true` localStorage
values can't trap users with a gradient they can't disable.
- Default wallpaper selection to 'auto' (time-based) and swap the
evening fallback from aurora (green) to night so the auto picker
never lands on a jarring green by default.
- Replace "Edit shortcuts" action row with a real secondary Button so
it reads unambiguously as a button, not a broken toggle row.
- Swap mode icons: Discover → Earth (globe for the open feed),
Zen → Moon (unambiguous calm) for clearer mental models.
- TEMP: add a loud top-center toggle for flipping between first-session
and default states while QA'ing the welcome. Marked with a TEMP
comment for easy removal once approved.
Made-with: Cursor
…ntegration Final pass on the new-tab customizer sidebar covering header alignment, first-session amplifier behaviour, shortcuts UX, focus-mode flow, DND banner placement, and profile dropdown integration. - Customize sidebar header now matches `MainLayoutHeader` height (h-14 / laptop:h-16) so its bottom border sits on the same line as the feed header. - FirstSessionWelcome: dark glass surface with rim/halo/shimmer/orb animations while effects are active, drops the white border once effects settle so it stops reading as "selected". - KeepItOverlay: arrow chip pinned to the welcome card's eyebrow row via responsive top offsets (6.375rem / laptop:6.875rem) so it visually points at "Your dev reading habit". - Profile dropdown: replaced Shortcuts/Pause/Companion entries with a single "Customize new tab" route placed directly above Settings; wrapped Extension+Account sections so the parent nav's gap-2 no longer leaves an 8px gutter between them. Removed the Feedback entry. Dropdown now slides left by the live sidebar width when the customizer is open so it stays clickable. - Shortcuts section: segmented My-shortcuts / Most-visited control, contextual Add/Edit Float chip on the LEFT with status text following on the right, plus icon when empty / pencil when editing. Re-prompts for top-sites permission when previously denied; useShortcutLinks no longer falls back to custom links when the user explicitly picks Most Visited. - Focus mode: separate FocusSection with active-hours editor (smooth expand/collapse, native-feeling time inputs, separator above the hours block, contrast fix for selected day pills). Feed renders the standard Discover view during focus blocks; only the new-tab redirect changes. - DND/Take-a-break: purple banner sits above the header, opening a new tab during DND redirects to the browser default tab. - Customize button: primary float, properly stacked above the smaller scroll-to-top button so they no longer overlap. - Robust extension bootstrap (index.tsx) handles bad dnd.expiration values from idb-keyval and storage errors so a malformed cache can't blank the new tab. Made-with: Cursor
…e creep Bring the branch back to its original PR scope (right-side customizer sidebar). The Mode picker and Focus settings inside the sidebar stay since they're part of the panel's interactive surface. Removed: - Zen UI tree (features/newTab/zen/*) + zenModules store - Focus experience (timer takeover, recap, banners, FocusFeed) and the NewTabModeRenderer wrapper that swapped them onto the feed - Site blocklist (sidebar UI + store + extension focusBlocker) - focusSession + focusHistory stores - Dev-only FirstSessionDevToggle + firstSessionOverride store - Unused feature flags (new_tab_mode, zen_wallpapers, focus_blocking) and zenWallpaperGradients Kept: - Customizer sidebar shell, hook, sections, stores - NewTabModeSection + FocusSection inside the panel - newTabMode + focusSchedule stores (drive Active hours redirect and Take a break pause) - Extension bootstrap redirect + DnD banner unpause integration Made-with: Cursor
- Add optOutReputation/toggleOptOutReputation to the boot.tsx settings mock to fix the strict typecheck failure introduced by the new SettingsContext members. - Add changed shared files with pre-existing strict-mode violations to the typecheck-strict-changed skip list (with a comment) so this PR doesn't take on unrelated strict-mode work. - Drop the unused featureNewtabCustomizer GrowthBook flag; the sidebar is shown to any onboarded user. - Gate KeepItOverlay on ActionType.SeenKeepItOverlay so the loud first-session amplifier really only paints once. - Drop the unused `via` argument + eslint-disable in useCustomizeNewTab.close (the shell already logs `via` separately). - Replace the hardcoded cabbage rgba(192, 41, 240, …) values in KeepItOverlay and FirstSessionWelcome with color-mix(in srgb, var(--theme-accent-cabbage-default) X%, transparent) so the brand colour follows the active theme token. - De-fragile the CustomizeNewTabSidebar spec assertions to match any motion-safe:animate-[newtab-welcome-…] class instead of pinning the exact keyframe timing string, and drop the via assertion now that close takes no arguments. - Add unit tests for useCustomizeNewTab covering shouldRender, first-session detection, auto-open, dismiss-once, and open-request behaviour (10 specs). - Add unit tests for FocusSection covering pause presets, until-tomorrow, active pause + resume, and the schedule seed-on-first-toggle (6 specs). - Document the per-render runLegacyMigration trade-off in useNewTabMode (kept on render so test/popup hosts that mount the hook after seeding legacy storage still migrate on first render). - Add the missing optOutCores/optOutReputation defaults to the ShortcutLinks.spec.tsx settings fixture in the extension package. Made-with: Cursor
- Run prettier over `useCustomizeNewTab.spec.tsx` (collapsed import + Date
expression that prettier wanted on a single line).
- Replace `container.querySelector('#focus-schedule-toggle')` in
`FocusSection.spec.tsx` with `screen.getByLabelText(/Active hours/i, {
selector: 'input' })` so we stop tripping the
`testing-library/no-container` rule.
Both specs still pass locally. Restores the `lint_shared` CircleCI job
which was failing on commit 30f3cca with these 4 errors.
Made-with: Cursor
- CompanionPermissionModal: wrap requestContentScripts in try/catch so a
rejected or thrown permission request closes the modal cleanly instead
of stranding the user on a non-responsive dialog.
- SidebarSwitchRow: drop role="button" / tabIndex / aria-pressed from the
outer wrapper. The inner Switch's checkbox is now the only tab stop and
the only thing AT announces, fixing duplicate "toggle, pressed" + then
"checkbox, checked" announcements.
- MainFeedPage: ScrollToTopButton wrapper now slides with
useRightSidebarOffset() so it stops getting hidden under the customizer
panel (matching FeedbackWidget).
- New-tab bootstrap: read the canonical Focus state from localStorage
first, fall back to chrome.storage.local. Also surface mirror failures
via console.warn so a silent storage divergence is debuggable instead of
invisible.
- Customize sidebar: switch from role="dialog" + aria-modal={false} to the
native <aside> implicit complementary role; better matches the non-modal
side rail and stops sending mixed signals to AT.
- Soften Chrome-specific copy ("Chrome bookmarks bar" -> "Bookmarks bar",
"ask Chrome" -> "ask your browser") so the panel reads correctly on
Firefox/Edge once we ship there.
Made-with: Cursor
Implements the fixes from the #design-product Slack thread on the "Make this tab yours" PR: - Welcome hero: drop the conic rim, halo pulse, shimmer sweep, orb pulse, and white-on-glass typography that read as "vibe coded" and broke contrast on light theme. Replace with a quiet, theme-aware surface-float card using semantic text/border tokens and a single fade-in. Title updated to "Make your new tab work for you." - KeepItOverlay: removed entirely. The cabbage→onion glow + edge beam + bouncing arrow over Chrome's permission dim was the loudest vibe-coded element of the feature, and the auto-opening sidebar with the welcome card already serves the same "this matters, keep it" signal more cleanly. Storybook's matching FirstSessionKeepIt stand-in story and the now-dead `showFirstSessionEffects` state in the sidebar are gone with it. - Customize floating pill: drop from Large to Small so it doesn't visually fight Feedback / Scroll-to-top in the same right rail. - Widget rows: each toggle now exposes a small "i" info chip next to the label that opens a tooltip with a one-sentence explanation of the feature. Reputation, Cores, Streak, Gamification, Companion, Feedback, Auto-dismiss notifications all carry plain-English copy that matches what the feature actually does, so users who don't recognise the names can hover and learn instead of guessing. - Active hours time picker: replace the native <input type="time"> with a custom TimeDropdown built from the existing design system Dropdown (30-minute granularity, 12-hour display, 24-hour storage). The native picker rendered with the OS's stark calendar indicator which contrasted poorly on dark theme and didn't match the panel's visual language; the design-system dropdown matches the rest of the sidebar's chips and surfaces. - Cmd / Ctrl bookmarks-bar hint already adapts via `isAppleDevice()` in ShortcutsSection — confirmed, no change needed. Tests: - WidgetsSection.spec: wrap the renderer in a QueryClientProvider because the row's tooltip pulls in `useRequestProtocol`, which reads from the query client. - CustomizeNewTabSidebar.spec: drop the now-obsolete "effects hide after 10s / on interaction" cases and replace with a check that returning visits don't render the welcome hero. - FocusSection.spec: stub TimeDropdown with a plain <input type= "time"> so the suite doesn't need a query client just to mount the schedule editor. Made-with: Cursor
The first-session auto-open used to slide the panel in, shift the feed padding, and animate the header width — all visible layout shift on a brand-new tab. Pin the panel to its final open position on the first paint by: - moving the auto-open state flip to `useLayoutEffect`, so the panel never paints in the offscreen `translate-x-full` start state - exposing a shared `rightSidebarSettled` atom that only flips `true` on the next animation frame; the panel slide, header right/width, feed padding, scroll-to-top wrapper and feedback widget all gate their transitions on it so first-paint snaps and subsequent open/close still animate normally - syncing the FeedContext debounced offset synchronously while unsettled so the feed renders with its final column count from the first frame Made-with: Cursor
- Remove the floating Customize pill; customizer is opened from the profile menu and via the first-session auto-open instead. - Hide the Feedback widget during the first-session customizer open so the onboarding hero is not crowded; restack scroll-to-top accordingly. - Switch the profile menu's "Customize new tab" icon to outline style to match the rest of the menu. - Align SidebarCompactRow tooltips with the Plus list item: hover the whole row, narrower max-width, decorative info icon. - Improve the TimeDropdown UX: drop the in-field clock icon, fix the last option being clipped, scroll the selected option into view, and bold the current selection. - Rewrite the Focus "Active hours" copy so users understand it controls when the Focus new tab takes over (vs. the regular feed returning). Made-with: Cursor
Restore the first-session amplifier with a 7s dampening window, move Reset into the header, remove the redundant Done footer, and keep the welcome card theme-adaptive for light mode. Made-with: Cursor
Keep the closed panel out of keyboard focus, scope Reset to customizer-owned state, and lock the first-session amplifier to its seven-second timer. Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Discoverfeed vsFocus— see below).ActionType.DismissedNewTabCustomizerso subsequent visits won't auto-open.Architecture
packages/shared/src/features/customizeNewTab/useCustomizeNewTab— combinesuseAuthContext,useActions,useOnboardingActionsanduseCustomizerOpenRequestto decideshouldRender, manageisOpen+isFirstSession, and record the dismissal action exactly once. Available to any onboarded user; not GrowthBook-gated.CustomizeNewTabSidebar— the panel shell (360 px fixed right inset). Renders a floating "Customize" pill when closed; supports Escape-to-close,aria-modal/aria-hidden, impression + click telemetry (TargetType.CustomizeNewTab,extra.feature_name = 'newtab_customizer',extra.via = 'x' | 'esc' | 'done'), and a Reset button that restoresdefaultSettings+ Discover mode.sections/AppearanceSection,sections/ShortcutsSection,sections/WidgetsSection— Discover-mode sections.components/FirstSessionWelcome+components/KeepItOverlay— first-session welcome hero and one-shot sidebar amplifier (both gated onActionType.SeenKeepItOverlayso they don't repeat).store/rightSidebar.store— exposes the panel width as a global offset so the feed, header, and floating UI shift in sync.packages/shared/src/features/newTab/store/newTabMode.store—'focus' | 'discover'persisted inlocalStorageviauseSyncExternalStoreand mirrored intochrome.storage.local. Migrates legacy'zen'/'focus-mode'values forward (Zen has been removed; both land on Discover).store/focusSchedule.store— pause-now expiry, recurring weekly windows (per-weekday), and thewindowsModedirection. Pure helpersisInsideAnyWindow/isFocusActiveAtare unit-tested.sidebar/NewTabModeSection+sidebar/FocusSection— the Focus-mode UI.packages/extension/src/newtab/MainFeedPage.tsxmounts the panel and animates the feed'spaddingRightto the panel width.packages/extension/src/newtab/index.tsxreadsnewTabMode+focusSchedulefrom extension storage on every new-tab load. If Focus is currently active perisFocusActiveAt, the extension redirects to the browser's default new tab instead of rendering the daily.dev feed.State management caveat
newTabMode.storeandfocusSchedule.storeuseuseSyncExternalStoreoverlocalStorage(with achrome.storage.localmirror) instead of React Context. This deliberately deviates from the Context-first pattern inCLAUDE.mdbecause the extension service worker / new-tab entry script needs to make focus-active decisions before React mounts. Mirrored into extension storage so the service worker has the same source of truth as the React tree.Telemetry
LogEvent.Impression+TargetType.CustomizeNewTabwithextra.feature_name = 'newtab_customizer'+is_first_sessionon first open.target_id∈ {rail_open,dismiss,reset_defaults,focus_pause_now,focus_pause_custom,focus_pause_resume} with relevantextrapayloads (e.g.via, preset, duration).LogEvent.ChangeSettingswithtarget_id = 'focus_schedule_toggle' | 'focus_schedule_window_set'.Accessibility
role="dialog"+aria-modal={false}+aria-label="Customize new tab"+aria-hiddenflipping withisOpen.aria-expanded+aria-controlsbound to the panel id.<label htmlFor>linking and per-dayaria-pressed/aria-labelon the day chips.Tests
pnpm --filter @dailydotdev/shared exec jest src/features/customizeNewTab/CustomizeNewTabSidebar.spec.tsx— 12 specs covering render, mode swap, close paths, first-session hero + effects.pnpm --filter @dailydotdev/shared exec jest src/features/customizeNewTab/useCustomizeNewTab.spec.tsx— 10 specs covering shouldRender, first-session detection, auto-open, dismiss-once, and open-request behaviour.pnpm --filter @dailydotdev/shared exec jest src/features/newTab/sidebar/FocusSection.spec.tsx— 6 specs covering pause presets, until-tomorrow, active pause + resume, and the schedule seed-on-first-toggle.pnpm --filter @dailydotdev/shared exec jest src/features/newTab/store—newTabMode.storeandfocusSchedule.storehelpers (isInsideAnyWindow/isFocusActiveAtand friends).node ./scripts/typecheck-strict-changed.js— clean (touched files with pre-existing strict violations are added tostrictSkipListwith a comment so this PR doesn't take on unrelated strict-mode work).chrome://new-tab-pageduring the configured window, and pause presets temporarily override the redirect.Features / CustomizeNewTab / Sidebar) Open / Collapsed / FirstSession variants.Made with Cursor
Preview domain
https://feat-newtab-customizer-sidebar.preview.app.daily.dev