Skip to content

fix(a11y): announce empty mail and search states without wrapping actions - #1576

Open
seonghobae wants to merge 22 commits into
developfrom
palette/dynamic-empty-state-a11y-5903985985804935687
Open

fix(a11y): announce empty mail and search states without wrapping actions#1576
seonghobae wants to merge 22 commits into
developfrom
palette/dynamic-empty-state-a11y-5903985985804935687

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Current authority

  • protected base: develop@042b0c70531b229af3acbd0421a2f23098d848b3
  • branch: palette/dynamic-empty-state-a11y-5903985985804935687
  • exact head: 58df9fb7b7d09688c4dc13c8ce2a312bfa6679ad
  • exact tree: 030675b6f42ebf8493284727d989bee0b944f080
  • lifecycle: Ready for independent review / mergeable / not merge-ready
  • effective delta: six files — EmailList.tsx, EmailList.test.tsx, EmailList.request-ordering.test.tsx, SearchLayout.tsx, SearchLayout.live-region.test.tsx, frontend/tests/e2e/mail-search-accessibility.spec.ts

Ready is review admission only. No predecessor check or review evidence is transferred to this new commit SHA.

Product contracts retained

The effective tree preserves the verified causal repairs developed earlier in this PR:

  • initial empty inbox uses inbox copy (받은 메일이 없습니다), while an actually submitted empty search uses 맥락 검색 결과가 없습니다; editing a query alone does not change result-set authority and clearing returns to inbox authority;
  • every mail/search load receives a monotonically increasing request sequence, and only the latest sequence may commit rows/error/completion state, preventing a late stale search response from overwriting a newer inbox refresh;
  • SearchLayout empty results and empty Sender DAG relationships use polite status semantics, the relationship-capture action stays outside the status subtree, and capture failure is an alert;
  • focused component regressions cover inbox/search empty-state semantics, stale-response ordering, Sender DAG action/status separation, and capture failure;
  • the Playwright accessibility spec exercises those contracts through the product routes, with the search-detail heading locator narrowed to level 2 to avoid false strictness failures.

This browser semantics contract does not by itself prove what VoiceOver/NVDA/JAWS spoke. Do not claim screen-reader speech verification without actual AT evidence.

Concurrent child audit and non-force repair

After 270acbc60b637e42ac9aaa9a5f0e8a987e3ea9fd had completed repository-owned CI, direct child 33f1e14555663dcc9a6c1a1510a280bc33a201bf arrived normally. It was read and preserved in ancestry rather than treated as a race.

That child removed the dedicated request-ordering regression and browser E2E, regressed the initial inbox assertion back to search-empty copy, duplicated the SearchLayout empty-state test into the broad product-events suite, removed the request-sequence/source-state repair, and reintroduced repository-root helper patch_email_list_test.js. Those changes discarded already-validated buyer-visible behavior and test authority; they were not a new independent product requirement.

Ordinary child 58df9fb7b7d09688c4dc13c8ce2a312bfa6679ad therefore adopts the exact reviewed tree from 270acbc... while keeping 33f1e145... in ancestry. The branch ref was advanced with force=false; there was no destructive rebase or force-push.

Evidence boundary

The exact tree now equals the previously exercised 270acbc... tree, but exact-head governance is SHA-bound. The following are predecessor evidence only and are not claimed as current-head GREEN for 58df9fb...:

  • 270acbc... Application CI 34022935311 succeeded, including frontend tests/lint/build, Playwright Chromium and Run full product smoke, plus backend Python 3.14 lint/tests;
  • Security Scan 34022935317, Semgrep 34022935287, Bandit 34022935385, and Docker 34022935414 succeeded;
  • CodeQL PR 34022935340 failed in the canonical central dispatch/verdict path;
  • CodeRabbit completed an incremental review through 270acbc... with no actionable comments on the current six-file delta, but that review is not transferred to 58df9fb...;
  • the historical formal CHANGES_REQUESTED review is on old a0c544b...; its only inline thread was resolved after the scope was narrowed.

A fresh CodeRabbit request after Ready hit the included-review limit while trying to process the now-superseded 33f1e145...; do not manufacture a source commit to bypass that external review-capacity limit. Current-head OpenCode/CodeRabbit evidence may arrive through normal governance without changing the product tree.

Merge boundary

Do not merge until the unchanged 58df9fb... head has all applicable required checks terminal-success, no valid unresolved current-head finding, and qualifying current-head robot-review evidence under docs/development/merge-gate-policy.md. CodeQL remains a canonical .github owner-path blocker; do not widen authorization or add a Naruon-local bypass. Actual assistive-technology speech evidence remains required before making any stronger screen-reader-announcement claim.

The product Gap ledger remains owned by #1557; this PR does not edit docs/product-technical-gap-baseline.md in parallel. Protected AGENTS/CLAUDE routing-language drift remains tracked by #1548.

UI Delivery Gate: FAIL (current-head evidence incomplete). The intended source/test/browser contract is restored, but the new exact head still needs its own hosted gate and robot-review evidence. No self-approval, force-push, destructive rebase, dummy/no-op requeue, predecessor-evidence transfer, gate weakening, or unsupported accessibility/performance claim is used.

…서 빈 상태(Empty State)를 표시하는 요소에 role="status"와 aria-live="polite" 속성을 추가하여 스크린 리더 접근성을 개선했습니다.
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: a26c05b7-56de-497d-b2eb-7f3ef1a9c9dc

📥 Commits

Reviewing files that changed from the base of the PR and between a0c544b and 270acbc.

📒 Files selected for processing (5)
  • frontend/src/components/EmailList.request-ordering.test.tsx
  • frontend/src/components/EmailList.test.tsx
  • frontend/src/components/EmailList.tsx
  • frontend/src/components/SearchLayout.live-region.test.tsx
  • frontend/tests/e2e/mail-search-accessibility.spec.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

EmailList now separates inbox and search empty states and ignores stale responses. SearchLayout adds live-region semantics for empty results, empty relationships, and capture failures. Unit and Playwright tests validate these behaviors.

Changes

Email search state and ordering

Layer / File(s) Summary
EmailList state and request ordering
frontend/src/components/EmailList.tsx, frontend/src/components/EmailList.test.tsx, frontend/src/components/EmailList.request-ordering.test.tsx
EmailList tracks request order, normalizes queries, selects inbox or search empty-state copy, and ignores superseded responses. Tests cover loading, clearing, empty results, and stale responses.

SearchLayout accessibility semantics

Layer / File(s) Summary
SearchLayout live-region behavior
frontend/src/components/SearchLayout.tsx, frontend/src/components/SearchLayout.live-region.test.tsx
Empty relationships and search results use polite status regions. Capture failures use alerts. Component tests validate the roles, live attributes, and control boundaries.

End-to-end accessibility validation

Layer / File(s) Summary
Mail search accessibility validation
frontend/tests/e2e/mail-search-accessibility.spec.ts
Playwright tests validate separate inbox and search statuses, polite announcements, non-interactive status content, and capture failure alerts.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 270ac

This change distinguishes inbox and search empty states, prevents stale search responses from replacing newer inbox data, and improves screen-reader announcements for empty and error states. No current merge-blocking risk is evidenced.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main accessibility changes: announcing empty mail and search states while keeping actions outside the live region.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch palette/dynamic-empty-state-a11y-5903985985804935687

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae
seonghobae marked this pull request as draft September 6, 2026 02:41
@seonghobae seonghobae changed the title 🎨 Palette: 동적 빈 상태(Empty State) 스크린 리더 접근성 개선 fix(a11y): announce dynamic empty states without wrapping actions Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact head a0c544b01c9438ec55de3ce5f7c1522c007dc59b, with particular attention to live-region semantics, interactive descendants of status regions, alert urgency, repeated announcements across simultaneous empty states, keyboard/focus behavior, and the focused SearchLayout.live-region.test.tsx regression. Do not treat predecessor-head evidence as current.

@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Checkout identity =='
git rev-parse HEAD
git status --short
git show -s --format='commit=%H%nparents=%P%nsubject=%s' HEAD

echo '== Effective diff paths against protected base =='
git diff --name-status 042b0c70531b229af3acbd0421a2f23098d848b3..HEAD

echo '== Effective diff =='
git diff --unified=80 042b0c70531b229af3acbd0421a2f23098d848b3..HEAD -- \
  '*EmailList.tsx' '*SearchLayout.tsx' '*TasksLayout.tsx' '*DocumentRepositoryTab.tsx' '*SearchLayout.live-region.test.tsx'

echo '== Candidate files =='
fd -t f -a '^(EmailList|SearchLayout|TasksLayout|DocumentRepositoryTab)(\.live-region\.test)?\.tsx$' . \
  -E node_modules -E dist -E build

echo '== Component and test outlines =='
for f in $(fd -t f -a '^(EmailList|SearchLayout|TasksLayout|DocumentRepositoryTab)(\.live-region\.test)?\.tsx$' . -E node_modules -E dist -E build); do
  echo "--- $f"
  ast-grep outline "$f" --items all || true
done

echo '== Live roles, focus APIs, and interactive controls in changed components =='
rg -n -C 8 --glob '*EmailList.tsx' --glob '*SearchLayout.tsx' --glob '*TasksLayout.tsx' --glob '*DocumentRepositoryTab.tsx' \
  'role=(["'\''](?:status|alert)|\{)|aria-live=|aria-atomic=|autoFocus|\.focus\s*\(|<button\b|<Button\b|<a\b|<input\b|<select\b|<textarea\b' . \
  -g '!node_modules/**' -g '!dist/**' -g '!build/**'

echo '== Focused test =='
test_file=$(fd -t f -a '^SearchLayout\.live-region\.test\.tsx$' . -E node_modules -E dist -E build | head -n 1 || true)
if [ -n "$test_file" ]; then
  cat -n "$test_file"
fi

echo '== Test runner references =='
rg -n -C 3 --glob 'package.json' --glob 'vitest.config.*' --glob 'vite.config.*' --glob 'jest.config.*' \
  'vitest|jest|test' . -g '!node_modules/**' -g '!dist/**' -g '!build/**'

Length of output: 173402


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Tasks state lifecycle and changed render branches =='
sed -n '160,290p' frontend/src/components/TasksLayout.tsx
sed -n '300,410p' frontend/src/components/TasksLayout.tsx
sed -n '480,705p' frontend/src/components/TasksLayout.tsx

echo '== Document repository changed render branches =='
sed -n '70,125p' frontend/src/components/data-layout/DocumentRepositoryTab.tsx
sed -n '270,315p' frontend/src/components/data-layout/DocumentRepositoryTab.tsx
sed -n '565,615p' frontend/src/components/data-layout/DocumentRepositoryTab.tsx

echo '== Search lifecycle and Sender DAG state derivation =='
sed -n '340,610p' frontend/src/components/SearchLayout.tsx
sed -n '780,970p' frontend/src/components/SearchLayout.tsx

echo '== Related accessibility and component tests =='
rg -n -C 3 --glob '*.{test,spec}.{ts,tsx}' \
  'role=.status.|aria-live|role=.alert.|SearchLayout|TasksLayout|DocumentRepositoryTab|EmailList' \
  frontend/src -g '!node_modules/**'

echo '== Test setup =='
cat -n frontend/src/test/setup.ts
cat -n frontend/vitest.config.ts

echo '== Current PR metadata, reviews, and checks =='
gh pr view 1576 --repo ContextualWisdomLab/naruon \
  --json headRefOid,isDraft,mergeStateStatus,reviewDecision,reviews,statusCheckRollup,url

Length of output: 84547


@seonghobae I reviewed exact head a0c544b01c9438ec55de3ce5f7c1522c007dc59b.

I found two accessibility issues.

  1. TasksLayout.tsx can announce four empty states together.
    At Line 355, each empty Kanban column creates its own polite status region. The default Kanban view renders all four columns. When /api/tasks returns no tasks, or a filter excludes all tasks, assistive technology can receive four separate “no tasks” announcements. Keep the visible per-column messages, but expose one consolidated polite status for the board state.

  2. DocumentRepositoryTab.tsx can announce three empty states together.
    At Lines 292, 588, and 605, the asset, project-folder, and connector-event empty states each create a polite status region. These collections can be empty at the same time. This can produce three ungrouped announcements during one data transition. Use one consolidated status for the repository surface, or announce only the state that changed.

The Sender DAG repair is correct in source. The empty relationship message is a standalone polite status. The capture button is outside that status. The capture failure uses role="alert".

SearchLayout.live-region.test.tsx covers those two Sender DAG requirements. It does not cover simultaneous empty states or keyboard focus. The passive additions do not move focus in source. Existing task tab focus handling remains unchanged.

I could not execute frontend tests in this environment. The exact-head hosted checks are queued, and browser or assistive-technology evidence is still incomplete. Keep #1576 as Draft until those checks complete and the focused behavior is verified on this exact head.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]

This comment was marked as resolved.

@google-labs-jules

Copy link
Copy Markdown
Contributor

@coderabbitai review

Please review exact head a0c544b01c9438ec55de3ce5f7c1522c007dc59b, with particular attention to live-region semantics, interactive descendants of status regions, alert urgency, repeated announcements across simultaneous empty states, keyboard/focus behavior, and the focused SearchLayout.live-region.test.tsx regression. Do not treat predecessor-head evidence as current.

Acknowledged. However, there is no command directed to me in this comment, and it seems to be an automated trigger for another bot (@coderabbitai). No action required from me.

@seonghobae seonghobae changed the title fix(a11y): announce dynamic empty states without wrapping actions fix(a11y): announce empty mail and search states without wrapping actions Sep 6, 2026
@seonghobae
seonghobae marked this pull request as ready for review September 6, 2026 13:11

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review the unchanged exact head 270acbc60b637e42ac9aaa9a5f0e8a987e3ea9fd. Focus on the six-file effective delta: inbox/search empty-state authority, stale async response ordering, live-region/action separation, alert semantics, duplicate announcements, keyboard/focus regressions, and whether the new Playwright accessibility contract actually exercises the intended browser behavior. The historical requested-changes review was on a0c544b...; do not transfer it as current-head evidence. CodeQL remains a separate central control-plane blocker and is not a reason to weaken or churn this product head.

@google-labs-jules

Copy link
Copy Markdown
Contributor

@coderabbitai review

Please review the unchanged exact head 270acbc60b637e42ac9aaa9a5f0e8a987e3ea9fd. Focus on the six-file effective delta: inbox/search empty-state authority, stale async response ordering, live-region/action separation, alert semantics, duplicate announcements, keyboard/focus regressions, and whether the new Playwright accessibility contract actually exercises the intended browser behavior. The historical requested-changes review was on a0c544b...; do not transfer it as current-head evidence. CodeQL remains a separate central control-plane blocker and is not a reason to weaken or churn this product head.

Acknowledged. However, there is no command directed to me in this comment, and it seems to be an automated trigger for another bot (@coderabbitai). No action required from me.

seonghobae and others added 2 commits September 6, 2026 13:15
…서 빈 상태(Empty State)를 표시하는 요소에 role="status"와 aria-live="polite" 속성을 추가하여 스크린 리더 접근성을 개선했습니다.
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

expected_head='270acbc60b637e42ac9aaa9a5f0e8a987e3ea9fd'
actual_head="$(git rev-parse HEAD)"
printf 'HEAD=%s\n' "$actual_head"
if [ "$actual_head" != "$expected_head" ]; then
  echo "ERROR: checkout does not match requested exact head" >&2
  exit 2
fi

echo '== Commit identity =='
git show -s --format='commit=%H%nparents=%P%nsubject=%s' HEAD
git status --short

echo '== Merge-base and effective changed files =='
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD origin/master 2>/dev/null || true)"
printf 'base=%s\n' "$base"
if [ -n "$base" ]; then
  git diff --name-status "$base"..HEAD
  echo '== Effective diff: requested surfaces =='
  git diff --unified=60 "$base"..HEAD -- \
    'frontend/src/components/EmailList.tsx' \
    'frontend/src/components/SearchLayout.tsx' \
    'frontend/src/components/EmailList.test.tsx' \
    'frontend/src/components/EmailList.request-ordering.test.tsx' \
    'frontend/src/components/SearchLayout.live-region.test.tsx' \
    'frontend/tests/e2e/mail-search-accessibility.spec.ts'
fi

echo '== Candidate file existence =='
for f in \
  frontend/src/components/EmailList.tsx \
  frontend/src/components/SearchLayout.tsx \
  frontend/src/components/EmailList.test.tsx \
  frontend/src/components/EmailList.request-ordering.test.tsx \
  frontend/src/components/SearchLayout.live-region.test.tsx \
  frontend/tests/e2e/mail-search-accessibility.spec.ts \
  frontend/src/components/TasksLayout.tsx \
  frontend/src/components/data-layout/DocumentRepositoryTab.tsx; do
  test -f "$f" && echo "$f"
done

echo '== Component outlines =='
ast-grep outline frontend/src/components/EmailList.tsx --items all
ast-grep outline frontend/src/components/SearchLayout.tsx --items all

echo '== EmailList: state, effects, fetch and empty-state render =='
rg -n -C 14 --glob 'EmailList.tsx' \
  'request|fetchEmails|emptyStateMode|isSearching|useEffect|role=|aria-live|emptyCopy|search' \
  frontend/src/components

echo '== SearchLayout: status, alert, buttons, focus APIs and SenderDagPanel =='
rg -n -C 12 --glob 'SearchLayout.tsx' \
  'SenderDagPanel|role=|aria-live|aria-atomic|capture|relationships|\.focus\s*\(|autoFocus|<button\b|<Button\b|<input\b|<a\b' \
  frontend/src/components

echo '== Tasks and document duplicate-status surfaces at this head =='
rg -n -C 8 --glob 'TasksLayout.tsx' --glob 'DocumentRepositoryTab.tsx' \
  'role=|aria-live|status|empty|없습니다' \
  frontend/src/components

echo '== Focused unit tests =='
cat -n frontend/src/components/EmailList.test.tsx
cat -n frontend/src/components/EmailList.request-ordering.test.tsx
cat -n frontend/src/components/SearchLayout.live-region.test.tsx

echo '== Playwright contract and configuration references =='
cat -n frontend/tests/e2e/mail-search-accessibility.spec.ts
rg -n -C 5 --glob 'playwright.config.*' --glob 'package.json' --glob '*e2e*' \
  'testDir|projects|desktop|baseURL|webServer|mail-search-accessibility|playwright' \
  frontend

echo '== Hosted exact-head metadata and checks =='
gh pr view 1576 --repo ContextualWisdomLab/naruon \
  --json headRefOid,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup,url

Length of output: 3586


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='ContextualWisdomLab/naruon'
head='270acbc60b637e42ac9aaa9a5f0e8a987e3ea9fd'

echo '== Local requested-object availability =='
if git cat-file -e "${head}^{commit}" 2>/dev/null; then
  echo 'local_commit=yes'
  git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$head"

  echo '== Local effective base candidates =='
  for ref in origin/main origin/master; do
    if git show-ref --verify --quiet "refs/remotes/$ref"; then
      base="$(git merge-base "$head" "$ref")"
      printf '%s=%s\n' "$ref" "$base"
      git diff --name-status "$base" "$head"
    fi
  done

  echo '== Exact-head files and diff =='
  git show --name-status --format='' "$head"
  git show --unified=45 "$head" -- \
    frontend/src/components/EmailList.tsx \
    frontend/src/components/SearchLayout.tsx \
    frontend/src/components/EmailList.test.tsx \
    frontend/src/components/EmailList.request-ordering.test.tsx \
    frontend/src/components/SearchLayout.live-region.test.tsx \
    frontend/tests/e2e/mail-search-accessibility.spec.ts

  echo '== Exact-head live status surfaces outside effective delta =='
  for f in frontend/src/components/TasksLayout.tsx frontend/src/components/data-layout/DocumentRepositoryTab.tsx; do
    echo "--- $f"
    git show "${head}:${f}" 2>/dev/null | nl -ba | grep -E -C 8 'role=|aria-live|status|empty|없습니다' || true
  done
else
  echo 'local_commit=no'
fi

echo '== GitHub requested commit =='
gh api "repos/${repo}/commits/${head}" \
  --jq '{sha,parents:[.parents[].sha],message:.commit.message,files:[.files[]|{filename,status,additions,deletions,patch}]}'

echo '== Pull request metadata and exact-head checks =='
gh pr view 1576 --repo "$repo" \
  --json headRefOid,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup,url

Length of output: 14848


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='ContextualWisdomLab/naruon'
head='270acbc60b637e42ac9aaa9a5f0e8a987e3ea9fd'

echo '== Requested-head ancestry =='
git log --format='%H %P %s' --max-count=35 "$head"

echo '== Requested-head tree: six review files =='
for f in \
  frontend/src/components/EmailList.tsx \
  frontend/src/components/SearchLayout.tsx \
  frontend/src/components/EmailList.test.tsx \
  frontend/src/components/EmailList.request-ordering.test.tsx \
  frontend/src/components/SearchLayout.live-region.test.tsx \
  frontend/tests/e2e/mail-search-accessibility.spec.ts; do
  echo "--- $f"
  git show "${head}:${f}" | cat -n
done

echo '== Requested-head tree: simultaneous-empty and focus surfaces =='
for f in \
  frontend/src/components/TasksLayout.tsx \
  frontend/src/components/data-layout/DocumentRepositoryTab.tsx; do
  echo "--- $f"
  git show "${head}:${f}" | grep -n -E -C 10 \
    'role=|aria-live|aria-atomic|\.focus\s*\(|autoFocus|onKeyDown|empty|없습니다' || true
done

echo '== GitHub compare requested head against default branch =='
default_branch="$(gh api "repos/${repo}" --jq '.default_branch')"
printf 'default_branch=%s\n' "$default_branch"
gh api "repos/${repo}/compare/${default_branch}...${head}" \
  --jq '{status,ahead_by,behind_by,merge_base_commit:.merge_base_commit.sha,files:[.files[]|{filename,status,additions,deletions,patch}]}'

echo '== Relationship between requested commit and current PR head =='
current_head="$(gh pr view 1576 --repo "$repo" --json headRefOid --jq '.headRefOid')"
printf 'pr_head=%s\n' "$current_head"
gh api "repos/${repo}/compare/${head}...${current_head}" \
  --jq '{status,ahead_by,behind_by,merge_base_commit:.merge_base_commit.sha}'

Length of output: 157163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='270acbc60b637e42ac9aaa9a5f0e8a987e3ea9fd'
current='58df9fb7b7d09688c4dc13c8ce2a312bfa6679ad'

echo '== Exact-head E2E helper candidates =='
fd -t f -a '^(helpers|playwright\.config|package)\.(ts|tsx|js|json)$' frontend
for f in $(fd -t f -a '^(helpers|playwright\.config|package)\.(ts|tsx|js|json)$' frontend); do
  echo "--- $f"
  git show "${head}:${f}" 2>/dev/null | cat -n || true
done

echo '== Exact-head browser-route and desktop workspace references =='
git grep -n -E \
  'mockDashboardApi|데스크톱 메일 작업공간|메일함 바로가기|function.*Desktop|Desktop.*Mail|EmailList' \
  "$head" -- frontend/tests/e2e frontend/src 2>/dev/null || true

echo '== Exact-head accessibility-test registration =='
git grep -n -E \
  'mail-search-accessibility|testDir|projects:|name: .desktop.|desktop' \
  "$head" -- frontend 2>/dev/null || true

echo '== Only commits after the requested head =='
git log --format='%H %P %s' "${head}..${current}"
git diff --name-status "$head..$current"
git diff --unified=20 "$head..$current" -- frontend/tests/e2e/mail-search-accessibility.spec.ts

Length of output: 118488


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='270acbc60b637e42ac9aaa9a5f0e8a987e3ea9fd'

echo '== Playwright configuration at requested head =='
git show "${head}:frontend/playwright.config.ts" | cat -n

echo '== mockDashboardApi implementation at requested head =='
git show "${head}:frontend/tests/e2e/helpers.ts" | sed -n '780,940p' | cat -n

echo '== Helper search and ontology mock branches =='
git show "${head}:frontend/tests/e2e/helpers.ts" \
  | grep -n -E -C 12 \
    "api/search|ontology/relationships|Q2 출시 계획|launch|route\(" || true

echo '== Desktop workspace render behavior at requested head =='
git show "${head}:frontend/src/components/WorkspaceHome.tsx" \
  | sed -n '740,885p' | cat -n

echo '== Current-head comparison identity only =='
git diff --name-status "$head..58df9fb7b7d09688c4dc13c8ce2a312bfa6679ad"

Length of output: 21832


@seonghobae I reviewed the six-file effective delta at 270acbc60b637e42ac9aaa9a5f0e8a987e3ea9fd.

I found no blocking issues in the requested scope.

  • EmailList uses one request sequence for each fetch. Older search responses cannot replace newer inbox results. The regression test covers the clear-search race.
  • Inbox-empty and search-empty copy derives from the active request mode. The copy changes only when a search is submitted or cleared.
  • Empty-state status regions contain no interactive descendants.
  • SenderDagPanel keeps the empty relationship status separate from the capture button.
  • Capture failure uses role="alert".
  • The reviewed TasksLayout and DocumentRepositoryTab empty messages are not polite live regions at this head. The earlier repeated-announcement finding does not apply.
  • The new changes do not add focus movement. Existing clear controls retain focus on their search input. Existing task-tab keyboard focus behavior is unchanged.
  • The Playwright tests run on the desktop project. They exercise the mail empty-state transition, the search empty state, the Sender DAG empty state, and capture failure. The route handlers provide deterministic data for these flows.

The Playwright tests validate browser-rendered roles, aria-live attributes, interactive-descendant separation, and alert exposure. They do not validate speech output from a screen reader. Assistive-technology validation remains separate evidence.

The current #1576 head is 58df9fb7b7d09688c4dc13c8ce2a312bfa6679ad, not the requested SHA. It is two commits later, but its tree has no diff from the requested commit. I did not use the historical review on a0c544b... as current-head evidence. I also did not treat CodeQL as a reason to change this product review result.

You are interacting with an AI system.

@seonghobae seonghobae added bug Something isn't working priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: bug Defect or incorrect behavior labels Sep 7, 2026 — with ChatGPT Codex Connector
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: bug Defect or incorrect behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant