Skip to content

MEMBERS-DIRECTORY-001M — Restore surviving photo-control focus after rejected removal #643

Closed

Description

@daliu

Parent: #504. Connected release remains owned by #507.

Officer impact: In the preserved future My Account interface, a keyboard or screen-reader user whose focused Remove action disappears after a rejected removal and authoritative profile refresh will be returned to the closest surviving photo action. The connected interface remains unavailable on production.

Officer documentation: Update SYSTEM_DESIGN.md, IMPLEMENTATION_PLAN.md, SECURITY.md, and only the optional-directory one-sentence text alternative plus one no-terminal source-evidence procedure in docs/officers/EVENTS_SHOP_MEMBERS.md. No Mermaid change is needed because page topology, permissions, and data movement do not change.

Deployment evidence: None at issue creation. This is source-only accessibility hardening. memberDirectoryAvailability.ts must remain byte-for-byte false; no website, Firebase, provider, account, sign-in, production-data, deployment, or connected-live action is authorized.

Atomic outcome

Restore keyboard focus to the correct surviving photo control after a focused Remove current saved photo receives a definitive rejection and a successful authoritative profile readback.

Defect

On exact released main f3da2f3, a Remove mutation disables the focused Remove action while pending. A definitive rejection then performs an authoritative profile read. If that current read says hasPhoto:false, Remove disappears. Unlike confirmed removal, the definitive-rejection path records no ready-state focus handoff. Browser-disabled or removed-control focus can therefore remain on the document body.

The existing rejection test returns a profile that still has a saved photo and does not assert focus, so it does not exercise the missing-control branch.

Invariants and transitions

  • Create a rejection-focus intent only after request-ID creation and startMutation('remove') admit the exact current operation into pending.
  • Create it only when Remove current saved photo owns focus at that point.
  • Store only the current mounted application/account lifetime and exact mutation-operation identity. Store no photo bytes, profile value, request ID, revision, provider value, or error.
  • Programmatic or outside-focused Remove invocation, request-ID failure, and rejected mutation start create no intent.
  • After definitive Remove rejection and successful current authoritative readback, consume the matching intent after ready controls render.
  • Choose the current rendered destination: surviving Remove; otherwise current enabled Save profile photo when the exact local draft is ready; otherwise the persistent Add/Replace file input, including no draft, reading, or not-yet-ready draft.
  • If the target already owns focus, leave it alone. Restore only from body, the document root element, absent, or disconnected focus. Preserve any other connected focus deliberately selected during pending.
  • Keep the fixed rejection role=alert. If Remove survives, retain its error description. If Remove disappears, leave the alert standalone; do not associate it with Save or the file input.
  • Clear/fence the intent on confirmed success, unknown outcome, failed confirming read, application change, UID change, unmount, or obsolete operation.
  • Preserve confirmed-success focus behavior, MEMBERS-DIRECTORY-001J — Preserve profile-recovery keyboard focus #637 Reload recovery focus, local draft identity/bytes/render state/revision rules, and all mutation/read/render/context fences.
  • Focus creates no request ID, callable, retry, automatic mutation, draft, data URL, confirmation, audit, or provider/data action.
  • Availability stays false; the default branch remains inert and zero-context/zero-ID/zero-call.
  • No photo query, facial recognition, matching, embedding, similarity, biometric processing, roster authority, or membership proof is added.

Exact ownership

  • src/pages/account/MemberDirectoryProfile.tsx: only the exact-operation focused-Remove rejection intent, cleanup guards, and guarded current-state focus handoff.
  • src/pages/account/MemberDirectoryProfile.test.tsx: one separately named MEMBERS-DIRECTORY-001M block plus minimum directly affected helpers/assertions.
  • SYSTEM_DESIGN.md: one separately named 001M source-only paragraph.
  • IMPLEMENTATION_PLAN.md: one separately named 001M current-source paragraph.
  • SECURITY.md: one separately named 001M/RISK-042 row immediately after released 001L; preserve active OAUTH-001A2L — Bound Strava activity/statistics JSON and cancel the unread statistics body #616's future OAUTH-001A2L/RISK-024 hunk.
  • docs/officers/EVENTS_SHOP_MEMBERS.md: only the optional-directory one-sentence text alternative and one no-terminal 001M source-evidence procedure; no Mermaid or unrelated procedure change.

No CSS change: the same native controls and existing focus styles are used.

Trustworthy RED and required tests

  • On unchanged runtime, focus Remove, begin a deferred valid removal, assert disabled, explicitly blur to model native focus eviction, reject definitively, resolve confirming read with hasPhoto:false, and prove focus remains off the surviving file input.
  • Repeat with a current ready replacement and prove old source does not focus Save profile photo.
  • Corrected behavior restores surviving Remove, ready Save, or file input for no/reading/not-ready draft.
  • Surviving Remove retains aria-describedby to the fixed error; vanished Remove leaves a standalone alert and does not attach it to Save/input.
  • Programmatic/outside-focused invocation creates no intent; deliberate outside focus during pending is preserved; already-retained Remove is not redundantly focused.
  • Request-ID failure remains enabled/focused with zero removal call.
  • Failed confirming read retains existing unavailable/Reload focus; ordinary unknown outcome retains existing uncertain/Reload focus; confirmed Remove behavior remains unchanged.
  • Application change, UID change, unmount, and stale operation completion are focus-inert.
  • No extra request ID, remove/upload/visibility call, retry, confirmation, draft, or data URL.
  • Default preview remains zero-context/zero-ID/zero-call.
  • Existing profile, draft, rejection, uncertainty, recovery-focus, response-boundary, accessibility, privacy, race, and mobile suites remain green.

Explicit exclusions

No Account.tsx/test, CSS, service adapter, availability, inbound WebP validation, admin finder, AdminGuard, routes, backend Function/Rule/index/schema/repair/package/lock/workflow/release control, privacy/capability policy, Firebase/provider configuration, deployment, account/sign-in, production data, real name/photo, connected/live action, or #616 path/hunk.

Migration and residual risk

No migration. This focus handoff is accessibility state, not server acknowledgement, deletion proof, reconciliation, authorization, provider evidence, or proof that removal succeeded. A definitive rejection and a concurrent authoritative state with no photo may both be true; the rejection alert therefore remains separate from the current surviving action.

Availability stays false and live #623 remains inert. #507 retains every connection and release gate.

Claim protocol

Assign the issue and post a timestamped CLAIMED marker naming the exact branch and released base before any edit. The claim remains active through reviewed merge, exact-main/deployment audit, closure, and explicit release.

Activity

  1. added
    bugSomething isn't working
    priority:P1High-priority follow-up
    type:securitySecurity or privacy boundary
    area:membershipMembership lifecycle and reconciliation
    area:webWeb application and hosting
    size:SSmall focused issue
    status:readyDependencies complete and ready to claim
    type:testingTest infrastructure and quality gates
    area:privacyPersonal data, consent, minimization, retention, and privacy operations
    status:in-progressClaimed and actively being implemented
    and removed
    status:readyDependencies complete and ready to claim
    on Aug 14, 2026
  2. self-assigned this
    on Aug 14, 2026
  3. daliu commented on Aug 14, 2026

    @daliu
    ContributorAuthor

    CLAIMED by /root at 2026-08-14T00:40:00Z on branch codex/issue-643-remove-rejection-focus, exact released base f3da2f351513f947b96d2cb9b883840d55919da9.

    Exact ownership:

    • src/pages/account/MemberDirectoryProfile.tsx: only the exact-operation focused-Remove definitive-rejection intent, current-state no-steal guards, cleanup, and post-render handoff.
    • src/pages/account/MemberDirectoryProfile.test.tsx: one separately named MEMBERS-DIRECTORY-001M block plus minimum directly affected helpers/assertions.
    • one separately named 001M hunk each in SYSTEM_DESIGN.md and IMPLEMENTATION_PLAN.md;
    • one separate 001M/RISK-042 row immediately after released 001L in SECURITY.md;
    • only the optional-directory one-sentence text alternative and one no-terminal 001M procedure in docs/officers/EVENTS_SHOP_MEMBERS.md; no CSS or Mermaid.

    The focus intent exists only for a focused Remove mutation admitted into pending and is bound to that operation/lifetime. After definitive rejection plus current successful readback it restores only from body/root/absent/disconnected focus to surviving Remove, otherwise current ready Save, otherwise the persistent file input; any deliberately chosen connected focus is preserved. If Remove disappears, its fixed rejection remains a standalone alert and is not falsely attached to Save/input. Confirmed-success focus and #637 Reload recovery stay unchanged.

    Everything else is immutable: Account wiring/tests, CSS, services, availability, admin finder, backend/Rules/indexes/packages/workflows/release controls, #507 gates, Firebase/provider/account/sign-in/data/deploy/live work, and both #616 Strava files plus its separate OAUTH/RISK-024 SECURITY hunk. No source edit preceded this marker. Claim remains active through reviewed merge, exact-main/deployment audit, closure, and explicit release.

  4. daliu commented on Aug 14, 2026

    @daliu
    ContributorAuthor

    CLAIM TIMESTAMP CORRECTION — GitHub created the claim marker at authoritative server time 2026-08-14T00:39:35Z. The body's 00:40:00Z was a transcription error and must not be used as the claim time. Exact branch/base/six-path ownership, invariants, exclusions, and claim lifecycle are unchanged. No source edit has occurred; the worktree remains clean. This correction is part of the pre-edit claim record.

  5. daliu commented on Aug 14, 2026

    @daliu
    ContributorAuthor

    #643 candidate evidence — 2026-08-14T01:03:16Z

    Exact released base and current remote main: f3da2f351513f947b96d2cb9b883840d55919da9 (tree 0a2deecc78c1c1645c50db8a1b79f991df41f580).

    The reviewed working diff is exactly the six claimed paths:

    • src/pages/account/MemberDirectoryProfile.tsx
    • src/pages/account/MemberDirectoryProfile.test.tsx
    • SYSTEM_DESIGN.md
    • IMPLEMENTATION_PLAN.md
    • SECURITY.md
    • docs/officers/EVENTS_SHOP_MEMBERS.md

    Exact binary diff SHA-256: 0497c75100eddc38b7b3b38d7fd8403cb60d01b7bbad9a50bbb79472ec3d28b7.

    Trustworthy RED on unchanged runtime: the separately named MEMBERS-DIRECTORY-001M block had 5 focus-destination failures and 9 control passes. Corrected source: 15/15 named tests and 111/111 full MemberDirectoryProfile tests pass.

    Verification is green:

    • full frontend: 18 suites, 1,196 tests;
    • TypeScript --noEmit;
    • scoped ESLint;
    • lint baseline: 120 files, 113 reviewed legacy errors, 6 reviewed warnings;
    • repository Node/workflow/security tests: 105/105;
    • diagnostic production build;
    • git diff --check.

    Independent focus/race, security/privacy, frontend/accessibility, and backup-officer/operations reviews all report GO with no findings. The officer procedure is no-terminal, its text alternative is one sentence, and Mermaid bytes are unchanged.

    The source-controlled availability file remains literal false at blob 295909b0df8dd7c54a0f164f99d4943656aece4b. Active #616 files remain byte-identical: functions/strava.js blob 2eb48945061ab5ad2a896c6835f308cefd7fe63a and functions/strava.test.js blob d1792d390bd57608fecab84341a6e5916fcf8196. Its separate RISK-024/OAUTH hunk is untouched.

    This is SOURCE ONLY and NOT LIVE. Live production remains the inert #623 deploy 6a7e072f8f346b0008510d29; no website, Firebase, provider, account/sign-in, production-data, or connected behavior was changed or tested. #507 retains every connection and release gate.

  6. daliu commented on Aug 14, 2026

    @daliu
    ContributorAuthor

    COMPLETE / CLOSED / CLAIM RELEASED — 2026-08-14T01:13:26Z

    PR #644 merged as exact main commit 6b6a597914b3322275a1afe3e5a3c7133ab2d0a9, sole parent f3da2f351513f947b96d2cb9b883840d55919da9, tree a07d391b8e23e28dd39785329be444f452fa2953. The tree is identical to reviewed head 5ac46b43b7d2e18e338bb75d23106a851d6c7942; the parent-to-main binary diff SHA-256 is 0497c75100eddc38b7b3b38d7fd8403cb60d01b7bbad9a50bbb79472ec3d28b7 across exactly the six claimed paths.

    Exact-main CI run 31759745715 completed successfully with all five jobs green. Netlify main attempt 6a7e6ae13dd14f00086801ba is terminal error, exact commit 6b6a597..., production/main, and unpublished (published_at:null) with no Functions or Edge Functions. No GitHub deployment exists for this SHA.

    The public production pointer remains inert #623 deploy 6a7e072f8f346b0008510d29, control 9d5cc8612b4321172370bd949d307e7e4ac0ec7d. The public release marker remains byte-identical at SHA-256 b74498854585346aa66c0341e29c829245445ce986806c3864cad526b267876f. Availability remains literal false at blob 295909b0df8dd7c54a0f164f99d4943656aece4b.

    Active #616 files remain byte-identical (2eb48945061ab5ad2a896c6835f308cefd7fe63a, d1792d390bd57608fecab84341a6e5916fcf8196) and its separate SECURITY hunk remains outside this change.

    No website, Firebase, provider, account/sign-in, production-data, or connected-live behavior was changed. This closes only the source/test accessibility hardening. #507 retains every connection and release gate.

    The exact file/hunk claim is released. The remote working branch is retired. Remove the assignee and status:in-progress; later work must take a fresh claim from the then-current released main.

  7. removed their assignment
    on Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:membershipMembership lifecycle and reconciliationarea:privacyPersonal data, consent, minimization, retention, and privacy operationsarea:webWeb application and hostingbugSomething isn't workingpriority:P1High-priority follow-upsize:SSmall focused issuetype:reliabilityReliability and recoverytype:securitySecurity or privacy boundarytype:testingTest infrastructure and quality gates

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions