diff --git a/.gitignore b/.gitignore
index e3009e6..18e828f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,5 @@ node_modules/
.pnp.loader.mjs
.yarnrc.yml
*storybook.log
+.env
+drafts/
diff --git a/AGENTS.md b/AGENTS.md
index 33ec614..cab7d21 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -11,12 +11,6 @@
respond in that language.
- If a message is mixed-language, reply in the dominant language unless the
user specifies otherwise.
-- Run `make eslint` before handoff or commit preparation only when changed
- files include code covered by eslint rules (for example `*.js`, `*.ts`,
- and similar source files). Do not run `make eslint` for markdown-only
- changes (for example `*.md`).
-- Getter/helper functions must be side-effect free. Side effects are allowed
- only by prior agreement and only when there are strong, explicit reasons.
## Reporting
- Keep handoff reports natural and outcome-focused: describe what was done.
@@ -26,9 +20,9 @@
affect correctness, safety, or reproducibility.
## Purpose
-This file defines practical instructions for working in the
-`modulify/m3-web` repository, with a focus on test execution and commit
-workflow.
+This file defines practical instructions for day-to-day work in the
+`modulify/m3-web` repository, with a focus on repository conventions,
+test execution, and standard local commands.
## Repository Structure
- This project is a Yarn Workspaces monorepo.
@@ -38,13 +32,27 @@ workflow.
- Vitest workspace targets are declared in `vitest.workspace.ts`:
`m3-react`, `m3-vue`.
-## Local Environment Prerequisites
+## Architecture Rules
+- Getter/helper functions must be side-effect free. Side effects are allowed
+ only by prior agreement and only when there are strong, explicit reasons.
+- Tests inside a workspace must cover only code owned by that workspace. In
+ `m3-foundation`, `m3-react`, `m3-vue`, and any future workspace, do not add
+ tests for implementation that belongs to another workspace.
+- Cross-workspace imports must go through the target workspace package name as
+ declared in `package.json`. Relative or absolute filesystem imports into
+ another workspace are forbidden. For example,
+ `import type { Appearance } from '@modulify/m3-foundation/types/components/button'`
+ is allowed, but
+ `import type { Appearance } from '../../../m3-foundation/types/components/button'`
+ is not.
+
+## Local setup
- Yarn version is `4.6.0` (see `packageManager` in `package.json`).
-- Local `.yarnrc.yml` is generated from `.yarnrc.yml.dist` using:
+- Generate local `.yarnrc.yml` from `.yarnrc.yml.dist`:
```bash
make .yarnrc.yml
```
-- Install dependencies with:
+- Install dependencies:
```bash
make node_modules
# or
@@ -54,16 +62,6 @@ yarn install
## Running Tests
### Local Path
-- Generate local Yarn config:
-```bash
-make .yarnrc.yml
-```
-- Install dependencies:
-```bash
-make node_modules
-# or
-yarn install
-```
- Run all tests:
```bash
make test
@@ -104,44 +102,47 @@ make help
```
## Important Project Rules
-- Commit messages follow Conventional Commits.
-- Commitlint configuration is in `.commitlintrc.json` with:
- `header-max-length=200`, `body-max-line-length=200`,
- `footer-max-line-length=200`, `subject-case=never`.
-- Getter/helper functions must be side-effect free. Side effects are allowed
- only by prior agreement and only when there are strong, explicit reasons.
+- Before performing actions, analyze whether there is a suitable local skill
+ for the task and consult it for detailed instructions.
+- Before performing repeated or operational actions, inspect `make help` and
+ its output to see whether an existing recipe already covers the task.
+- If a suitable recipe exists, prefer it over ad hoc commands to reduce extra
+ work, keep workflows standardized, and avoid unnecessary escalations.
+- The project includes a Playwright container and make recipes for screenshot
+ capture; use them when visual analysis of Storybook pages, component states,
+ or other UI behavior is helpful.
+- The project also includes runtime-analysis research recipes for DOM, styles,
+ layout metrics, a11y snapshots, traces, network/performance logs, token
+ diffs, and screenshot matrices; use them to reduce uncertainty and to
+ understand what is going wrong before guessing at visual or runtime issues.
+ Read `docs/en/runtime-analysis-recipes.md` first when the task involves
+ visual regressions, layout ambiguity, token/theme uncertainty, unclear
+ animation behavior, or other runtime issues where these recipes may help.
+- Run eslint before handoff or commit preparation only when changed files
+ include code covered by eslint rules (for example `*.js`, `*.ts`, and
+ similar source files). Do not run eslint for markdown-only changes.
+- Prefer running eslint with `--fix` when available so autofixable issues are
+ resolved automatically before manual follow-up.
+
+## Skills
+The skills listed below are stored locally in this repository under `skills/`.
+
+If the context was compacted and you see `Context compacted`, reread any skill
+whose description below starts with `[reread]` after the colon before
+continuing.
-## Commit Workflow
-- Default commit message language is English (unless explicitly requested
- otherwise).
-- Commit style is Conventional Commits.
-- Write commit subjects as historical facts (not intentions).
-- Start commit subject description with an uppercase letter.
-- Keep commit subject description concise.
-- Move long details to commit body; lists in body are allowed for enumerations.
-- Use past/perfective wording; prefer passive voice for changelog-friendly phrasing.
-Examples: `Added ...`, `Removed ...`, `Refactored ...`, `Fixed ...`.
-- Respect commitlint limits from `.commitlintrc.json`:
- `header-max-length=200`, `body-max-line-length=200`,
- `footer-max-line-length=200`.
-- For workspace commits, use scope equal to the workspace directory name:
- `m3-foundation`, `m3-react`, `m3-vue`.
-- Split commits by logical change. Workspace-local changes should stay in
- their workspace scope.
-- Changes in `yarn.lock` must always be committed separately from all other files.
-- Commit message for `yarn.lock`-only commit must be exactly:
-`chore: Updated yarn.lock`.
-- Exception for intentional dependency updates:
-if commit purpose is dependency update (`yarn up`, `yarn add`, `yarn remove`, etc.),
-after rebase conflict resolution rerun the original dependency command and recreate separate
-`chore: Updated yarn.lock` commit.
-- Exception: global workspace-level changes can be combined in one commit.
-Global examples: eslint rule updates, shared dependency updates, repository-level infra/config changes.
-- For commit tasks, use the local skill:
-`skills/commit-workflow/SKILL.md`.
-- For `yarn.lock` merge/rebase conflict resolution, use the local skill:
-`skills/yarn-lock-conflict-resolution/SKILL.md`.
-- For coverage deficit analysis and recovery strategy, use the local skill:
-`skills/coverage-recovery/SKILL.md`.
-- For documentation creation or edits under `docs/` with locale parity, use the local skill:
-`skills/docs-parity/SKILL.md`.
+- `commit-workflow`: [reread] Use when creating or splitting git commits in
+ this repository. Reread it before every commit creation; it standardizes
+ commit grouping, Conventional Commits, workspace scopes, and commitlint
+ limits.
+- `coverage-recovery`: Use when coverage is below target or uncovered code must
+ be analyzed and closed without adding artificial tests.
+- `docs-parity`: Use when creating or editing files under `docs/`. Keeps
+ English-first edits, locale parity, and locale index updates aligned.
+- `exploration-workflow`: [reread] Use only when the user explicitly switches
+ the task into exploration mode: autonomous hypothesis-driven work on
+ uncertain functionality, with timeboxing, milestone logging, and tightly
+ controlled pre-agreed escalation windows, plus `drafts/current.yml` and a
+ dedicated `drafts/` activity directory for logs, facts, and artifact links.
+- `yarn-lock-conflict-resolution`: Use when resolving merge or rebase conflicts
+ in `yarn.lock` according to repository policy.
diff --git a/Makefile b/Makefile
index 824cfbf..1276beb 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,18 @@
+.DEFAULT_GOAL := help
+
TARGET_HEADER=@echo -e '===== \e[34m' $@ '\e[0m'
TARGET_OK=@echo -e '\e[32mOK\e[0m'
YARN=@docker-compose run --rm node yarn
-YARN_PLAYWRIGHT=@docker-compose run --rm playwright yarn
-COVERAGE_PARTS_DIR=coverage/.parts
-COVERAGE_UNIT_DIR=coverage/unit
-COVERAGE_E2E_REACT_DIR=coverage/e2e-react
-COVERAGE_E2E_VUE_DIR=coverage/e2e-vue
-NYC_OUTPUT_DIR=.nyc_output
+PLAYWRIGHT_NODE_CMD=docker-compose run --rm playwright node
+M3_UA?=m3-web-research/1.0
.PHONY: up
-up: ## Starts storybook
+up: ## [Dev][docker] Starts storybook
$(TARGET_HEADER)
docker-compose up -d
.PHONY: restart
-restart: ## Restarts all docker services or a particular service, if argument "service" is specified (example: make restart service="storybook").
+restart: ## [Dev][docker] Restarts all docker services or a particular service, if argument "service" is specified (example: make restart service="storybook")
$(TARGET_HEADER)
ifdef service
@@ -24,213 +22,27 @@ else
endif
.PHONY: stop
-stop: ## Stops all docker services
+stop: ## [Dev][docker] Stops all docker services
$(TARGET_HEADER)
docker-compose stop
-.PHONY: .yarnrc.yml
-.yarnrc.yml: ## Creates yarn configuration
- @cp .yarnrc.yml.dist .yarnrc.yml
-
-.PHONY: node_modules
-node_modules: package.json yarn.lock ## Installs dependencies
- $(TARGET_HEADER)
- $(YARN) install
- @touch node_modules || true
- @echo ""
+include recipes/common.mk
.PHONY: build
-build: node_modules ## Creates a dist catalogue with library build
+build: node_modules ## [Build][docker] Creates a dist catalogue with library build
$(TARGET_HEADER)
$(YARN) build
-.PHONY: storybook-build-test
-storybook-build-test: node_modules ## Builds Storybook in --test mode for all UI workspaces
- $(TARGET_HEADER)
- $(YARN) workspace @modulify/m3-react storybook:build --test --quiet
- $(YARN) workspace @modulify/m3-vue storybook:build --test --quiet
-
-.PHONY: storybook-build-test-react
-storybook-build-test-react: node_modules ## Builds Storybook in --test mode for @modulify/m3-react
- $(TARGET_HEADER)
- $(YARN) workspace @modulify/m3-react storybook:build --test --quiet
-
-.PHONY: storybook-build-test-vue
-storybook-build-test-vue: node_modules ## Builds Storybook in --test mode for @modulify/m3-vue
- $(TARGET_HEADER)
- $(YARN) workspace @modulify/m3-vue storybook:build --test --quiet
-
-.PHONY: test-smoke
-test-smoke: node_modules ## Runs smoke tests for all UI workspaces
- $(TARGET_HEADER)
- $(YARN) test:smoke
-
-.PHONY: test-runtime-parity
-test-runtime-parity: ## Checks Node/Yarn parity across docker services (node, storybook, playwright)
- $(TARGET_HEADER)
- ./runtime-parity.test.sh
-
-.PHONY: husky
-husky: node_modules ## Adds husky git hooks with commit content checks
- @docker-compose run --rm node npx husky init
-
-.PHONY: eslint
-eslint: node_modules ## Runs eslint
- $(TARGET_HEADER)
- $(YARN) eslint
-
-.PHONY: tsc
-tsc: node_modules ## Runs type checks in all workspaces
- $(TARGET_HEADER)
- $(YARN) tsc
-
-.PHONY: tsc-foundation
-tsc-foundation: node_modules ## Runs type checks in @modulify/m3-foundation
- $(TARGET_HEADER)
- $(YARN) workspace @modulify/m3-foundation tsc
-
-.PHONY: tsc-react
-tsc-react: node_modules ## Runs type checks in @modulify/m3-react
- $(TARGET_HEADER)
- $(YARN) workspace @modulify/m3-react tsc
-
-.PHONY: tsc-vue
-tsc-vue: node_modules ## Runs type checks in @modulify/m3-vue
- $(TARGET_HEADER)
- $(YARN) workspace @modulify/m3-vue tsc
-
-.PHONY: tsc-tests
-tsc-tests: node_modules ## Runs type checks for tests in all UI workspaces
- $(TARGET_HEADER)
- $(YARN) tsc:tests
-
-.PHONY: tsc-tests-react
-tsc-tests-react: node_modules ## Runs type checks for tests in @modulify/m3-react
- $(TARGET_HEADER)
- $(YARN) workspace @modulify/m3-react tsc:tests
-
-.PHONY: tsc-tests-vue
-tsc-tests-vue: node_modules ## Runs type checks for tests in @modulify/m3-vue
- $(TARGET_HEADER)
- $(YARN) workspace @modulify/m3-vue tsc:tests
-
-.PHONY: tsc-e2e
-tsc-e2e: node_modules ## Runs type checks for Playwright Vitest configs
- $(TARGET_HEADER)
- $(YARN) exec tsc -p tsconfig.e2e.json --skipLibCheck
-
-.PHONY: test
-test: node_modules ## Runs autotests
- $(TARGET_HEADER)
-
-ifdef cli
- @echo "${YARN} test ${cli}"
- $(YARN) test $(cli)
-else
- @echo "${YARN} test"
- $(YARN) test
-endif
-
-.PHONY: test-coverage
-test-coverage: node_modules ## Runs merged coverage for unit and Playwright e2e tests
- $(TARGET_HEADER)
- @rm -rf coverage $(NYC_OUTPUT_DIR) artifacts
- @mkdir -p $(COVERAGE_PARTS_DIR) $(NYC_OUTPUT_DIR)
- $(YARN) test --coverage --coverage.provider=istanbul --coverage.reporter=json --coverage.reportsDirectory=$(COVERAGE_UNIT_DIR)
- $(YARN_PLAYWRIGHT) test:e2e:coverage
- @cp $(COVERAGE_UNIT_DIR)/coverage-final.json $(COVERAGE_PARTS_DIR)/unit.json
- @cp $(COVERAGE_E2E_REACT_DIR)/coverage-final.json $(COVERAGE_PARTS_DIR)/e2e-react.json
- @cp $(COVERAGE_E2E_VUE_DIR)/coverage-final.json $(COVERAGE_PARTS_DIR)/e2e-vue.json
- @$(YARN) nyc merge $(COVERAGE_PARTS_DIR) $(NYC_OUTPUT_DIR)/coverage-final.json >/dev/null
- $(YARN) nyc report
- @$(YARN) nyc report --reporter=json-summary >/dev/null
- @$(YARN) node --experimental-strip-types scripts/show-total-coverage.ts
-
-.PHONY: test-e2e
-test-e2e: node_modules ## Runs Playwright-based e2e tests (Vitest browser mode)
- $(TARGET_HEADER)
-
-ifdef cli
- @$(YARN_PLAYWRIGHT) test:e2e $(cli)
-else
- @$(YARN_PLAYWRIGHT) test:e2e
-endif
-
-.PHONY: test-e2e-react
-test-e2e-react: node_modules ## Runs Playwright-based e2e tests for @modulify/m3-react
- $(TARGET_HEADER)
-
-ifdef cli
- @$(YARN_PLAYWRIGHT) workspace @modulify/m3-react test:e2e $(cli)
-else
- @$(YARN_PLAYWRIGHT) workspace @modulify/m3-react test:e2e
-endif
-
-.PHONY: test-e2e-vue
-test-e2e-vue: node_modules ## Runs Playwright-based e2e tests for @modulify/m3-vue
- $(TARGET_HEADER)
-
-ifdef cli
- @$(YARN_PLAYWRIGHT) workspace @modulify/m3-vue test:e2e $(cli)
-else
- @$(YARN_PLAYWRIGHT) workspace @modulify/m3-vue test:e2e
-endif
-
-.PHONY: test-e2e-stop
-test-e2e-stop: ## Stops stuck Playwright E2E host processes and run containers
- $(TARGET_HEADER)
- @pkill -TERM -f "docker-compose run --rm playwright yarn test:[e]2e" || true
- @pkill -TERM -f "docker-compose run --rm playwright yarn workspace @modulify/m3-react test:[e]2e" || true
- @pkill -TERM -f "docker-compose run --rm playwright yarn workspace @modulify/m3-vue test:[e]2e" || true
- @docker ps -q --filter "name=m3-web-playwright-run" | xargs -r docker rm -f
-
-.PHONY: ci-actionlint
-ci-actionlint: ## Lints GitHub Actions workflows locally (actionlint binary or docker image)
- $(TARGET_HEADER)
- @if command -v actionlint >/dev/null 2>&1; then \
- actionlint; \
- elif command -v docker >/dev/null 2>&1; then \
- docker run --rm -v "$$(pwd):/repo" -w /repo rhysd/actionlint:latest; \
- else \
- echo "actionlint is not installed and docker is unavailable"; \
- exit 1; \
- fi
- $(TARGET_OK)
-
-.PHONY: ci-act-plan
-ci-act-plan: ## Shows act execution plan for tests workflow without running jobs
- $(TARGET_HEADER)
- @if command -v act >/dev/null 2>&1; then \
- act -P ubuntu-latest=catthehacker/ubuntu:act-latest -n pull_request -W .github/workflows/tests.yml; \
- elif command -v docker >/dev/null 2>&1; then \
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "$$(pwd):/repo" -w /repo alpine:3.20 sh -lc "apk add --no-cache curl tar >/dev/null && curl -fsSL https://github.com/nektos/act/releases/download/v0.2.84/act_Linux_x86_64.tar.gz | tar -xz -C /tmp && /tmp/act -P ubuntu-latest=catthehacker/ubuntu:act-latest -n pull_request -W .github/workflows/tests.yml"; \
- else \
- echo "act is not installed and docker is unavailable"; \
- exit 1; \
- fi
- $(TARGET_OK)
-
-.PHONY: ci-act-tests
-ci-act-tests: ## Runs tests workflow locally via act (pr-check, eslint, tests, storybook-tests)
- $(TARGET_HEADER)
- @if command -v act >/dev/null 2>&1; then \
- act -P ubuntu-latest=catthehacker/ubuntu:act-latest pull_request -W .github/workflows/tests.yml -j pr-check -j eslint -j tests -j storybook-tests; \
- elif command -v docker >/dev/null 2>&1; then \
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "$$(pwd):/repo" -w /repo alpine:3.20 sh -lc "apk add --no-cache curl tar >/dev/null && curl -fsSL https://github.com/nektos/act/releases/download/v0.2.84/act_Linux_x86_64.tar.gz | tar -xz -C /tmp && /tmp/act -P ubuntu-latest=catthehacker/ubuntu:act-latest pull_request -W .github/workflows/tests.yml -j pr-check -j eslint -j tests -j storybook-tests"; \
- else \
- echo "act is not installed and docker is unavailable"; \
- exit 1; \
- fi
- $(TARGET_OK)
-
-.PHONY: ci-check
-ci-check: ci-actionlint ci-act-plan ## Validates CI workflow config and prints local act plan
- $(TARGET_OK)
+include recipes/lint.mk
+include recipes/tests.mk
+include recipes/storybook.mk
+include recipes/ci.mk
+include recipes/research.mk
+include recipes/exploration.mk
.PHONY: help
-help: ## Calls recipes list
- @cat $(MAKEFILE_LIST) | grep -e "^[-a-zA-Z_\.]*: *.*## *" | awk '\
- BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+help: ## [General] Shows grouped command help
+ @HELP_FILTER="$(value filter)" HELP_SHOW_INTERNAL="$(value show_internal)" ./recipes/help.sh $(MAKEFILE_LIST)
# Colors
$(call computable,CC_BLACK,$(shell tput -Txterm setaf 0 2>/dev/null))
diff --git a/docs/en/index.md b/docs/en/index.md
index 3d3428c..012396c 100644
--- a/docs/en/index.md
+++ b/docs/en/index.md
@@ -3,6 +3,7 @@
## Contents
- [Contributing Guide](./contributing.md) - Development workflow, core commands, and CI quality gates.
+- [Runtime Analysis Recipes](./runtime-analysis-recipes.md) - Playwright-based recipes for visual, layout, style, motion, and runtime investigation.
- [TSConfig Layering](./tsconfig-layering.md) - How TypeScript configs are split between editor DX and CI checks.
## Translations
diff --git a/docs/en/runtime-analysis-recipes.md b/docs/en/runtime-analysis-recipes.md
new file mode 100644
index 0000000..7bed121
--- /dev/null
+++ b/docs/en/runtime-analysis-recipes.md
@@ -0,0 +1,103 @@
+# Runtime Analysis Recipes
+
+This document describes the Playwright-based research recipes available in
+`m3-web` for reducing uncertainty when visual or runtime behavior is unclear.
+
+## When To Use
+
+Use these recipes when:
+- a Storybook page looks wrong and screenshots are not enough;
+- a layout shifts, overlaps, or animates unexpectedly;
+- local theming or CSS tokens appear to be ignored;
+- React and Vue parity is unclear;
+- visual regressions need before/after or matrix comparison;
+- runtime errors may be hidden in console, network, or accessibility state.
+
+## Core Principle
+
+Prefer these recipes before guessing. They are intended to turn “something is
+off” into concrete evidence: screenshots, computed styles, layout metrics,
+accessibility trees, traces, and diffs.
+
+## Most Useful Recipes
+
+- `make research-capture url='...'` - Single screenshot with metadata.
+- `make research-capture-batch in=urls.txt out_dir=drafts/screenshots/...` -
+ Batch screenshot capture from a URL list.
+- `make research-style-dump url='...' selector='...'` - Computed styles and CSS
+ custom properties for one element.
+- `make research-layout-metrics url='...' selectors='.a||.b||.c'` - Bounding
+ boxes, scroll metrics, offsets, and layout details for multiple elements.
+- `make research-token-diff url='...' selector='.left' compare_selector='.right'`
+ - CSS variable diff between two scopes.
+- `make research-console-capture url='...'` - Console messages, page errors,
+ and failed requests.
+- `make research-a11y-snapshot url='...' selector='...'` - Accessibility tree
+ for the page or a subtree.
+- `make research-trace url='...' action_selector='...' interaction=click` -
+ Playwright trace for a page and optional interaction.
+- `make research-motion-sample url='...' action_selector='...' interaction=click`
+ - Timed frame sequence for motion and transitions.
+- `make research-capture-diff left=before.png right=after.png out=diff.png` -
+ Pixel diff for two images or directories.
+- `make research-capture-matrix ...` - Capture one URL across multiple themes,
+ globals, args, and viewports.
+- `make research-story-props story_id='...' themes='light,dark' args_sets='...'`
+ - Storybook-oriented matrix capture for story args and globals.
+
+## Typical Workflows
+
+## Investigate A Broken Layout
+
+1. Capture the current page:
+ `make research-capture url='...'`
+2. Dump layout metrics:
+ `make research-layout-metrics url='...' selectors='.host||.panel||.scrim'`
+3. Dump computed styles for the suspicious node:
+ `make research-style-dump url='...' selector='.panel'`
+
+## Investigate A Theme Or Token Problem
+
+1. Capture the page in both themes:
+ `make research-capture-matrix story_id='...' themes='light,dark'`
+2. Compare token scopes:
+ `make research-token-diff url='...' selector='.default-scope' compare_selector='.local-scope'`
+3. Inspect computed variables:
+ `make research-style-dump url='...' selector='.local-scope' var_prefixes='--m3-sys-,--m3-state-layers-'`
+
+## Investigate Animation Or Motion
+
+1. Capture a motion sequence:
+ `make research-motion-sample url='...' action_selector='...' interaction=click`
+2. If behavior is still unclear, capture a trace:
+ `make research-trace url='...' action_selector='...' interaction=click`
+
+## Compare Two Runtime States
+
+1. Capture both states into separate directories.
+2. Run:
+ `make research-capture-diff left=dir-a right=dir-b out_dir=drafts/research/diff`
+
+## Storybook Notes
+
+- Storybook pages can be opened directly with:
+ `http://m3-vue.modulify.test/?path=/story/...&globals=theme:dark`
+- For systematic capture across states, prefer `research-story-props` and
+ `research-capture-matrix` over manually building many URLs.
+
+## Output Conventions
+
+- Screenshots are written under `drafts/screenshots/...` unless overridden.
+- Runtime inspection outputs default to `drafts/research/...`.
+- Most recipes emit machine-readable JSON so results can be inspected or
+ compared later.
+
+## Recommendation
+
+When a bug report is vague, start with:
+1. `research-capture`
+2. `research-style-dump`
+3. `research-layout-metrics`
+
+That combination usually clarifies whether the problem is geometry, styling,
+token inheritance, or runtime state.
diff --git a/docs/ru/index.md b/docs/ru/index.md
index 6a36c7c..e5f7526 100644
--- a/docs/ru/index.md
+++ b/docs/ru/index.md
@@ -3,4 +3,5 @@
## Содержание
- [Руководство по участию](./contributing.md) - Процесс разработки, ключевые команды и проверки в CI.
+- [Рецепты runtime-анализа](./runtime-analysis-recipes.md) - Playwright-рецепты для исследования visual, layout, style, motion и runtime-проблем.
- [Слои TSConfig](./tsconfig-layering.md) - Разделение TypeScript-конфигов для IDE и package-level typecheck.
diff --git a/docs/ru/runtime-analysis-recipes.md b/docs/ru/runtime-analysis-recipes.md
new file mode 100644
index 0000000..32cd720
--- /dev/null
+++ b/docs/ru/runtime-analysis-recipes.md
@@ -0,0 +1,105 @@
+# Рецепты runtime-анализа
+
+Этот документ описывает Playwright-рецепты исследования, доступные в
+`m3-web`, чтобы снимать неопределённость, когда визуальное или runtime-поведение
+непонятно.
+
+## Когда применять
+
+Используйте эти рецепты, когда:
+- Storybook-страница выглядит неправильно, и одного скриншота недостаточно;
+- layout сдвигается, наползает или анимируется неожиданно;
+- локальная тема или CSS-токены как будто игнорируются;
+- неясно, есть ли parity между React и Vue;
+- нужно сравнить состояния до/после или матрицу сценариев;
+- runtime-ошибки могут прятаться в console, network или accessibility-состоянии.
+
+## Базовый принцип
+
+Предпочитайте эти рецепты угадыванию. Их задача — превращать расплывчатое
+«что-то не так» в конкретные артефакты: скриншоты, computed styles, layout
+метрики, accessibility tree, traces и diffs.
+
+## Самые полезные рецепты
+
+- `make research-capture url='...'` - Один скриншот с метаданными.
+- `make research-capture-batch in=urls.txt out_dir=drafts/screenshots/...` -
+ Пакетное снятие скриншотов по списку URL.
+- `make research-style-dump url='...' selector='...'` - Computed styles и CSS
+ custom properties для одного элемента.
+- `make research-layout-metrics url='...' selectors='.a||.b||.c'` - Bounding
+ boxes, scroll-метрики, offsets и layout-детали для нескольких элементов.
+- `make research-token-diff url='...' selector='.left' compare_selector='.right'`
+ - Diff CSS-переменных между двумя scope.
+- `make research-console-capture url='...'` - Console messages, page errors и
+ failed requests.
+- `make research-a11y-snapshot url='...' selector='...'` - Accessibility tree
+ для страницы или subtree.
+- `make research-trace url='...' action_selector='...' interaction=click` -
+ Playwright trace для страницы и опционального взаимодействия.
+- `make research-motion-sample url='...' action_selector='...' interaction=click`
+ - Последовательность кадров для motion и transitions.
+- `make research-capture-diff left=before.png right=after.png out=diff.png` -
+ Pixel diff для двух изображений или каталогов.
+- `make research-capture-matrix ...` - Съёмка одного URL по нескольким themes,
+ globals, args и viewports.
+- `make research-story-props story_id='...' themes='light,dark' args_sets='...'`
+ - Storybook-oriented matrix capture для story args и globals.
+
+## Типовые сценарии
+
+## Разобрать сломанный layout
+
+1. Снять текущую страницу:
+ `make research-capture url='...'`
+2. Снять layout metrics:
+ `make research-layout-metrics url='...' selectors='.host||.panel||.scrim'`
+3. Снять computed styles для подозрительного узла:
+ `make research-style-dump url='...' selector='.panel'`
+
+## Разобрать проблему темы или токенов
+
+1. Снять страницу в обеих темах:
+ `make research-capture-matrix story_id='...' themes='light,dark'`
+2. Сравнить token scope:
+ `make research-token-diff url='...' selector='.default-scope' compare_selector='.local-scope'`
+3. Посмотреть computed variables:
+ `make research-style-dump url='...' selector='.local-scope' var_prefixes='--m3-sys-,--m3-state-layers-'`
+
+## Разобрать анимацию или motion
+
+1. Снять последовательность кадров:
+ `make research-motion-sample url='...' action_selector='...' interaction=click`
+2. Если поведение всё ещё неясно, снять trace:
+ `make research-trace url='...' action_selector='...' interaction=click`
+
+## Сравнить два runtime-состояния
+
+1. Снять оба состояния в разные каталоги.
+2. Запустить:
+ `make research-capture-diff left=dir-a right=dir-b out_dir=drafts/research/diff`
+
+## Примечания по Storybook
+
+- Storybook-страницы можно открывать напрямую так:
+ `http://m3-vue.modulify.test/?path=/story/...&globals=theme:dark`
+- Для систематической съёмки по состояниям лучше использовать
+ `research-story-props` и `research-capture-matrix`, а не собирать множество
+ URL вручную.
+
+## Формат артефактов
+
+- Скриншоты по умолчанию пишутся в `drafts/screenshots/...`.
+- Runtime inspection outputs по умолчанию пишутся в `drafts/research/...`.
+- Большинство рецептов сохраняет machine-readable JSON, чтобы результаты можно
+ было потом изучать и сравнивать.
+
+## Рекомендация
+
+Когда баг-репорт расплывчатый, начинайте с:
+1. `research-capture`
+2. `research-style-dump`
+3. `research-layout-metrics`
+
+Эта комбинация обычно быстро показывает, проблема в геометрии, styling,
+наследовании токенов или runtime-состоянии.
diff --git a/evidence/popper.md b/evidence/popper.md
new file mode 100644
index 0000000..b0468ac
--- /dev/null
+++ b/evidence/popper.md
@@ -0,0 +1,70 @@
+# Popper Animation Evidence
+Date: 2026-02-22
+
+## Sources
+### Primary references
+- User-provided design reference video:
+ - https://firebasestorage.googleapis.com/v0/b/design-spec/o/projects%2Fgoogle-material-3%2Fimages%2Fmhlk1jwz-GM3_Menus_Guidelines%2001_IA_v01.mp4?alt=media&token=2e7dfcc9-2447-4808-8234-83c313d82df8
+- Material 3 motion pages:
+ - https://m3.material.io/styles/motion/overview
+ - https://m3.material.io/foundations/motion/applying-easing-and-duration
+ - https://m3.material.io/styles/motion/easing-and-duration/tokens-specs
+
+### Token/easing references used as practical source of truth
+- Flutter generated motion tokens:
+ - https://flutter.googlesource.com/mirrors/packages/+/refs/tags/google_maps_flutter-v2.7.0/packages/flutter/lib/src/material/motion.dart
+- Flutter API docs for durations and easing:
+ - https://api.flutter.dev/flutter/material/Durations-class.html
+ - https://api.flutter.dev/flutter/material/Easing/standard-constant.html
+ - https://api.flutter.dev/flutter/material/Easing/standardAccelerate-constant.html
+ - https://api.flutter.dev/flutter/material/Easing/standardDecelerate-constant.html
+ - https://api.flutter.dev/flutter/material/Easing/emphasizedAccelerate-constant.html
+ - https://api.flutter.dev/flutter/material/Easing/emphasizedDecelerate-constant.html
+
+### Internal empirical source
+- Implementation and local validation log in `draft.txt`:
+ - wrapper split (`.m3-popper-positioner` + `.m3-popper`)
+ - updated E2E assertions
+ - local check commands and outcomes
+
+## Extracted Facts
+### Direct facts
+1. Positioning and animation transforms conflict if both are applied to the same element.
+2. Splitting responsibilities into two elements is technically viable:
+ - outer element handles geometry (`absolute/top/left/transform` from floating-ui),
+ - inner element handles visual animation (`transform/opacity/visibility`).
+3. Show ordering matters: first position (`await adjust`), then reveal content.
+4. Animation direction must use the effective side after flip (actual placement), not requested placement.
+5. Headless access to `m3.material.io` pages may be limited (JS-required shell), so token tables were taken from official generated/tokenized sources.
+6. Local verification was reported as green after stabilization:
+ - `tsc` for foundation/react/vue,
+ - `eslint`,
+ - unit and e2e checks for popper,
+ - combined coverage run (`80.11%`).
+
+### Motion token facts (from token references)
+1. Duration token scale:
+ - short: 50/100/150/200 ms,
+ - medium: 250/300/350/400 ms,
+ - long: 450/500/550/600 ms,
+ - extra long: 700/800/900/1000 ms.
+2. Easing token curves:
+ - standard: `cubic-bezier(0.2, 0.0, 0.0, 1.0)`,
+ - standardAccelerate: `cubic-bezier(0.3, 0.0, 1.0, 1.0)`,
+ - standardDecelerate: `cubic-bezier(0.0, 0.0, 0.0, 1.0)`,
+ - emphasizedAccelerate: `cubic-bezier(0.3, 0.0, 0.8, 0.15)`,
+ - emphasizedDecelerate: `cubic-bezier(0.05, 0.7, 0.1, 1.0)`,
+ - linear: `cubic-bezier(0.0, 0.0, 1.0, 1.0)`.
+
+### Inferences used for implementation tuning
+1. Enter animation should prefer decelerate-family easing, exit should prefer accelerate-family easing.
+2. Perceived "menu unfolding from anchor point" depends on:
+ - side-aware `transform-origin`,
+ - axis-dominant scale (uncollapse) with small translation offset.
+3. Candidate presets for iterative tuning:
+ - `short3/short1` or `short4/short2`,
+ - decelerate for enter + accelerate for exit.
+
+## Notes for future work
+- Keep parity of popper behavior checks in React and Vue E2E.
+- Freeze chosen duration/easing pair in tests via computed-style assertions to prevent regressions.
diff --git a/evidence/surface/fetch/bottom-sheets.json b/evidence/surface/fetch/bottom-sheets.json
new file mode 100644
index 0000000..3f8310e
--- /dev/null
+++ b/evidence/surface/fetch/bottom-sheets.json
@@ -0,0 +1 @@
+{"category": "containment", "component_image_1x1": {}, "component_image_2x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/341c3402df784b026de4b8b6fe23f3eeddcfc45e753bc872f59861c10edde81bfb0410c8b1838a86255f0f66b47581ff3621044502dee57db2fb365ca71021da", "height": 560, "url": "https://lh3.googleusercontent.com/UIUrt9wQoicIdyQ4IjklJLL6-vIR-GmyYF4ErZOfLrFLREEII6mOVuE15ImA8sq6VvneWK0KQvVOFh8nR_39hUdwN2YJMT7OX9Bivo7aPBBPj0zakQQ", "width": 1120}, "component_image_3x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/c2b9b97f5b43d4d8edfb0efa2cae3a4ede8e4e442ea0c5231f5309e37613166abbfb485a7f96a16f06bdd58a0abeebaa7a24aed225d635193aaa2e07fb0de0f1", "height": 560, "url": "https://lh3.googleusercontent.com/2YiNco_ni2-lSwWlJuYpcp0SKqQQzz1FtoDMeqvd5rjvkTenfvcYm3ccSqJh2oowZ_bXvcUnrMvRzLXrR4HT-3iwlA25df97JZBiIAeOfZEoSHeldA", "width": 1680}, "component_image_3x2": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/14d3cfe953f0b131da2966f35d9437ba2065e16f2d584c8da4192c45a2ba1937052ca085c386576e924d0cdb2c454593054b9742c644f4e57a541c1e5eed5a04", "height": 560, "url": "https://lh3.googleusercontent.com/vs0KnopDGwWY_DYPbdNdmkyYL_N4Cf3eOG7StgUi-LFVsgXqBJ0k4Jlgw25QLpCuxUFPk8Rwt3ijxuBgIohNjVQsEAIqvOB7EzObKxSQzbRyNgGVEYXG", "width": 840}, "component_image_position": "top center", "dark_component_image_1x1": {}, "dark_component_image_2x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/6442b37617d160c37751d4301b1265a781d2c786aed628fa890892d2e191038e5b987ff74363ccd261904eb0d686dbabd2e356107badfeaf57b6e13b629e0697", "height": 560, "url": "https://lh3.googleusercontent.com/aOb9-hvKVti3KeEn-NfoD-f1IonRWbrz-A26gJ4SSuakHtKHC2Vw_uh1w3E_1JzMAx4CQ0tjpi2Zvq4yzQ60whrjOAzyIq2CcjAzMgx5VcbGpNb1h4cA", "width": 1120}, "dark_component_image_3x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/2e5a55f30d6a8841040c2e2d947f4039c5713cb5144318a02d5a1ef5a2d9b4fafb5f3f89af22ad89876c5f2fdbc5be393b23f2457e0829d2ed877d61301bec50", "height": 560, "url": "https://lh3.googleusercontent.com/2eharb9N0r0D_CQm81EpHY7FtjRm1hfksH7QVW09EsH2Wx6OwToQyFaWpUmqqGqtk3r30XIf42o1GeDOwjmkZtLPivrY0LyV0q2avatZVgKk8U0Gyw", "width": 1680}, "dark_component_image_3x2": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/31f1e3efd779bd9bd956ab312e0107409d00de80ef55beb7a4fcc5e1588da9b88e3d4a5281002ff43de1ee61169a9538a9544015a620d9a2445c2f072c66e094", "height": 560, "url": "https://lh3.googleusercontent.com/3MqGJTtT0PiPqw6-AG2hxNyEw3EN7kfRuPQBbq4jAd5tU2lDf2eBPFzXyEmoe9iPQH1RfQaoql6df_4tuSKQli87ijf1aWl8JqoyarmV1FM36g33Gg", "width": 840}, "description": "Bottom sheets are surfaces containing supplementary content, anchored to the bottom of the screen.", "hero": {"background_asset": {}, "background_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/c9b614e2c6394e633361aedf8dfabee7018abc81fed5e9c2d2b830cfb91cdde77617375d80608ab19e9deaae6e65763ecf7ee7e7f3be1c5cb05eb08a89e09fe0", "height": 4096, "url": "https://lh3.googleusercontent.com/9gfaUBF_jWA_0uIpfK8gwcKthghUalMNBXP1_ILmkMAF8uf8q8xV3FPeiK2qJYfUyoUHf0zrQooP-olwdgm3nEfSIah6Do202yLoBMuFyl4nao29ao8", "width": 4096}, "foreground_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/533e014f36dc2451d394c03188633b3e805049119e1fdac9903be32e3e3163588ccfb610d365c2e1ada576174426cea0101031cbad8245a346d8fc1a88d4fff6", "height": 1816, "url": "https://lh3.googleusercontent.com/xG7HTNDPwS9Dj37Bw-sK9tqCB7RKZkDDYhw6oYHKxmEio1ZdY7ilg1TGGLteoAI14_qj3ZCMj4gbXa8NebMrGyYRKRcQ-5wqcMCdw6BeuTkI_P2gvOU", "width": 856}, "foreground_image_alignment": "center", "foreground_image_svg": null, "foreground_text_light": false}, "ia_title": "Components / Bottom Sheets", "isDisplayed": true, "jetpack": {}, "mdc_android": {"banners": [], "hide_generic_not_available_message": false, "hide_intro_block": true, "image": [{"a11y_description": "Example bottom sheet: modal bottom sheet", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/14fa0f73686498345e6b3d2c162cc038612f0ce475969b6223543977af215f0dd3a5d4473a594d5c4cb7cfb8f79f7fdb5f97f9159c372a2853111143861d1685", "height": 532, "url": "https://lh3.googleusercontent.com/Um-uopzOwBakYytrS-_tys-4Jhe_bwbY-rVuzx9swVBZMcizNX_w985A8GdRQ-FwbEW45mMfDgkiuMMsqi3JI9fqa6FHb0oSlvM4cwFEog1sSlicsYr-", "width": 1064}}, {"a11y_description": "Standard bottom sheet example. Collapsed on the left and expanded on the\nright.", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/b08f62e5cc779362c32c8360c3015ea601124ece798a056ab276b565e117d0deaa774e0c476230b7dd796ce8443cbf34f9265ecc0027944f8cfe742a753139e2", "height": 2160, "url": "https://lh3.googleusercontent.com/B7t0IbDHwFQuL7iRuKhTuTpZo8mblYAHg0q-bCowsAn8fpizoxpWnQfK0E9Zb-g_6DownUBiSOA6NdaeCoVEd_DKdR1P8qbeP9dCQFXy0uz9ngGNeg", "width": 2160}}, {"a11y_description": "Modal bottom sheet example. Collapsed on the left and expanded on the right.", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/f810ef2649599d0c1993697c41e609a37fb93773f5c7c09d52bb10e728e087c2f28c36b2cfe17bb8aa8c59725ba172f463b1723247f42cdac16d2379db1d52e4", "height": 2160, "url": "https://lh3.googleusercontent.com/ajuNBwFkxERvKgQ6Pk4e_ZOq8CPWj5f7aA_uYD6Qq5Tv1bYy1-jLKIhd_TK5EerJ550N0JnoyQ_fZ6_dEXC9g-hAWxe2GGamCxGEnsG1n_YxNVaqwQ", "width": 2160}}, {"a11y_description": "Bottom sheet anatomy", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/28bf359e28ada5a30a7b335b4ba3860c8a323c572cf1aa1b9d567e2caf99b6f39df87bbe15aa14ad6a4a8632ed0ec78b5e1c1051f34868f47e7ecddc1f3f550d", "height": 760, "url": "https://lh3.googleusercontent.com/UQIEJJ9UEOR3t5g9wtTOGoWy6s8QXbsxKYOdt7RJ6bSmINikBLnZ3XbSpIb9RBtQwY811TZCw9Zs57eg85AjERuRB4bE7LR08qZZsJLD21O4qRIjJQaX", "width": 1520}}, {"a11y_description": "Bottom sheet with pink background color. Collapsed on the left and expanded on\nthe right.", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/8f7f2b0ea75fdb81c0988892e73059dd272b71c910b181096c5f963c88e47c780bc83ce2dbb100b550248173a0b64523d8cb29b3ef19c636d117877d24e1a434", "height": 830, "url": "https://lh3.googleusercontent.com/5KpmGZgdsLzmO52VnZGPD5JqniKVgQ1dynaKlS948Igjo0NmW5h1MVQibtW2ZD6-RoSUraRWU1PRlvgie1J1_1Q2A3opYG9gyGsBOzpzwsR4HKJTLZQE", "width": 800}}], "is_beta": false, "markdown_content": "\n\n# Bottom Sheets\n\n[Bottom sheets](https://material.io/components/sheets-bottom) are surfaces\ncontaining supplementary content that are anchored to the bottom of the screen.\n\n\n\n**Contents**\n\n* [Using bottom sheets](#using-bottom-sheets)\n* [Standard bottom sheet](#standard-bottom-sheet)\n* [Modal bottom sheet](#modal-bottom-sheet)\n* [Anatomy and key properties](#anatomy-and-key-properties)\n* [Theming](#theming-bottom-sheets)\n\n## Using bottom sheets\n\nBefore you can use Material bottom sheets, you need to add a dependency to the\nMaterial Components for Android library. For more information, go to the\n[Getting started](/libraries/mdc-android/getting-started)\npage.\n\nStandard bottom sheet basic usage:\n\n```xml\n\n\n \n\n \n\n \n\n\n```\n\nModal bottom sheet basic usage:\n\n```kt\nclass ModalBottomSheet : BottomSheetDialogFragment() {\n\n override fun onCreateView(\n inflater: LayoutInflater,\n container: ViewGroup?,\n savedInstanceState: Bundle?\n ): View? = inflater.inflate(R.layout.modal_bottom_sheet_content, container, false)\n\n companion object {\n const val TAG = \"ModalBottomSheet\"\n }\n}\n\nclass MainActivity : AppCompatActivity() {\n ...\n val modalBottomSheet = ModalBottomSheet()\n modalBottomSheet.show(supportFragmentManager, ModalBottomSheet.TAG)\n ...\n}\n```\n\nMore information on each individual section, below.\n\n### Setting behavior\n\nThere are several attributes that can be used to adjust the behavior of both\nstandard and modal bottom sheets.\n\nBehavior attributes can be applied to standard bottom sheets in xml by setting\nthem on a child `View` set to `app:layout_behavior`, or programmatically:\n\n```kt\nval standardBottomSheetBehavior = BottomSheetBehavior.from(standardBottomSheet)\n// Use this to programmatically apply behavior attributes\n```\n\nBehavior attributes can be applied to modal bottom sheets using app-level theme\nattributes and styles:\n\n```xml\n\n\n\n\n\n```\n\nOr programmatically:\n\n```kt\nval modalBottomSheetBehavior = (modalBottomSheet.dialog as BottomSheetDialog).behavior\n// Use this to programmatically apply behavior attributes\n```\n\nMore information about these attributes and their default values is available in\nthe [behavior attributes](#behavior-attributes) section.\n\n### Retaining behavior on configuration change\n\nIn order to save and restore specific behaviors of the bottom sheet on\nconfiguration change, the following flags can be set (or combined with bitwise\nOR operations):\n\n* `SAVE_PEEK_HEIGHT`: `app:behavior_peekHeight` is preserved.\n* `SAVE_HIDEABLE`: `app:behavior_hideable` is preserved.\n* `SAVE_SKIP_COLLAPSED`: `app:behavior_skipCollapsed` is preserved.\n* `SAVE_FIT_TO_CONTENTS`: `app:behavior_fitToContents` is preserved.\n* `SAVE_ALL`: All aforementioned attributes are preserved.\n* `SAVE_NONE`: No attribute is preserved. This is the default value.\n\nBehaviors can also be set in code:\n\n```kt\nbottomSheetBehavior.saveFlags = BottomSheetBehavior.SAVE_ALL\n```\n\nOr in xml using the `app:behavior_saveFlags` attribute.\n\n### Setting state\n\nStandard and modal bottom sheets have the following states:\n\n* `STATE_COLLAPSED`: The bottom sheet is visible but only showing its peek\n height. This state is usually the 'resting position' of a bottom sheet, and\n should have enough height to indicate there is extra content for the user to\n interact with.\n* `STATE_EXPANDED`: The bottom sheet is visible at its maximum height and it\n is neither dragging nor settling (see below).\n* `STATE_HALF_EXPANDED`: The bottom sheet is half-expanded (only applicable if\n `behavior_fitToContents` has been set to false), and is neither dragging nor\n settling (see below).\n* `STATE_HIDDEN`: The bottom sheet is no longer visible and can only be\n re-shown programmatically.\n* `STATE_DRAGGING`: The user is actively dragging the bottom sheet up or down.\n* `STATE_SETTLING`: The bottom sheet is settling to a specific height after a\n drag/swipe gesture. This will be the peek height, expanded height, or 0, in\n case the user action caused the bottom sheet to hide.\n\nYou can set a state on the bottom sheet:\n\n```kt\nbottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED\n```\n\n**Note:** `STATE_SETTLING` and `STATE_DRAGGING` should not be set programmatically.\n\n### Listening to state and slide changes\n\nA `BottomSheetCallback` can be added to a `BottomSheetBehavior`:\n\n```kt\nval bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {\n\n override fun onStateChanged(bottomSheet: View, newState: Int) {\n // Do something for new state.\n }\n\n override fun onSlide(bottomSheet: View, slideOffset: Float) {\n // Do something for slide offset.\n }\n}\n\n// To add the callback:\nbottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback)\n\n// To remove the callback:\nbottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallback)\n```\n\n### Handling insets and fullscreen\n\n`BottomSheetBehavior` can automatically handle insets (such as for\n[edge to edge](https://developer.android.com/training/gestures/edge-to-edge)) by\nspecifying any of these to true on the view:\n\n* `app:paddingBottomSystemWindowInsets`\n* `app:paddingLeftSystemWindowInsets`\n* `app:paddingRightSystemWindowInsets`\n* `app:paddingTopSystemWindowInsets`\n\nOn API 21 and above the modal bottom sheet will be rendered fullscreen (edge to\nedge) if the navigation bar is transparent and `app:enableEdgeToEdge` is true.\nTo enable edge-to-edge by default for modal bottom sheets, you can override\n`?attr/bottomSheetDialogTheme` like the below example:\n\n```\n\n\n\n```\n\nInsets can be added automatically if any of the padding attributes above are set\nto true in the style, either by updating the style passed to the constructor, or\nby updating the default style specified by the `?attr/bottomSheetDialogTheme`\nattribute in your theme.\n\n`BottomSheetDialog` will also add padding to the top when the bottom sheet\nslides under the status bar, to prevent content from being drawn underneath it.\n\n### Making bottom sheets accessible\n\nThe contents within a bottom sheet should follow their own accessibility\nguidelines, such as setting content descriptions for images.\n\nTo support dragging bottom sheets with accessibility services such as TalkBack,\nVoice Access, Switch Access, etc., we provide a convenient widget\n`BottomSheetDragHandleView` which will automatically receive and handle\naccessibility commands to expand and collapse the attached bottom sheet when\nthe accessibility mode is enabled. To use `BottomSheetDragHandleView`, you can\nadd it to the top of your bottom sheet content. It will show a customizable\nvisual indicator for all users. See the example in the below section for how to\nadd a drag handle to your bottom sheet.\n\n**Note:** `BottomSheetDragHandleView` has a default min width and height of 48dp\nto conform to the minimum touch target requirement. So you will need to preserve\nat least 48dp at the top to place a drag handle.\n\n## Standard bottom sheet\n\nStandard bottom sheets co-exist with the screen\u2019s main UI region and allow for\nsimultaneously viewing and interacting with both regions. They are commonly used\nto keep a feature or secondary content visible on screen when content in the\nmain UI region is frequently scrolled or panned.\n\n`BottomSheetBehavior` is applied to a child of\n[CoordinatorLayout](https://developer.android.com/reference/androidx/coordinatorlayout/widget/CoordinatorLayout)\nto make that child a **persistent bottom sheet**, which is a view that comes up\nfrom the bottom of the screen, elevated over the main content. It can be dragged\nvertically to expose more or less content.\n\nAPI and source code:\n\n* `BottomSheetBehavior`\n * [Class definition](https://developer.android.com/reference/com/google/android/material/bottomsheet/BottomSheetBehavior)\n * [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java)\n\n### Standard bottom sheet example\n\nThe following example shows a standard bottom sheet in its collapsed and\nexpanded states:\n\n\n\n`BottomSheetBehavior` works in tandem with `CoordinatorLayout` to let you\ndisplay content on a bottom sheet, perform enter/exit animations, respond to\ndragging/swiping gestures, etc.\n\nApply the `BottomSheetBehavior` to a direct child `View` of `CoordinatorLayout`:\n\n```xml\n\n\n \n\n \n \n\n \n \n\n \n\n \n\n \n\n \n\n\n```\n\nIn this example, the bottom sheet is the `FrameLayout`.\n\n## Modal bottom sheet\n\nModal bottom sheets present a set of choices while blocking interaction with the\nrest of the screen. They are an alternative to inline menus and simple dialogs\non mobile devices, providing additional room for content, iconography, and\nactions.\n\n`BottomSheetDialogFragment` is a thin layer on top of the regular support\nlibrary Fragment that renders your fragment as a **modal bottom sheet**,\nfundamentally acting as a dialog.\n\nModal bottom sheets render a shadow on the content below them, to indicate that\nthey are modal. If the content outside of the dialog is tapped, the bottom sheet\nis dismissed. Modal bottom sheets can be dragged vertically and dismissed by\nsliding them down completely.\n\nAPI and source code:\n\n* `BottomSheetDialogFragment`\n * [Class definition](https://developer.android.com/reference/com/google/android/material/bottomsheet/BottomSheetDialogFragment)\n * [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomsheet/BottomSheetDialogFragment.java)\n\n### Modal bottom sheet example\n\nThe following example shows a modal bottom sheet in its collapsed and expanded\nstates:\n\n\n\nFirst, subclass `BottomSheetDialogFragment` and overwrite `onCreateView` to\nprovide a layout for the contents of the sheet (in this example, it's\n`modal_bottom_sheet_content.xml`):\n\n```kt\nclass ModalBottomSheet : BottomSheetDialogFragment() {\n\n override fun onCreateView(\n inflater: LayoutInflater,\n container: ViewGroup?,\n savedInstanceState: Bundle?\n ): View? = inflater.inflate(R.layout.modal_bottom_sheet_content, container, false)\n\n companion object {\n const val TAG = \"ModalBottomSheet\"\n }\n}\n```\n\nThen, inside an `AppCompatActivity`, to show the bottom sheet:\n\n```kt\nval modalBottomSheet = ModalBottomSheet()\nmodalBottomSheet.show(supportFragmentManager, ModalBottomSheet.TAG)\n```\n\n`BottomSheetDialogFragment` is a subclass of `AppCompatFragment`, which means\nyou need to use `Activity.getSupportFragmentManager()`.\n\n**Note:** Don't call `setOnCancelListener` or `setOnDismissListener` on a\n`BottomSheetDialogFragment`. You can override\n`onCancel(DialogInterface)` or `onDismiss(DialogInterface)` if necessary.\n\n## Anatomy and key properties\n\nBottom sheets have a sheet, content, and, if modal, a scrim.\n\n\n\n1. Sheet\n2. Content\n3. Scrim (in modal bottom sheets)\n\n### Sheet attributes\n\nElement | Attribute | Related method(s) | Default value\n-------------- | --------------------- | --------------------------------- | -------------\n**Color** | `app:backgroundTint` | N/A | `?attr/colorSurface`\n**Shape** | `app:shapeAppearance` | N/A | `?attr/shapeAppearanceLargeComponent`\n**Elevation** | `android:elevation` | N/A | `3dp`\n**Max width** | `android:maxWidth` | `setMaxWidth` `getMaxWidth` | `640dp`\n**Max height** | `android:maxHeight` | `setMaxHeight` `getMaxHeight` | N/A\n\n### Behavior attributes\n\nMore info about these attributes and how to use them in the\n[setting behavior](#setting-behavior) section.\n\nBehavior | Related method(s) | Default value\n------------------------------------------- | ------------------------------------------------------------------------- | -------------\n`app:behavior_peekHeight` | `setPeekHeight` `getPeekHeight` | `auto`\n`app:behavior_hideable` | `setHideable` `isHideable` | `false` for standard `true` for modal\n`app:behavior_skipCollapsed` | `setSkipCollapsed` `getSkipCollapsed` | `false`\n`app:behavior_fitToContents` | `setFitToContents` `isFitToContents` | `true`\n`app:behavior_draggable` | `setDraggable` `isDraggable` | `true`\n`app:behavior_halfExpandedRatio` | `setHalfExpandedRatio` `getHalfExpandedRatio` | `0.5`\n`app:behavior_expandedOffset` | `setExpandedOffset` `getExpandedOffset` | `0dp`\n`app:behavior_significantVelocityThreshold` | `setSignificantVelocityThreshold` `getSignificantVelocityThreshold` | `500 pixels/s`\n\nTo save behavior on configuration change:\n\nAttribute | Related method(s) | Default value\n------------------------ | --------------------------------- | -------------\n`app:behavior_saveFlags` | `setSaveFlags` `getSaveFlags` | `SAVE_NONE`\n\n### Styles\n\n**Element** | **Default value**\n------------------------- | -------------------------------------------\n**Default style (modal)** | `@style/Widget.Material3.BottomSheet.Modal`\n\nDefault style theme attribute:`?attr/bottomSheetStyle`\n\nNote: The `?attr/bottomSheetStyle` default style theme attribute is for modal\nbottom sheets only. There is no default style theme attribute for standard\nbottom sheets, because `BottomSheetBehavior`s don't have a designated associated\n`View`.\n\n### Theme overlays\n\n**Element** | **Theme overlay**\n------------------------- | ------------------------------------------\n**Default theme overlay** | `ThemeOverlay.Material3.BottomSheetDialog`\n\nDefault theme overlay attribute: `?attr/bottomSheetDialogTheme`\n\nSee the full list of\n[styles](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomsheet/res/values/styles.xml),\n[attrs](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomsheet/res/values/attrs.xml),\nand\n[themes and theme overlays](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomsheet/res/values/themes.xml).\n\n## Theming bottom sheets\n\nBottom sheets support\n[Material Theming](https://material.io/components/sheets-bottom#theming), which\ncan customize color and shape.\n\n### Bottom sheet theming example\n\nAPI and source code:\n\n* `BottomSheetBehavior`\n * [Class definition](https://developer.android.com/reference/com/google/android/material/bottomsheet/BottomSheetBehavior)\n * [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java)\n* `BottomSheetDialogFragment`\n * [Class definition](https://developer.android.com/reference/com/google/android/material/bottomsheet/BottomSheetDialogFragment)\n * [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomsheet/BottomSheetDialogFragment.java)\n\nThe following example shows a bottom sheet with Material Theming, in its\ncollapsed and expanded states.\n\n\n\n#### Implementing bottom sheet theming\n\nSetting the theme attribute `bottomSheetDialogTheme` to your custom\n`ThemeOverlay` will affect all bottom sheets.\n\nIn `res/values/themes.xml`:\n\n```xml\n\n\n\n```\n\nIn `res/values/styles.xml`:\n\n```xml\n\n\n\n```\n\n**Note:** The benefit of using a custom `ThemeOverlay` is that any changes to\nyour main theme, such as updated colors, will be reflected in the bottom sheet,\nas long as they're not overridden in your custom theme overlay. If you use a\ncustom `Theme` instead, by extending from one of the\n`Theme.Material3.*.BottomSheetDialog` variants, you will have more control over\nexactly what attributes are included in each, but it also means you'll have to\nduplicate any changes that you've made in your main theme into your custom\ntheme.\n", "markdown_path": "docs/components/BottomSheet.md", "meta": {"keywords": null, "meta_robots": " ", "schema_structure": "{}", "share_description": null, "share_image": null, "share_title": null}, "title": "Bottom sheets", "up_next_section": {"items": []}, "document_id": "4640185669255168"}, "mdc_flutter": {"banners": [], "hide_generic_not_available_message": false, "hide_intro_block": true, "image": [], "is_beta": false, "markdown_content": null, "markdown_path": null, "meta": {"keywords": null, "meta_robots": " ", "schema_structure": "{}", "share_description": null, "share_image": {}, "share_title": null}, "title": "Bottom Sheets", "up_next_section": {"items": []}, "document_id": "5472253978476544"}, "mdc_web": {}, "metadata": {"keywords": "Material, Material Design, Google, Material Design 3, Material 3, Material You, Material Design Components, Material Components, components, cards", "meta_robots": null, "schema_structure": "{}", "share_description": "Bottom sheets show secondary content anchored to the bottom of the screen. There are two types of bottom sheets: standard and modal.", "share_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/c953bf9b2efdd14a850224135254c8b148a26adbce86d8a93dcf42439deb393f1ebbb519dc761df6c1569e1c08f757a88e4d533cc82dc8c7f4d25b16b781fb79", "height": 560, "url": "https://lh3.googleusercontent.com/anRizsTTKb3ZFDxvaOQnGhnaepLiJtk__kY2eOG1-X9tBEOZ_Efjd8vHU14mujMBUuRZXdKZjW6kZp32adjusmMOSOrgzXKXqvJHUUkEDF9C-tteNDyQ", "width": 1120}, "share_title": "Bottom sheets \u2013 Material Design 3"}, "page_hero": {"foreground_image_1x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/423127aef1c64cb6a4d543d785647510ee752773c1e3730cd2aedeea5ce998522efed9ab837c46053e0b59e94b383b0626daf465340c81b7c98b05e944f9318d", "height": 1120, "url": "https://lh3.googleusercontent.com/LYDT9qYA1q7NLsDc9uG-q6ADc8znPvqUZ78u1oAg28gAsH6KDuUou2jk_OBbds4h3MDfZ0HwStPi5LPeXDGvpm7CkL1me_RlYTWXsHBmtW-FAYY-nXE", "width": 1120}, "foreground_image_1x1_dark_mode": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/2b1aa6610cb93219f521691df9d488f81e51d42cdf8515a454ad22f7f2820ed3c35ec81f42f80b2715a47e0d9cc6cff730ff516220572fa989ad9ccb7f1573e7", "height": 1120, "url": "https://lh3.googleusercontent.com/G9867ICt9b-lncJHHeVCxGxXlAKtIHq2Mzk4exKf7IkmAbj663wW-c4gIUsyeT0JfuNm8rrf1Hmo1wdCDS5AH0zScZNH8SLxn0iIi_58kS--vmVqPg", "width": 1120}, "foreground_image_2x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/f27e561a36c757333bc0e1671f30bf024bf5fc42d120495a9f443ce9cd207fa573c01d10b4ea977586470eeca1a134949841971504e59d1d12d34543974f2d72", "height": 1000, "url": "https://lh3.googleusercontent.com/6Xkb4jV_z0luNQskg19qC2PaETvm-JM49LlNOeqzfFiSXSmwae6J93dXBo7Jc442xJdZSENioqa28IsM406PXN_bPxo53-4kw5ZpXyj1lGKzOC2ORfSS", "width": 2000}, "foreground_image_2x1_dark_mode": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/22fc7f4780841efbb4c9acc5c9134eb76d84aec484c21dcc003c134fa90b77495aa6ed31efaaeec851096e2746ca4660aa396a49c45ed1726c6b1552b883d75c", "height": 1000, "url": "https://lh3.googleusercontent.com/XCWmA6moCnvUU5E2-0TNFM3MY2l4DlNkwRzwrjbD_ZHXcP-h2H02Y6UZ9juXVm4O0Eliok1wjM7kc4mZGRfRKEW_lS0DAk_qJdpPkG6gWtKozPpGOnfY", "width": 2000}, "full_width": false, "full_width_content_centered": false, "horizontal_foreground_alignment_override": null, "main_background_image": {}, "main_background_image_dark_mode": {}, "split_background_asset": {}, "split_background_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/6ab37909dc8478dd5e56137ee989c11507af974de96d6b52d7b04c096eaf500eb6cb297ecbc6d68d676634ef0c77bc196b4b50daf9101baf0a393f0d4e4d70c5", "height": 4096, "url": "https://lh3.googleusercontent.com/Zepnyf8JSnn6jKK6Zq6Ppm1oWSVdUMujaWc3pp-TMsk0U5THxsPYusqEIccnUary6eohY1UoVBgB7bQQ9FRgawuvXKDAPy2EPZwj6D9zgkRPV-breddj", "width": 4096}, "split_background_image_dark_mode": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/11266e9deb9db2deaab0114997d42033baa68f7cdc695f7f62dae5722a76b2e4787c5b96f0193b88c26968894dfbc41cc8fb0d07608fb463b21f5afd20ff238b", "height": 4096, "url": "https://lh3.googleusercontent.com/ewqelGlD5dUrp4LyDl4uyaS1aEAT0LqmTGwUblaNx6UlioWljMsus4141fu-waKli5dgu71O2g42Kl8owxFv6ijaFo_HTSnexyGaVHwF1DUqPEfW0Q", "width": 4096}, "vertical_foreground_alignment_override": null}, "title": "Bottom sheets", "document_id": "5317529291587584", "modified_date": "2025-04-25"}
\ No newline at end of file
diff --git a/evidence/surface/fetch/dialogs.json b/evidence/surface/fetch/dialogs.json
new file mode 100644
index 0000000..6696384
--- /dev/null
+++ b/evidence/surface/fetch/dialogs.json
@@ -0,0 +1 @@
+{"category": "containment", "component_image_1x1": {}, "component_image_2x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/1201b10f2aa29cc2b012aedf3d6767fa7b34b9a502457c68e1a6ef87c1b3d8c35e2f631bec3c785a03d0ee6e560e2004e6cf4527dc1faf03206476fde49d502f", "height": 560, "url": "https://lh3.googleusercontent.com/CFJfp5JSKy1CCK9X0jVO7pRomxEXLpvUPlj6biCd8V1zkvQAdNnnhWqE-pMeX8nol5yx9YwMikRapiuiahQLUEufqpxDJxs4w00HrUNUMcxLftTcddA", "width": 1120}, "component_image_3x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/a8ee8e7fdedecf347ab7be12bdbe17ae43a145b06f8b6849806808fe705a329dae84930131c39debda19c5ebc03d60d93b102e96323f1a342e65c42354cb52e8", "height": 560, "url": "https://lh3.googleusercontent.com/vA2EVTxGzbpxA8uKcqA_kUbswA5Ip9NL9iuDq6XXU4DlNyAFNQaXu5H0JslNpWvfo0wqTABMQchMT2toI5YQFByNKhkty8pwj3cZXIXBsw0Hd5PwdBlx", "width": 1680}, "component_image_3x2": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/667710b7bb2a7ea03afea363090125566b582bf944873a5fc55e158fc76d05a18f9784140bef9f4d631fe0392e2386d81aa854e59279c438d36a7e2cda9049c3", "height": 560, "url": "https://lh3.googleusercontent.com/asmiS26l6fx3OYk-LUie65WSh26b_c1zjpLUODiitaz_ICoFGQz-hyrnid0pw4GiCWNGy4incRNik8xuTNWjaMyPjKUQo5njyXuwdlm7A8yAFlCZIg", "width": 840}, "component_image_position": "center", "dark_component_image_1x1": {}, "dark_component_image_2x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/994d5fde6374e3edf539c37a962337f22e7ebcecb254b5b8f0b624245e2a34148282576570acac992f31fd10ad56d328f63e06536d38a5dfb0e0112711bcbe73", "height": 560, "url": "https://lh3.googleusercontent.com/8r_6V4X-Rw2Iywz74S0SHRsbe1Dghi2GmPHKHTqQ_AkOKhjWXsiJ9RUGx4l8n5WT17AHdj9E2ycJlIzqqPWJJ0Lii3C-WgXV1em_EEEtl4t0H1UGULg", "width": 1120}, "dark_component_image_3x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/cf8967735ecfa675a864c392e728b94723b3d365ac8e0334c776fa4d2a3ed869e6a7514cd0f0671c753f33d891fd49a4d84450b8c05482c95e261603268075ba", "height": 560, "url": "https://lh3.googleusercontent.com/kVRpL11HJpgVz1WwKXoePGQs50bzRfd_vrk1gntt3VMrQDniwtkEinIYOj7CDeKh4j38EaxuR4OIXHao0ahKelQyT2fvNvB6-qMw7pplTaARrXLP9r-F", "width": 1680}, "dark_component_image_3x2": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/720bedc77eae4ce24f71b551978a503ba999d9518f626b3478efe9166a76765ce767f84b59d5952c9bcca3cdc69fcac897ca20503ed5b0418fd57dee4cdca900", "height": 560, "url": "https://lh3.googleusercontent.com/UxQqwGi4wvijSnl77_1OLF9jpxNnS6zoadsG94JlTwHVusL-jOc5M2EMECCgSpuhp7euZlXdo2dj4s5o23F3puPqaLS6mS91DVS8ANeQGbNsGIrYBw", "width": 840}, "description": null, "hero": {"background_asset": {}, "background_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/374704d69859062037341f8f902b1efd04a623e626051d8249b8cd4ffe237f131bb0259de564e978f96a5ad0e3386721618b3cbb1f01890c63d94109c0f84fcc", "height": 4096, "url": "https://lh3.googleusercontent.com/p5D8lSFRzHHtJeJh9glEIalFFsCBHp0PBPImeTzekk3IE8mtEYxCMNXaj0TffIlcPkdFOjvjLf6Zp7SZDpNOA2e6M6jJNHoA5jRV5V0iSSE8AAljlZ4", "width": 4096}, "foreground_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/96b77eadf1dc3f9f97ed94edfdbe69a7716114344cb29f3a4ab0e9265945e617679c07754f984ed5d53fca0741176f1973d95bd76928ec9dd845487c81db3c1b", "height": 1816, "url": "https://lh3.googleusercontent.com/cD2FTVbrqtc_pZ7IwitArkWuRFGvQ_CHj-cuD76UiDUZZpjY2F0EmeUPmdLdf29NQKITPu540wiWTGIz4CbCZFzE_REolC9FEWXM6_pShckgW2Wg9Q", "width": 856}, "foreground_image_alignment": "center", "foreground_image_svg": null, "foreground_text_light": false}, "ia_title": "Components / Dialogs", "isDisplayed": true, "jetpack": {}, "mdc_android": {"banners": [{"description": "
Dialogs are available in the Material Design 3 library for Jetpack Compose. For implementation details, visit the androidx.compose.material3library documentation on Android Developers:
", "links": [{"aria_label": "Read about implementing Material Design 3 AlertDialogs using Jetpack Compose", "href": "https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#alertdialog", "label": "AlertDialog API reference", "open_new_window": true}], "title": "Jetpack Compose support available"}], "hide_generic_not_available_message": true, "hide_intro_block": true, "image": [{"a11y_description": "Examples of the two types of dialogs.", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/4702378141911baecc1e6721afe2722cd953dbcece8c607f27ad431a1d2f3c59255d1b291781cfc8ea2251a4a42395ec674cec71ee85f8eb9712bffc963792d1", "height": 1036, "url": "https://lh3.googleusercontent.com/U3KG3u3LdCQsgAwfJMpOsk_RAZF1qYzBg-IVOQIqZEMmicfZu-1hHHHtfPTORjuMlP6zl5-gBV7ZMGoCxvQpGhL9_605HTMGUmVBBlSc8auT_9lV", "width": 1999}}, {"a11y_description": "\"Dialog with title, and text and purple buttons to cancel, decline or accept\"", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/641d369239c3c8122c7504ec20bb4734d7fa5e82211b760e7a2b353cafe5120bba9117af0a26e7d3726d8c9384f52e5c083ab8cd6df4b7eb8cea49f90703c8ed", "height": 687, "url": "https://lh3.googleusercontent.com/kEgwg9iNd9jboCKEfFrhvyJYod5D4ZALPrNd5Ru63Cqy4OG_FCX7zs8RU5AKBp1KrJxZzhGvu1QWG8dv1Z4Ngc0BYp8CChsqKcV8YFhnddJqRJU50ZQ", "width": 1080}}, {"a11y_description": "anatomy", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/34848ab749d6ff65f57f82b2c5ff5a54fe75ee3e7edafbe09cd860b75f16216d8409c6d0a3e044f13e4de1dd4c0db6b0f2c692c8265af4cc422f2939523b3b2e", "height": 970, "url": "https://lh3.googleusercontent.com/7GNdJqfCwAzo8wn9yzGsmGPeAFU3R3s6_A3VCdKX9u9cpGmQeCgnYni2t6CleU4bMEq01sBxsgQsNRJ2agc6lEdo5-j2angIeg87yqOTOTaaQS8Awbo", "width": 1410}}, {"a11y_description": "Dialog with title and text buttons in brown and selected radio button in pink", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/beb9fd6d806c96a1fc008977850761f1bc11c0e49fac272a021e11785fba46813bd8f6c66e31d1a6df3853f7cab78b7a242871798b0b94a20a09901925a8e0d5", "height": 737, "url": "https://lh3.googleusercontent.com/Ts8rbXjz2Oemc7LixUJrHcsXICUeM2tuHs89gPRfDL9pIfbLsHdu_5UUBCpx6cM8jkiFn3qAtNj3Ue0cpMTTzTprdhwjw-e45G4JrpaX51p4aHU9iOo", "width": 1080}}], "is_beta": false, "markdown_content": "\n\n# Dialogs\n\n[Dialogs](https://material.io/components/dialogs/) inform users about a task and\ncan contain critical information, require decisions, or involve multiple tasks.\n\n\n\n**Contents**\n\n* [Using dialogs](#using-dialogs)\n* [Basic dialog](#basic-dialog)\n* [Full-screen dialog](#full-screen-dialog)\n* [Theming](#theming-dialogs)\n\n## Using dialogs\n\nBefore you can use Material dialogs, you need to add a dependency to the\nMaterial Components for Android library. For more information, go to the\n[Getting started](/libraries/mdc-android/getting-started)\npage.\n\n```kt\nMaterialAlertDialogBuilder(context)\n // Add customization options here\n .show()\n```\n\n### Making dialogs accessible\n\nThe contents within a dialog should follow their own accessibility guidelines,\nsuch as an icon on a title having a content description via the\n`android:contentDescription` attribute set in the\n`MaterialAlertDialog.Material3.Title.Icon` style or descendant.\n\n### Types\n\nThere are two types of dialogs: 1\\. [Basic dialog](#basic-dialog), 2\\.\n[Full-screen dialog](#full-screen-dialog)\n\n\n\n## Dialogs\n\nA dialog is a type of modal window that appears in front of app content to\nprovide critical information or ask for a decision. Dialogs disable all app\nfunctionality when they appear, and remain on screen until confirmed, dismissed,\nor a required action has been taken.\n\nDialogs are purposefully interruptive, so they should be used sparingly.\n\n### Dialog examples\n\nAPI and source code:\n\n* `MaterialAlertDialogBuilder`\n * [Class description](https://developer.android.com/reference/com/google/android/material/dialog/MaterialAlertDialogBuilder)\n * [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/dialog/MaterialAlertDialogBuilder.java)\n\n## Basic dialog\n\nThe following example shows a basic dialog.\n\n\n\nIn code:\n\n```kt\nMaterialAlertDialogBuilder(context)\n .setTitle(resources.getString(R.string.title))\n .setMessage(resources.getString(R.string.supporting_text))\n .setNeutralButton(resources.getString(R.string.cancel)) { dialog, which ->\n // Respond to neutral button press\n }\n .setNegativeButton(resources.getString(R.string.decline)) { dialog, which ->\n // Respond to negative button press\n }\n .setPositiveButton(resources.getString(R.string.accept)) { dialog, which ->\n // Respond to positive button press\n }\n .show()\n```\n\n## Full-screen dialog\n\nFull-screen dialogs group a series of tasks, such as creating a calendar entry\nwith the event title, date, location, and time. Because they take up the entire\nscreen, full-screen dialogs are the only dialogs over which other dialogs can\nappear.\n\nThere is no specific Material implementation of a full-screen dialog. You can\nimplement it by using a\n[`DialogFragment`](https://developer.android.com/reference/androidx/fragment/app/DialogFragment)\nas explained in the\n[Android Developer guides](https://developer.android.com/guide/topics/ui/dialogs#FullscreenDialog).\n\n### Anatomy and key properties\n\nA dialog has a container, content (either supporting text or a set of items of a\nparticular type), a background scrim, and, optionally, title and buttons.\n\n\n\n1. Container\n2. Icon (optional)\n3. Title (optional)\n4. Content\n5. Buttons (optional)\n6. Scrim\n\n#### Container attributes\n\nElement | **Attribute** | **Related methods** | **Default value**\n----------------------------------- | -------------------------------------------------------- | ------------------------------------------------------ | -----------------\n**Color** | N/A | N/A | `?attr/colorSurface`\n**Shape** | `app:shapeAppearance` `app:shapeAppearanceOverlay` | N/A | `?attr/shapeAppearanceMediumComponent` with a corner size of `28dp`\n**Background inset start and end** | `app:backgroundInsetStart` `app:backgroundInsetEnd` | `setBackgroundInsetStart` `setBackgroundInsetEnd` | `24dp`\n**Background inset top and bottom** | `app:backgroundInsetTop` `app:backgroundInsetBottom` | `setBackgroundInsetTop` `setBackgroundInsetBottom` | `80dp`\n\n#### Title attributes\n\nElement | **Attribute** | **Related methods** | **Default value**\n-------------- | ------------------------ | -------------------------------- | -----------------\n**Text label** | N/A | `setTitle` `setCustomTitle` | `null`\n**Text color** | `android:textColor` | N/A | `?attr/colorOnSurface`\n**Typography** | `android:textAppearance` | N/A | `?attr/textAppearanceHeadlineSmall`\n**Icon** | N/A | `setIcon` `setIconAttribute` | `null`\n**Icon tint** | `app:tint` | N/A | `?attr/colorSecondary`\n\n#### Content attributes\n\n**Supporting text**\n\nElement | **Attribute** | **Related methods** | **Default value**\n-------------- | ------------------------ | ------------------- | -----------------\n**Text** | N/A | `setMessage` | `null`\n**Color** | `android:textColor` | N/A | `?attr/colorOnSurfaceVariant`\n**Typography** | `android:textAppearance` | N/A | `?attr/textAppearanceBodyMedium`\n\n**List item**\n\nElement | **Attribute** | **Related methods** | **Default value**\n--------------------------------------- | ------------------------------ | ---------------------- | -----------------\n**List item layout** | `app:listItemLayout` | `setItems` | [`@layout/mtrl_alert_select_dialog_item`](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/dialog/res/layout/mtrl_alert_select_dialog_item.xml)\n**List item layout style** | N/A | N/A | `?attr/materialAlertDialogBodyTextStyle`\n**List item text color** | `android:textColor` | N/A | `?attr/colorOnSurfaceVariant`\n**List item typography** | `android:textAppearance` | N/A | `?attr/textAppearanceBodyMedium`\n**Multi choice item layout** | `app:multiChoiceItemLayout` | `setMultiChoiceItems` | [`@layout/mtrl_alert_select_dialog_multichoice`](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/dialog/res/layout/mtrl_alert_select_dialog_multichoice.xml)\n**Single choice item layout** | `app:singleChoiceItemLayout` | `setSingleChoiceItems` | [`@layout/mtrl_alert_select_dialog_singlechoice`](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/dialog/res/layout/mtrl_alert_select_dialog_singlechoice.xml)\n**Multi/single choice item style** | `android:checkedTextViewStyle` | N/A | `@style/Widget.Material3.CheckedTextView`\n**Multi/single choice item text color** | `android:textColor` | N/A | `?attr/colorOnSurfaceVariant`\n**Multi/single choice item typography** | `android:textAppearance` | N/A | `?attr/textAppearanceBodyLarge`\n\n**Note:** You can set any custom view to be the content of your dialog via the\n`setView` method.\n\n#### Buttons attributes\n\nElement | **Attribute** | **Related methods** | **Default value**\n------------------------------------------------ | --------------------------------- | ------------------- | -----------------\n**Buttons theme attributes (negative/positive)** | `app:buttonBar*ButtonStyle` | N/A | `@style/Widget.Material3.Button.TextButton.Dialog`\n**Buttons theme attributes (neutral)** | `app:buttonBarNeutralButtonStyle` | N/A | `@style/Widget.Material3.Button.TextButton.Dialog.Flush`\n**Buttons (neutral/negative/positive)** | N/A | `set*Button` | `null`\n**Icons** | N/A | `set*ButtonIcon` | `null`\n\nFor specific button attributes, see the\n[Buttons documentation](https://github.com/material-components/material-components-android/tree/master/docs/components/Button.md).\n\n#### Scrim attributes\n\nElement | **Attribute** | **Related methods** | **Default value**\n-------------- | ----------------------------- | ------------------- | -----------------\n**Dim amount** | `android:backgroundDimAmount` | N/A | 32%\n\n#### Theme overlays\n\nElement | **Theme overlay**\n-------------------------- | -----------------\n**Default theme overlay** | `ThemeOverlay.Material3.MaterialAlertDialog`\n**Centered theme overlay** | `ThemeOverlay.Material3.MaterialAlertDialog.Centered`\n\nDefault theme overlay attribute: `?attr/materialAlertDialogTheme`\n\n#### Theme attributes\n\nElement | **Theme attribute** | **Default value**\n------------------------- | ----------------------------------------- | -----------------\n**Default style** | `?attr/alertDialogStyle` | `@style/MaterialAlertDialog.Material3`\n**Title text style** | `?attr/materialAlertDialogTitleTextStyle` | `@style/MaterialAlertDialog.Material3.Title.Text`\n**Supporting text style** | `?attr/materialAlertDialogBodyTextStyle` | `@style/MaterialAlertDialog.Material3.Body.Text`\n\nSee full list of\n[styles](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/dialog/res/values/styles.xml),\n[attributes](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/dialog/res/values/attrs.xml),\nand\n[theme overlays](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/dialog/res/values/themes.xml).\n\n## Theming dialogs\n\nA dialog supports\n[Material Theming](https://material.io/components/dialogs/#theming) which can\ncustomize color, typography and shape.\n\n### Dialog theming example\n\nAPI and source code:\n\n* `MaterialAlertDialogBuilder`\n * [Class description](https://developer.android.com/reference/com/google/android/material/dialog/MaterialAlertDialogBuilder)\n * [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/dialog/MaterialAlertDialogBuilder.java)\n\nThe following example shows a dialog with Material Theming.\n\n\n\n#### Implementing dialog theming\n\nSetting the theme attribute `materialAlertDialogTheme` to your custom\n`ThemeOverlay` will affect all dialogs.\n\nIn `res/values/themes.xml`:\n\n```xml\n\n\n\n```\n\nIn `res/values/styles.xml`:\n\n```xml\n\n\n\n\n \n\n\n```\n\nOr if you want to change only one specific dialog, pass the `themeResId` to the\nconstructor:\n\n```kt\nMaterialAlertDialogBuilder(context, R.style.ThemeOverlay_App_MaterialAlertDialog)\n ...\n .show()\n```\n", "markdown_path": "docs/components/Dialog.md", "meta": {"keywords": null, "meta_robots": " ", "schema_structure": "{}", "share_description": null, "share_image": null, "share_title": null}, "title": "Dialogs", "up_next_section": {"items": []}, "document_id": "4969122496249856"}, "mdc_flutter": {"banners": [{"description": "
", "links": [{"aria_label": "Read about implementing Material Design 3 Dialog using Flutter", "href": "https://api.flutter.dev/flutter/material/Dialog-class.html", "label": "Dialog API reference", "open_new_window": true}, {"aria_label": "Read about implementing Material Design 3 AlertDialog using Flutter", "href": "https://api.flutter.dev/flutter/material/AlertDialog-class.html", "label": "AlertDialog API reference", "open_new_window": true}], "title": "Flutter support available"}], "hide_generic_not_available_message": true, "hide_intro_block": true, "image": [], "is_beta": false, "markdown_content": null, "markdown_path": null, "meta": {"keywords": null, "meta_robots": " ", "schema_structure": "{}", "share_description": null, "share_image": {}, "share_title": null}, "title": "Dialogs", "up_next_section": {"items": []}, "document_id": "5240559261974528"}, "mdc_web": {}, "metadata": {"keywords": "Material, Material Design, Google Material, Google, Material Design 3, Material 3, Material You, Material Design Components, Material Components, components, dialogs", "meta_robots": null, "schema_structure": "{}", "share_description": "Dialogs provide important prompts in a user flow. Use dialogs to make sure users act on information", "share_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/645047f1a7e12769b092d3f1746d0a86fc87b52d90a065f64ae8582a5547fa67706ae0c2b4ca6193b7df3172692ae586d3368247bcdb349749ac862e2e7c6bfb", "height": 560, "url": "https://lh3.googleusercontent.com/mfskU6xDOqSsTCcgnGcsTfdCcT35ftTpCJmb4kuchYadG_41OF410XdGQP08L3iqFsq_pwIJ7_Zyf-8e-8J7nEwdNWMB-PqZGdICdnLsmHYfXy08Wrw", "width": 1120}, "share_title": "Dialogs \u2013 Material Design 3"}, "page_hero": {"foreground_image_1x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/c26a0789742bad25a27d711dea8e655b1048515c311e47467b70ce19aada489175aeebb7b779c1dadfe85d429aec3c13715cea872735e5b59951114d5a593bc2", "height": 1120, "url": "https://lh3.googleusercontent.com/q-dfTEhPWF_-_eM4VpJrZA694bAqAO8BJMQMvVrDsqZ1kHyCOOhhnaXWYJw-wP3bGv6CXAKaEOrAxk2OEY47frCMhjGgp4ZZUtFsUCeMn0dthK0T8w", "width": 1120}, "foreground_image_1x1_dark_mode": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/9ed9fa99e47af01d81ae06cb058b307f64fe5ad80f518c46655fab1b66a8eb019aca19b27aaf200320caaaced0560bc0e68888d728194894bdd9710eb1080594", "height": 1120, "url": "https://lh3.googleusercontent.com/NFxYirrIZx8x5PbwPfor383cpP0mjHhJtIeXy-fAbo878fU2IJ5NEiRubwH3xHFVuN8J12QtUD6erRN6YCyx7e1OluYRzs8hYtRFer3K5oNn562hrOo", "width": 1120}, "foreground_image_2x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/16ec9ceebf3552311c38177ae3e547df730ad4398714a450983194b622471a59168f0d13e76f271096b27d3e5cf3d309a08de27408b08466dfad4e022ecd995c", "height": 1000, "url": "https://lh3.googleusercontent.com/euzNp7-lR2RlNLgwaPFT0mjcpki6eFepE5yrYjxsGmVn3Mi-3e7nmGiVWBdPKUYHKn9cWZncX_09QVYT_0QWdl8vbo88ajt3Usbeqv-_ypmB0pu43-9x", "width": 2000}, "foreground_image_2x1_dark_mode": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/22d90bf7a02bea312349422e7bc3a1a2b915f0a07b4d25e51fc5bef75cd7a3d2cbfe49fe6851f048ad712861e2ac09844daab502007b649de1103fde29ed466c", "height": 1000, "url": "https://lh3.googleusercontent.com/QfWucnJId1YNG8tBqQdCxL5aDcK5kcDxCo57DWf_e9_Q8y-W1Om1ulxFtF0WZI6V4W-bBxj7spcHt2vJx1LfnWFE_PMmQ-aRYS-kH2COxgwyAgmWJg8", "width": 2000}, "full_width": false, "full_width_content_centered": false, "horizontal_foreground_alignment_override": null, "main_background_image": {}, "main_background_image_dark_mode": {}, "split_background_asset": {}, "split_background_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/06a3ad2112a7a75e2a38793327f4e8e486d6c9419a6767c779d54d7dbbacfbc5490ff86f55d08ab821aa6e89881362465f8332e9bf5eb67b9c7c9bba8b59c6d6", "height": 4096, "url": "https://lh3.googleusercontent.com/ClaZSxMwSB7FD1hlooJt1hkkA2NiWXWIVrCHb1qizShsE2nNht2euUQm6BmS-C6B6nwzB8DUuuUjYy25E9weS5oMxQFkrWgcucbDtXtBobfs_riZootR", "width": 4096}, "split_background_image_dark_mode": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/04794fbf602312f722329bc76020840bdd22adb6c751aba94802088e571ebaff854cfb859c1cf2efc541f99ed8d274386b67b60c8ad4586e3d9a6079a1af0404", "height": 4096, "url": "https://lh3.googleusercontent.com/hORpCLy8XRY7280TZFng7WkrjURav7C3AApKqntCFUZnFZ4_NybzinpEA-HSGL46pPn_m96V4GL9t08BxTkkKmT3FX3SrRsUi2yWdQyzwQmUqzNSSMj7", "width": 4096}, "vertical_foreground_alignment_override": null}, "title": "Dialogs", "document_id": "5732087063379968", "modified_date": "2025-05-01"}
\ No newline at end of file
diff --git a/evidence/surface/fetch/layout-applying.json b/evidence/surface/fetch/layout-applying.json
new file mode 100644
index 0000000..61b87c6
--- /dev/null
+++ b/evidence/surface/fetch/layout-applying.json
@@ -0,0 +1 @@
+{"category": null, "component_image_1x1": {}, "component_image_2x1": {}, "component_image_3x1": {}, "component_image_3x2": {}, "component_image_position": "center", "dark_component_image_1x1": {}, "dark_component_image_2x1": {}, "dark_component_image_3x1": {}, "dark_component_image_3x2": {}, "description": "Use window size classes to create layouts that scale across most devices and form factors.", "hero": {"background_asset": {}, "background_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_be877ab23edb4a39be610a57a771c69d_/2a62e31378857d6697036e05f7be96efbf388211a4d46eeefaecbe993b06d74be5826e0e9dcc08fc8b450c5b0faef9f061aebc8cce918d965b2aa7338b8833a6", "height": 1200, "url": "https://lh3.googleusercontent.com/CIyB5lCif4nv2PAOf5rXTP3F5QR1FrtGwOfF0nTi1YUu38kd9Q-16uQ4huiiaPFftqAgZqNM8JaymDXcxUb_P_4vASsP8hlP6jMDXXfezufgTRkMC9s", "width": 2400}, "foreground_image": {}, "foreground_image_alignment": "top", "foreground_image_svg": null, "foreground_text_light": false}, "ia_title": "Foundations / Layout / Applying layout", "isDisplayed": true, "jetpack": {}, "mdc_android": {}, "mdc_flutter": {}, "mdc_web": {}, "metadata": {"keywords": "Material, Material Design, Google Material, Google, Material Design 3, Material 3, Material You, adaptive, responsive, layouts", "meta_robots": " ", "schema_structure": "{}", "share_description": "Use window size classes to create layouts that scale across most devices and form factors.", "share_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_be877ab23edb4a39be610a57a771c69d_/a5205fc1d66462945233e8abb82dd1a98633141a775cf9809bdfc8a8dbcd6bc2215cfc3309a640dceb773eca70977b133e878cd2e6b98ade4a377686ef149260", "height": 540, "url": "https://lh3.googleusercontent.com/9IC8KyAT2SkaRgd0oRs6Q9_EThFKVJQomkWlNMfvupqnnafcaoMNDpju-OSuXaKR5pvAwLF1NGQibVeKPdmwuisCTerH_NdiJdpxYi2AmD9QT_Nleg", "width": 960}, "share_title": "Applying layout \u2013 Material Design 3"}, "page_hero": {"foreground_image_1x1": {}, "foreground_image_1x1_dark_mode": {}, "foreground_image_2x1": {}, "foreground_image_2x1_dark_mode": {}, "full_width": false, "full_width_content_centered": false, "horizontal_foreground_alignment_override": null, "main_background_image": {}, "main_background_image_dark_mode": {}, "split_background_asset": {"file_name": "Google_Mio_UnderstandingLayout_1080x1080.mp4", "file_path": "f3eb2abc4e890d68862f4d078fb6c1b39232bc11ccf12df23c1b7dd35eb6b7554fd0c937fd186d6340d5147c096764df5a2075bff03d3b64333cd60d0efd067a", "url": "https://kstatic.googleusercontent.com/files/f3eb2abc4e890d68862f4d078fb6c1b39232bc11ccf12df23c1b7dd35eb6b7554fd0c937fd186d6340d5147c096764df5a2075bff03d3b64333cd60d0efd067a"}, "split_background_image": {}, "split_background_image_dark_mode": {}, "vertical_foreground_alignment_override": null}, "title": "Applying layout", "document_id": "6396979944620032", "modified_date": "2024-05-10"}
\ No newline at end of file
diff --git a/evidence/surface/fetch/manifest.yaml b/evidence/surface/fetch/manifest.yaml
new file mode 100644
index 0000000..a44b7e4
--- /dev/null
+++ b/evidence/surface/fetch/manifest.yaml
@@ -0,0 +1,17 @@
+schema_version: 1
+package: "surface_fetch_baseline"
+updated_at: "2026-02-23"
+source_run: "EXP-2026-02-22-longrun-001"
+source_path: "drafts/experiment/runs/EXP-2026-02-22-longrun-001/fetch"
+notes:
+ - "This fetch set was selected as the canonical preserved baseline."
+ - "Equivalent fetch sets existed in other drafts runs and were byte-identical."
+files:
+ - "bottom-sheets.json"
+ - "dialogs.json"
+ - "layout-applying.json"
+ - "menus.json"
+ - "motion-easing-duration.json"
+ - "motion-transitions.json"
+ - "navigation-drawer.json"
+ - "side-sheets.json"
diff --git a/evidence/surface/fetch/menus.json b/evidence/surface/fetch/menus.json
new file mode 100644
index 0000000..ad78789
--- /dev/null
+++ b/evidence/surface/fetch/menus.json
@@ -0,0 +1 @@
+{"category": "selection", "component_image_1x1": {}, "component_image_2x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/f3d1d05fa3f98b7ed34577f6bf887203bd162e7b9345e649ff4cbf5fed67d6f6ab856ea9ccd72f468d1470880f1d9dc00bbeb72b9846dbb421bcda0fbc41139c", "height": 560, "url": "https://lh3.googleusercontent.com/k9FOfw5GEcyNWd-Iqc8zhd2qfWULlhXjaEnV_N_twzxIYtR77YiLgLEwGgcK-xdWA4uumfdX-aYKo_V8-Ufqarw-n2YmJqeNiyDcNEqza48A7eipbOg", "width": 1120}, "component_image_3x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/205babd7b20c05185e7b46ac4fae5fb4d132f7ff66544af9160f3f36e424b640f0b0791be257bd8b2e8554e4af9cfc2312ca19b10da7f238ba6c331e917af67a", "height": 560, "url": "https://lh3.googleusercontent.com/xtpBXljEGhJN5JEnvNw2UVR_l_yng6IfOoCQGC6Ls4WxVvoROmN5O8qyvdCoIelP90wzZR3_QN41CDXlHgqZnw5_WjWIs9uPP8iI0K90lFQ_HsCLyh0", "width": 1680}, "component_image_3x2": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/3e17b5ed53619638fe6ece233a0a31848381cf699b3685def701b7d49f8f9e5eb0561a077a3ca478c95d76ef770e8c5f80478a0a49b94522078eb5cf3c481fa5", "height": 560, "url": "https://lh3.googleusercontent.com/VxcZwSoYqM_KdhpHLPeAaM-KScIOFBt4uzxbifNaVrQwQYEw6ZbDh-ehhaBrd_lxWn5mTxUXUKb_foMmV-zYN-rd4vJOEl6cxWdZToarE8edli0mhp4", "width": 840}, "component_image_position": "center", "dark_component_image_1x1": {}, "dark_component_image_2x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/f46cfd9b67bc7fcda8898b95c5f15a6642f8d0227aceb890cb46d730bd4b57f3549972d614c76bfdc1a7ab9b5af623eb8ba675c431f01f1a6c28851962f30c48", "height": 560, "url": "https://lh3.googleusercontent.com/g-nOrn0GXf-nZJ22TwAM8ChICae6oDtnAVPPWnoe0JQ9Mh2OpWkl_qfQ45Q59YUZDv2ZCLnpierG13fE8UinLDi1xFNv1XyyYmiw0vf0wfqEjvadTJI", "width": 1120}, "dark_component_image_3x1": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/89bb3e1f2cd4b1ca5e62e21f9042322a871a424e12dcb6c5b46d1fe57606f5283f9f5b73b9ecd354963a903739e7212af3fb8b950d30c5fbec2975419f26085e", "height": 560, "url": "https://lh3.googleusercontent.com/8qsTRu46sT7aR_EHcDRrbgTHAp_emItoya46tNkjc8u0i5UHbeo3u5kViYcYSXq8v0UeGyFXJeCDyJ8Nm4dqxpMc-gkASRqZDIa94rTnpXe5lCFBFNzQ", "width": 1680}, "dark_component_image_3x2": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/aa0f1e028214700f1e8d3cccf8824090f78360e951eabac3341cd71dbb64778c761d6a2b42f075a87360a70de9a2aaa95682a11bdac4c679a0d2004cd2547daf", "height": 560, "url": "https://lh3.googleusercontent.com/4un3gOBZ2kAujd4tLWLaKBpPgfMyz0TEQzAcdQqRTj0KFe-snSi9L7Sqpe0rdFwruDkRvn4wlQmtqJPNY54F1fQeKkXuGlFHyKjLWB2JsdpAuPIyNN4", "width": 840}, "description": null, "hero": {"background_asset": {}, "background_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/7a8fa848b6b77f42495f75eafac551a09a53d560ca7a9430c7a5056f89ba232f0bb1fd6ef8b4888e8f33871e050a153c2d8eb677e4a8c45a68775a42c305784d", "height": 4096, "url": "https://lh3.googleusercontent.com/2fY0UpxDH_6NCU8n8L7xSnxSokFQqdA8qOWo_NNCU8ikQW5BhiP4Ha5aP_L0oYnFC8wtHdkEtxxC8CcGue6Tj_wrY7-QtEVsYjZziIasgck97JD4pIo", "width": 4096}, "foreground_image": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_95f9980dd12d4f8886c881994eaa6a79_/ec9075bd851c86a3ae9930b62d7fefbc2cc4300973d11813ae6c8f7cbc2da2b00341136ab501bac4e3a8e8e85c144ced8f574fc89a1f0c25afa66c66b9f54cb1", "height": 1120, "url": "https://lh3.googleusercontent.com/1nhfDYUuWn2Y837J6niWOD9td-3BcYN1e83LtvwoWVyMrs16SQcxxWnr1dlxrQPyyTPe08aKzSlxetsVLWX1uBTyvdYRthlhgNrv5Q8UnxJGGbj1Ow", "width": 1120}, "foreground_image_alignment": "top", "foreground_image_svg": null, "foreground_text_light": true}, "ia_title": "Components / Menus", "isDisplayed": true, "jetpack": {}, "mdc_android": {"banners": [{"description": "
Menus are available in the Material Design 3 library for Jetpack Compose. For implementation details, visit the androidx.compose.material3library documentation on Android Developers:
", "links": [{"aria_label": "Read about implementing Material Design 3 AlertDialogs using Jetpack Compose", "href": "https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#DropdownMenu(kotlin.Boolean,kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.ui.unit.DpOffset,androidx.compose.ui.window.PopupProperties,kotlin.Function1)", "label": "DropdownMenu API Reference", "open_new_window": true}, {"aria_label": "Read about implementing Material Design 3 ExposedDropdownMenu using Jetpack Compose", "href": "https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#ExposedDropdownMenuBox(kotlin.Boolean,kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)", "label": "ExposedDropdownMenu API Reference", "open_new_window": true}], "title": "Jetpack Compose support available"}], "hide_generic_not_available_message": true, "hide_intro_block": true, "image": [{"a11y_description": "Menu hero example", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/da9bc72e7ce22d5466d4932275b9af1a2c798a87b47943fc3c8be1e70e12255833c07378041e1cd278e80620c1076ce4929d72b2b37ccb282d449b6ab54f7020", "height": 754, "url": "https://lh3.googleusercontent.com/Kb7vuTWlHrnwuv-8taEKtQZuTzKta54HxDNsUzN864yfInGGIx333w1VErFA8zlmFEW8hRa9JDXi-K6wUy-ReZQHERNZH4CA273evW3urLrys-KsYQ", "width": 1999}}, {"a11y_description": "Composite image of example dropdown and exposed dropdown menu types", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/13e3ee536262916e9edd4ea67762d836c54d571e515ce1234308e96abcede4082375a2aec11cc9c6b82050663ed69a456182b3ff18cc83ad7da8eb17fd944ab5", "height": 940, "url": "https://lh3.googleusercontent.com/X_D_If_smcA-GPD4A-USkWJgj0UgT9YSx2JmTcxI7dfvJiKU7M08lpFsc8-MNb-ZHuh5jVwpx5b6eeMuzlpNVmt0F6MbSVjvL63oGlzeJLFEjBarQeQ", "width": 2048}}, {"a11y_description": "Composite image: purple \"Menus\" bar, and purple \"Menus\" bar with white menu\nand 3 options", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/d5dadc3e2f0d1b341fb784613fcb87fde6231cc9803588b5b56f06415e5106f66cae8dba24edb84ee928dd94aea9f6f1a78e67b9fefa89bf09a46e1fa969c64c", "height": 650, "url": "https://lh3.googleusercontent.com/gh99kXdWMairaR0nsNpVkgE90_dMb74HMzoPfxFIZwIJo2vkJ3wKL8Nd87QdKXqaA1zFImD3ACa0sUbuqpQ1WYU5QapTEB8JOuO18JZLERWbE_d_n9c", "width": 1080}}, {"a11y_description": "White menu window with two options floating over a white background with grey\ntext", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/c308438bedef64d236cf034176a68e6bd5c4758eeb0a6e9e91c4762dfd5da830ac7f340ada2280d0210d57412bf6b0b1886a4e2f05b65e05598079b083683520", "height": 463, "url": "https://lh3.googleusercontent.com/j4Crs8v0RxV8F4W40NQ78y8IANvZfG4ra-hAXxP3qe5WVYycY91eVpyfEQbNZ9vX11qryv-lEK0GO1DoI6laBsoOkxjBHpDGH_Gy0lWx2gzZBVfrjA", "width": 1080}}, {"a11y_description": "White menu container with 3 options open below a purple \"Show menu\" button", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/3a9ece693f83eb50c0810d14bc629f88293aa830fe94487f17bbe440a9a7a3b59cd558f6819e2fb6e38842aca960d9eb7e53b0da8698c6166cee415efb52b071", "height": 655, "url": "https://lh3.googleusercontent.com/n--GNHXqdi6IAWHN4AqGWHN8hWZ1D2Zp2Vzz4H5Hyn13DCUjQtJYxTg8QEA3KVNSJ_32X4SPuwRJrMHveZwvqwxBaKXptGRO3OwUamedoPtLMiVW3nw", "width": 1080}}, {"a11y_description": "White menu window with 3 options open below a purple \"Show menu\" button. Each\noption has an icon.", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/243744613f0f55c96c0f809d11beb9598662c3525f4942dfe6c5bf5981be692b55bc48d382d1f6814dff115788a6e93392e5870eb6f6df474faa23f49126c677", "height": 746, "url": "https://lh3.googleusercontent.com/DQC8wOdRISEh_rVPd8uCJryHd1SvjzYkKBSiSkCd6-9Zm1Cpwafo5cRWWqh6xfhby-6xgKNazfOnjDRy0zNkBNYtUkUJ8xMNs_05Cr-bV8QsJ96Zhrkb", "width": 1080}}, {"a11y_description": "White menu container with 3 options below a purple \"Show menu\" button", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/7f7449899e6653b6854d950c1ec540034cb2dbfdccbd5f150950d2cd1bfc2d6065595a02bd2d1a855dd2b24f3c12bcfbddcff952dd54e98cc9419e59b4d9ef69", "height": 851, "url": "https://lh3.googleusercontent.com/dUS2p3rCcRYLLAqbINsDJNG6BS35MCimP6RCHjoEPybt7rCmylcw6umvXSxSbZLPM1_Nw-baGHJxQTRGYoHlevsSK2jvvI2Qmf4BMHyRjyiEcfA2krE", "width": 1080}}, {"a11y_description": "Menus anatomy diagrams", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/3fdf3f958e1e19bdd6e730ab63e3bdea088c222eb1f7ccb8eb5dfc50e30aa17285220a071ded0ee7a7b3cc1404cce78a3ce71194f354b02952bac496eecea132", "height": 1045, "url": "https://lh3.googleusercontent.com/laHuXPSZTM4O_vqnZXda7hziGjy9ASSB-iISC1EyQ3d_7SXZNLp0JXMi795PoYeXC0YO1dfEtUcPh2uNAmXFvLUkW9hwxQqERmpKwN7OqF8GM8OfIx3O", "width": 1999}}, {"a11y_description": "2 menu states with text field element: 1) has \"item 1\", 2) has \"item 1\" and a\n4-item menu container.", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/fc19c6475eac357ad6f5095c449c90472faceca82417de3dc23a06ace9264596e4ccacff8d9f6b004cba367cdc43daedf1b1cf6b367962433cb9a2ddd49a96d9", "height": 1088, "url": "https://lh3.googleusercontent.com/wAY6SANH7FSIDjD3unZacLO13lhDGveMKaa0SPRYxXSb2DYIRMlGsZ5_439T_YMs9oCv47QCPxqlSz5avTVR9XfI9Ln_o1OcHxWMuYwyVMl_CgKmiZQ", "width": 1080}}, {"a11y_description": "Menu with purple outlined text field element and 4 items. Item 1 selected.", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/24ebf0ae74d894bdbd3a3866451ee6b4ca89aae5d40a6dff81bc98ca61036daffd5ccc7ae5927fe5170b1bf157955bada93c8a723da4f4c4f1c070f3ae2ed8b5", "height": 806, "url": "https://lh3.googleusercontent.com/50YgYjtYrYt-pt2z0cbeScd46k87_auB_rG8WzXaI-XH6dsdrn1CrI2C6uy9Peff1MxlyO5EEpOU3x2Jvjzb0DJq844Tg34zCimJruqLXIe-jsCQww", "width": 1080}}, {"a11y_description": "Exposed dropdown menu anatomy diagram", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/899b0462ee201be46c303101b746518dcabc60bd3bcf192fc713d74b7f9ac6c47612039c93bbfce86c3c3891ec71d26316d1428db98df0998f3ebc1c1c6442d9", "height": 1070, "url": "https://lh3.googleusercontent.com/-UN5DIcokiLQt7ga1W60Cgn7jOvXipS0dEOoHTIyj_AAs9PvkqbXZZpeHaXHib0jbBoK3nlFNVBRBjQsRCYnqWxkmICedLpAA3EbjvXBYc-34INabOc", "width": 2048}}, {"a11y_description": "White menu container with brown text showing 3 options", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/1764ef3c118e224429b07bf02f93685a5cc7519afda3744d917a3133226e0aeeadef6ca81491b417075e05c2dd39fb499b322f52b6c2edeaa92d1d97e711f32e", "height": 294, "url": "https://lh3.googleusercontent.com/D12ZcYdcTC8C6Opc_SwZbJ5hA5rCeVZo8hkTEtuawp0WZZ8P3bSSpbGWuar3_ehXTGwLI5AE2GVbixeehq3g2CanaIy7SWB5ZWx7awEkmSaD9c38XK4", "width": 400}}, {"a11y_description": "Composite image of \"Show menu\" button and options: gray button with pink\nunderline, and white button with pink\noutline", "bordered": true, "caption": null, "case": null, "file": {"file_path": "_f68a99ffe9144467a917320028f1ce3b_/_293ae6e29e4b420a9648cf846508437a_/2f6fe3ba4e94014212938f293059220eb01ad09b5e8d0493f5f62f19eee576ad0f4b91f1418c3e25ec9e8eae775a23fc9b3ed75bb96463bd1099b05602e46156", "height": 363, "url": "https://lh3.googleusercontent.com/MRNcysCTiR5Z9bsP_I3LkcLnvFTCl5ZIWdQrkjfeMUe4-YuhoI8duuHM7syR5q3m3pWUCi-6HI4Zm2ZSBbznP4A3_9Ylvbx8i4BK4uafeet1ClZat29o", "width": 1000}}], "is_beta": false, "markdown_content": "\n\n# Menus\n\n[Menus](https://material.io/components/menus) display a list of choices on\ntemporary surfaces.\n\n\n\n**Contents**\n\n* [Using menus](#using-menus)\n* [Dropdown menus](#dropdown-menus)\n* [Exposed dropdown menus](#exposed-dropdown-menus)\n* [Theming](#theming-menus)\n\n## Using menus\n\nA menu displays a list of choices on a temporary surface. They appear when users\ninteract with a button, action, or other control.\n\nBefore you can use Material menus, you need to add a dependency to the Material\nComponents for Android library. For more information, go to the\n[Getting started](/libraries/mdc-android/getting-started)\npage.\n\nA typical menu resource looks like this:\n\n```xml\n\n\n```\n\nA typical exposed dropdown menu looks like this:\n\n```xml\n\n\n \n\n\n```\n\nSee the [dropdown menus](#dropdown-menus) and\n[exposed dropdown menus](#exposed-dropdown-menus) sections for detailed usage\ninformation.\n\n### Making menus accessible\n\nMenus are readable by most screen readers, such as TalkBack. Text rendered in\nmenus is automatically provided to accessibility services. Additional content\nlabels are usually unnecessary.\n\nAndroid's exposed dropdown menu component APIs support both label text and\nhelper text, which tell the user what information is requested for a menu. While\noptional, their use is strongly encouraged. For more information about this\ncomponent's accessibility, check out\n[the text field's a11y section](https://www.github.com/material-components/material-components-android/tree/master/docs/components/TextField.md#making-text-fields-accessible).\n\n### Types\n\nMenus allow users to make a selection from multiple options. They are less\nprominent and take up less space than selection controls, such as a set of radio\nbuttons.\n\nThere are two types of menus: 1\\. [Dropdown menus](#dropdown-menus) (overflow,\ncontext, popup, and list popup window menus), 2\\.\n[Exposed dropdown menus](#exposed-dropdown-menus).\n\n\n\n## Dropdown menus\n\nDropdown menus display a list of options, triggered by an icon, button, or\naction. Their placement varies based on the element that opens them.\n\n### Dropdown menu examples\n\nAPI and source code:\n\n* `Menu`\n * [Class definition](https://developer.android.com/reference/android/view/Menu)\n* `MenuInflater`\n * [Class definition](https://developer.android.com/reference/android/view/MenuInflater)\n* `ContextMenu`\n * [Class definition](https://developer.android.com/reference/android/view/ContextMenu)\n* `PopupMenu`\n * [Class definition](https://developer.android.com/reference/android/widget/PopupMenu)\n* `ListPopupWindow`\n * [Class definition](https://developer.android.com/reference/android/widget/ListPopupWindow)\n\n#### Overflow menus\n\nThe following example shows an overflow menu.\n\n\n\nIn code:\n\n```kt\noverride fun onCreateOptionsMenu(menu: Menu): Boolean {\n val inflater: MenuInflater = menuInflater\n inflater.inflate(R.menu.overflow_menu, menu)\n return true\n}\n```\n\nIn `res/menu/overflow_menu.xml`:\n\n```xml\n\n```\n\n#### Context menus\n\nThe following example shows a context menu that appears when a `TextView` is\npressed for a designated amount of time.\n\n\n\nIn code:\n\n```kt\noverride fun onCreate(savedInstanceState: Bundle?) {\n ...\n val contextMenuTextView = view.findViewById(R.id.context_menu_tv)\n // Register context menu for TextView\n registerForContextMenu(contextMenuTextView)\n}\n\noverride fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {\n val contextMenuTextView = v as TextView\n val context = context\n // Add menu items via menu.add\n menu.add(R.string.option_1)\n .setOnMenuItemClickListener { item: MenuItem? ->\n // Respond to item click.\n }\n menu.add(R.string.option_2)\n .setOnMenuItemClickListener { item: MenuItem? ->\n // Respond to item click.\n }\n}\n\noverride fun onContextMenuClosed(menu: Menu) {\n // Respond to context menu being closed.\n}\n```\n\nIn the layout:\n\n```xml\n\n```\n\nAlternatively, you can inflate a context menu in `onCreateContextMenu` (as with\nthe overflow menu):\n\n```kt\noverride fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {\n super.onCreateContextMenu(menu, v, menuInfo)\n val inflater: MenuInflater = menuInflater\n inflater.inflate(R.menu.context_menu, menu)\n}\n\n// Then, to handle clicks:\noverride fun onContextItemSelected(item: MenuItem): Boolean {\n val info = item.menuInfo as AdapterView.AdapterContextMenuInfo\n return when (item.itemId) {\n R.id.option_1 -> {\n // Respond to context menu item 1 click.\n true\n }\n R.id.option_2 -> {\n // Respond to context menu item 2 click.\n true\n }\n else -> super.onContextItemSelected(item)\n }\n}\n```\n\nwith a `res/menu/context_menu.xml`:\n\n```xml\n\n```\n\n#### Popup menus\n\nThe following example shows a popup menu that displays when a button is clicked.\n\n\n\nIn code:\n\n```kt\noverride fun onCreate(savedInstanceState: Bundle?) {\n ...\n val button = view.findViewById