Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
26701fa
feat: refine X repost cards in grid and list
tomeredlich Feb 17, 2026
e96137c
feat: refine social X repost UI consistency
tomeredlich Feb 17, 2026
f19016c
Merge remote-tracking branch 'origin/main' into feat/social-twitter-r…
tomeredlich Feb 17, 2026
61137a8
feat(shared): align X repost metadata across cards and modal
tomeredlich Feb 17, 2026
53f4697
Merge branch 'main' into feat/social-twitter-repost-preview
rebelchris Feb 17, 2026
61f3bc3
fix(shared): add explicit type for metadataBottomLabel in SocialTwitt…
github-actions[bot] Feb 17, 2026
695960c
feat(shared): align social repost metadata sizing and quote headers
tomeredlich Feb 18, 2026
dbe9af4
refactor(shared): dedupe social Twitter metadata helpers
tomeredlich Feb 18, 2026
45a7685
fix(shared): format social repost text helper call
tomeredlich Feb 18, 2026
32e859c
Merge branch 'main' into feat/social-twitter-repost-preview
rebelchris Feb 18, 2026
7ebb5dc
fix: basic cleanup
rebelchris Feb 18, 2026
d29c82d
Merge branch 'main' into feat/social-twitter-repost-preview
rebelchris Feb 18, 2026
406f2cc
fix: more cleanup
rebelchris Feb 18, 2026
de1f4ae
Merge remote-tracking branch 'origin/feat/social-twitter-repost-previ…
rebelchris Feb 18, 2026
42b61ab
fix: more cleanup
rebelchris Feb 18, 2026
2b70065
fix: some minor changes
rebelchris Feb 18, 2026
8855f4d
fix: card renders
rebelchris Feb 19, 2026
3a580c6
fix(shared): align social repost UI across feed and modal
tomeredlich Feb 20, 2026
cf862e5
fix(shared): address social Twitter review feedback
tomeredlich Feb 20, 2026
c7cd859
feat(shared): add streak progression system with animations and miles…
tomeredlich Feb 23, 2026
23cdb3e
feat(shared): add streak increment/broken popovers, milestone celebra…
tomeredlich Feb 24, 2026
51570e4
feat(shared): add animated milestone sparkles
tomeredlich Feb 24, 2026
1164089
feat(shared): enhance streak milestone claim and sponsored reward UX
tomeredlich Feb 25, 2026
8c366bb
feat(shared): add streak drawer mode and persistent milestone share o…
tomeredlich Feb 25, 2026
fd0ce2e
feat(shared): refine reading milestone tooltip actions
tomeredlich Feb 25, 2026
b3b1fdb
feat(shared): polish milestone timeline spacing and sponsored visuals
tomeredlich Feb 25, 2026
94ed765
feat(shared): polish streak modal interactions and responsive behavior
tomeredlich Feb 25, 2026
e663dba
feat(shared): align streak reminder and +1 progress popovers
tomeredlich Feb 26, 2026
05e2277
feat(shared): refine streak popup timeline behavior and debug controls
tomeredlich Feb 26, 2026
545ae99
feat(shared): redesign milestone cards around reward headlines
tomeredlich Feb 26, 2026
9795c1f
feat(shared): polish streak recover and milestone timeline UI
tomeredlich Feb 26, 2026
907a70d
feat(shared): add feed night hero for reading streak nudges
tomeredlich Feb 26, 2026
c8c0eae
feat(shared): add debug hero mode overrides
tomeredlich Feb 27, 2026
f9c1076
feat(shared): add glowing butterflies animation to morning hero
tomeredlich Feb 27, 2026
a1ef477
fix: move butterfly keyframes out of night-only block and enhance mor…
tomeredlich Feb 27, 2026
5c46ea1
fix(shared): polish streak claim flow and timeline interactions
tomeredlich Feb 27, 2026
d5c7f00
docs(shared): add streak feature animation automation assets
tomeredlich Feb 27, 2026
90f7611
Merge remote-tracking branch 'origin/main' into feat/streak-progressi…
tomeredlich Mar 1, 2026
d9c7bfd
fix(shared): unblock CI for streak milestone assets and tests
tomeredlich Mar 1, 2026
14b63c1
fix(shared): use streak hook value in recover preview fallback
tomeredlich Mar 1, 2026
e90735b
fix(shared): polish streak hero theming and mobile drawer UX
tomeredlich Mar 2, 2026
b9115f7
feat(shared): refine streak surfaces and move nudges to toasts
tomeredlich Mar 3, 2026
6bdb890
fix(shared): polish streak hero copy and visual layering
tomeredlich Mar 3, 2026
1e70a3e
fix(shared): reduce streak hero greeting headline size
tomeredlich Mar 4, 2026
ad2dbd4
fix(shared): restore streak reminder popover and split milestone surf…
tomeredlich Mar 4, 2026
37bb05f
fix(shared): align streak hero morning and night backgrounds
tomeredlich Mar 4, 2026
bf8d65e
fix(shared): keep shortcut links out of feed hero preview
tomeredlich Mar 4, 2026
8673cd3
feat(shared): finalize streak progression rollout checks
tomeredlich Mar 5, 2026
b40d82d
fix(shared): update flame milestone reward to cores
tomeredlich Mar 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions .github/workflows/quality-gates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Quality Gates

on:
pull_request:
push:
branches:
- main

jobs:
quality-gates:
timeout-minutes: 45
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.22'

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.14.4

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run quality gates
run: pnpm verify:ci

e2e-smoke:
timeout-minutes: 45
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.54.2-noble
options: --user 1001
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.22'

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.14.4

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run anonymous smoke E2E tests
run: pnpm test:e2e:smoke

- name: Upload Playwright Report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-smoke-report
path: packages/playwright/playwright-report/
retention-days: 7
58 changes: 58 additions & 0 deletions RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Production Release Checklist

## CI gates (must be green)

- `pnpm verify:ci`
- lint across `shared`, `webapp`, `extension`
- typecheck across `shared`, `webapp`, `extension`
- unit/integration tests across `shared`, `webapp`, `extension`
- `pnpm test:e2e:smoke`
- anonymous browser smoke tests for core app routes

## E2E monitoring after deployment

- `E2E Tests` workflow runs after successful production deployment on `main`.
- Authenticated smoke tests require `USER_NAME` and `PASSWORD` secrets.

## Required environment and secrets checks

- Verify app env vars are set in production:
- `NEXT_PUBLIC_API_URL`
- `NEXT_PUBLIC_WEBAPP_URL`
- `NEXT_PUBLIC_DOMAIN`
- any feature-specific vars used by the release
- Verify CI secrets exist for authenticated E2E:
- `USER_NAME`
- `PASSWORD`
- `E2E_MONITORING_SLACK`

## Release-day smoke checks

- Open homepage and verify feed renders.
- Run search and verify results page opens.
- Open at least one post and navigate back to feed.
- Validate settings page is accessible for authenticated user.
- Validate extension new-tab feed still renders after release.

## Streak debug policy

- `?debugStreak` is disabled by default on production API.
- Internal QA can enable debug mode explicitly with:
- `localStorage.setItem('dd-streak-debug', 'enabled')`
- reload a URL that includes `?debugStreak`
- Remove local override after QA:
- `localStorage.removeItem('dd-streak-debug')`

## Shortcuts policy (webapp vs extension)

- Webapp supports custom shortcuts management and rendering.
- "Most visited sites" remains extension-only (browser permission based).
- Webapp surfaces an install-extension CTA for top-sites capability.

## Rollback readiness

- Confirm last known-good commit/tag before release.
- Keep rollback owner assigned and available during release window.
- If smoke fails post-release:
- rollback first
- analyze root cause after service is stable
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
"scripts": {
"prepare": "corepack enable || true",
"pnpm-version": "pnpm -v",
"lint": "pnpm --filter @dailydotdev/shared lint && pnpm --filter webapp lint && pnpm --filter extension lint",
"typecheck": "pnpm --filter @dailydotdev/shared typecheck && pnpm --filter webapp typecheck && pnpm --filter extension typecheck",
"typecheck:strict": "pnpm --filter @dailydotdev/shared typecheck:strict && pnpm --filter webapp typecheck:strict && pnpm --filter extension typecheck:strict",
"typecheck:strict:report": "pnpm --filter @dailydotdev/shared typecheck:strict || true; pnpm --filter webapp typecheck:strict || true; pnpm --filter extension typecheck:strict || true",
"test": "pnpm --filter @dailydotdev/shared test && pnpm --filter webapp test && pnpm --filter extension test",
"verify:ci": "pnpm lint && pnpm typecheck && pnpm test",
"test:e2e": "pnpm --filter playwright test",
"test:e2e:smoke": "pnpm --filter playwright test:smoke",
"test:e2e:headed": "pnpm --filter playwright test:headed",
"test:e2e:ui": "pnpm --filter playwright test:ui"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/extension/__mocks__/fileMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const mockFile = 'test-file-stub';

export default mockFile;
1 change: 1 addition & 0 deletions packages/extension/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: {
'\\.svg$': '<rootDir>/__mocks__/svgrMock.ts',
'\\.(png|jpe?g|gif|webp|avif)$': '<rootDir>/__mocks__/fileMock.ts',
'\\.css$': 'identity-obj-proxy',
'react-markdown': '<rootDir>/__mocks__/reactMarkdownMock.tsx',
},
Expand Down
1 change: 1 addition & 0 deletions packages/playwright/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"description": "E2E tests for daily.dev using Playwright",
"scripts": {
"test": "playwright test",
"test:smoke": "playwright test tests/smoke-anonymous.spec.ts --project=chromium",
"test:headed": "playwright test --headed",
"test:ui": "playwright test --ui",
"test:debug": "playwright test --debug",
Expand Down
4 changes: 4 additions & 0 deletions packages/playwright/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import dotenv from 'dotenv';
import path from 'path';

dotenv.config({ path: path.resolve(__dirname, '.env') });
const isLocalBaseUrl = /https:\/\/localhost(?::\d+)?/i.test(
process.env.BASE_URL ?? '',
);

/**
* @see https://playwright.dev/docs/test-configuration
Expand All @@ -23,6 +26,7 @@ export default defineConfig({
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.BASE_URL || 'https://app.daily.dev',
ignoreHTTPSErrors: isLocalBaseUrl,

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
Expand Down
78 changes: 78 additions & 0 deletions packages/playwright/tests/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { expect, type Locator, type Page } from '@playwright/test';

const cookieButtonNames = [
/accept all/i,
/accept/i,
/i understand/i,
/got it/i,
/allow all/i,
];

const tryClick = async (locator: Locator): Promise<boolean> => {
try {
if ((await locator.count()) === 0) {
return false;
}

await locator.first().click({ timeout: 2000 });
return true;
} catch {
return false;
}
};

export const dismissCookieBannerIfPresent = async (
page: Page,
): Promise<void> => {
for (const name of cookieButtonNames) {
const clicked = await tryClick(page.getByRole('button', { name }));
if (clicked) {
return;
}
}
};

export const expectFeedToBeVisible = async (page: Page): Promise<void> => {
await expect(page.getByTestId('posts-feed')).toBeVisible();
};

export const gotoAndExpectOk = async (
page: Page,
path: string,
): Promise<void> => {
const response = await page.goto(path, { waitUntil: 'domcontentloaded' });
expect(response, `No response while navigating to ${path}`).not.toBeNull();
expect(response?.ok(), `Navigation to ${path} returned ${response?.status()}`).toBeTruthy();
};

export const expectAppShellToBeVisible = async (page: Page): Promise<void> => {
const nextData = page.locator('script#__NEXT_DATA__');
await expect(nextData).toHaveCount(1);

const payload = await nextData.textContent();
expect(payload, 'Missing __NEXT_DATA__ payload').toBeTruthy();
expect(payload).toContain('"page"');
};

export const login = async (page: Page): Promise<void> => {
if (!process.env.USER_NAME || !process.env.PASSWORD) {
throw new Error('Missing USER_NAME/PASSWORD for authenticated tests');
}

await page.goto('/');
await dismissCookieBannerIfPresent(page);

await page.getByRole('button', { name: /^log in$/i }).first().click();
await page.getByRole('textbox', { name: /email/i }).fill(process.env.USER_NAME);
await page
.getByRole('textbox', { name: /password/i })
.fill(process.env.PASSWORD);
await page.getByRole('button', { name: /^log in$/i }).first().click();

await expect(
page
.getByRole('link', { name: /profile/i })
.or(page.getByRole('button', { name: /profile settings/i })),
).toBeVisible();
};

21 changes: 0 additions & 21 deletions packages/playwright/tests/login.spec.ts

This file was deleted.

47 changes: 47 additions & 0 deletions packages/playwright/tests/smoke-anonymous.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { test, expect } from '@playwright/test';
import {
dismissCookieBannerIfPresent,
expectAppShellToBeVisible,
gotoAndExpectOk,
} from './helpers';

test.describe('Anonymous smoke', () => {
test('loads homepage shell', async ({ page }) => {
await gotoAndExpectOk(page, '/');
await dismissCookieBannerIfPresent(page);

await expect(page).toHaveURL(/\/$/);
await expect(page).toHaveTitle(/daily\.dev/i);
await expectAppShellToBeVisible(page);
});

test('loads popular route for anonymous user', async ({ page }) => {
await gotoAndExpectOk(page, '/popular');
await dismissCookieBannerIfPresent(page);

await expect(page).toHaveURL(/popular/);
await expectAppShellToBeVisible(page);
});

test('loads search route for anonymous user', async ({ page }) => {
await gotoAndExpectOk(page, '/search?q=typescript');
await dismissCookieBannerIfPresent(page);

await expect(page).toHaveURL(/search/);
await expectAppShellToBeVisible(page);
});

test('supports browser back and forward between routes', async ({ page }) => {
await gotoAndExpectOk(page, '/');
await gotoAndExpectOk(page, '/popular');

await page.goBack();
await expect(page).toHaveURL(/\/$/);
await expectAppShellToBeVisible(page);

await page.goForward();
await expect(page).toHaveURL(/popular/);
await expectAppShellToBeVisible(page);
});
});

29 changes: 29 additions & 0 deletions packages/playwright/tests/smoke-authenticated.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { test, expect } from '@playwright/test';
import { expectFeedToBeVisible, login } from './helpers';

const hasCredentials = !!process.env.USER_NAME && !!process.env.PASSWORD;

test.describe('Authenticated smoke', () => {
test.skip(!hasCredentials, 'Requires USER_NAME/PASSWORD');

test('logs in successfully', async ({ page }) => {
await login(page);
});

test('loads my feed after login', async ({ page }) => {
await login(page);
await page.goto('/my-feed');

await expect(page).toHaveURL(/my-feed|app\.daily\.dev\/$/);
await expectFeedToBeVisible(page);
});

test('opens settings for authenticated user', async ({ page }) => {
await login(page);
await page.goto('/settings');

await expect(page).toHaveURL(/settings/);
await expect(page.getByRole('heading').first()).toBeVisible();
});
});

3 changes: 3 additions & 0 deletions packages/shared/__mocks__/fileMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const mockFile = 'test-file-stub';

export default mockFile;
1 change: 1 addition & 0 deletions packages/shared/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
moduleNameMapper: {
'^node-emoji$': 'node-emoji/lib/index.cjs',
'\\.svg$': '<rootDir>/__mocks__/svgrMock.ts',
'\\.(png|jpe?g|gif|webp|avif)$': '<rootDir>/__mocks__/fileMock.ts',
'\\.css$': 'identity-obj-proxy',
'react-markdown': '<rootDir>/__mocks__/reactMarkdownMock.tsx',
'react-turnstile': 'identity-obj-proxy',
Expand Down
Loading
Loading