From 6302fbc7595afa0003c2ae770234495161d58017 Mon Sep 17 00:00:00 2001 From: Ryota Murakami Date: Thu, 16 Jul 2026 16:50:01 +0900 Subject: [PATCH 1/3] feat: add reproducible Meticulous recording suite --- .github/workflows/meticulous.yml | 12 ++ app/[locale]/page.tsx | 17 +- docs/meticulous.md | 231 +++++++++++++++++++++++ e2e/meticulous/constants.ts | 7 + e2e/meticulous/meticulous-window.d.ts | 10 + e2e/meticulous/recording-helpers.ts | 62 +++++++ e2e/meticulous/recording.spec.ts | 258 ++++++++++++++++++++++++++ e2e/pwa-shortcuts.spec.ts | 29 +++ package.json | 1 + playwright.config.ts | 2 + playwright.meticulous.config.ts | 33 ++++ 11 files changed, 648 insertions(+), 14 deletions(-) create mode 100644 docs/meticulous.md create mode 100644 e2e/meticulous/constants.ts create mode 100644 e2e/meticulous/meticulous-window.d.ts create mode 100644 e2e/meticulous/recording-helpers.ts create mode 100644 e2e/meticulous/recording.spec.ts create mode 100644 e2e/pwa-shortcuts.spec.ts create mode 100644 playwright.meticulous.config.ts diff --git a/.github/workflows/meticulous.yml b/.github/workflows/meticulous.yml index ed3db30..739c664 100644 --- a/.github/workflows/meticulous.yml +++ b/.github/workflows/meticulous.yml @@ -17,6 +17,10 @@ permissions: jobs: test: + if: >- + github.event_name != 'pull_request' || + (github.event.pull_request.head.repo.full_name == github.repository && + github.actor != 'dependabot[bot]') runs-on: ubuntu-latest timeout-minutes: 30 @@ -42,6 +46,12 @@ jobs: NODE_ENV: production METICULOUS_BUILD: 'true' + # Serve immutable Next.js chunks directly to make visual replays faster and steadier. + - name: Prepare companion assets + run: | + mkdir -p companion-assets/_next + cp -r .next/static companion-assets/_next/ + - name: Start app run: | pnpm start & @@ -52,3 +62,5 @@ jobs: with: api-token: ${{ secrets.METICULOUS_API_TOKEN }} app-url: 'http://localhost:3009' + companion-assets-folder: 'companion-assets' + companion-assets-regex: '^/_next/static/' diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index db33e19..55fc75b 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -86,20 +86,9 @@ const Home = memo(function Home() { ) useTimerSessionSave(timeRemaining, initialTime, soundPreset, userSetTimeRef) - // Start timer from PWA shortcut (separate function to handle async) - const handleStartFromShortcut = useCallback(async () => { - try { - await audioManager.initialize() - const currentPreset = useSettingsStore.getState().soundPreset - if (currentPreset !== 'none') { - await audioManager.preload(currentPreset) - } - useTimerStore.getState().start() - } catch (error) { - console.error('[Shortcut] Failed to start timer:', error) - // Still try to start timer even if audio fails - useTimerStore.getState().start() - } + /** Starts a PWA shortcut timer synchronously because ShortcutHandler has no gesture to unlock Web Audio. @returns Nothing; the timer store is updated immediately. @example handleStartFromShortcut() */ + const handleStartFromShortcut = useCallback(() => { + useTimerStore.getState().start() }, []) // Open settings callback for PWA shortcut (memoized for ShortcutHandler) diff --git a/docs/meticulous.md b/docs/meticulous.md new file mode 100644 index 0000000..b68e10d --- /dev/null +++ b/docs/meticulous.md @@ -0,0 +1,231 @@ +# Meticulous recording and CI guide + +This note records the current Meticulous behavior and the recommended setup for Coffee Timer. It is based only on Meticulous's official documentation and official `alwaysmeticulous` repositories, plus GitHub's official documentation for fork-secret behavior. + +## Executive summary + +- A recording is a **session**, but not every recorded session is automatically part of the CI test suite. Meticulous continuously chooses a coverage-maximizing subset called **Selected Sessions**. A critical flow can be forced into the suite with **Add to selected sessions**. [Manual recording](https://app.meticulous.ai/docs/how-to/manually-recording-tests) · [Session selection](https://app.meticulous.ai/docs/how-to/testing-pool) +- The recorder should be a synchronous native `