Skip to content

Record pane: Esc that a field already handled still steps the pane back a level (or restores it from maximized) #28156

Open

Description

@objectstack-fleet

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

Filing gate: ② a wrong state shown to the user. This is an out-of-scope finding from PR #28116 (#27524 ②), raised by its card thread and confirmed by that PR's at-tier delta review (③). It is not fixed there, because navigation-overlay.tsx is #27545's surface.

Business reason

Who: a reader working inside a record opened in the drawer or pane.

What happens: at any level below the pane's root (a related record opened in place), they press Esc inside a field to close a mention list, cancel a cell edit, or leave the 动态 composer. The field handles the key, but the pane also steps back a level. Once #27545 B wires ⤢, the same happens while the pane is maximized: the pane restores. The reader loses their place for a key they meant only for the field.

Reach: every console reader who opens a related record in place today. The maximized case arrives once #28129 is on main.

Mechanism

ui/packages/components/src/custom/navigation-overlay.tsx (main @ 27e0bd58, line 551) passes onEscapeKeyDown={onBack ? (e) => { e.preventDefault(); onBack(); } : undefined}. #27953 and #28129 add the maximized || arm. Radix's dismissable layer calls onEscapeKeyDown for every Esc it sees, before the defaultPrevented check that gates only its own default dismiss. So a key a field has already marked handled (event.defaultPrevented) still runs onBack / handleEscape. PR #28116's EnlargedFace marks a field's Esc handled for exactly this reason. Its pins cover the root drawer and the root split pane only.

Wanted

The pane's Escape handler returns early when event.defaultPrevented is set: if (e.defaultPrevented) return; before preventDefault() and the step back / restore.

Acceptance

  • At a level below the root, Esc in a field inside the record (the composer, a cell editor, a mention list) keeps the level open. A bare Esc still steps back one level (CONTROL).
  • Maximized, Esc in such a field keeps the pane maximized. A bare Esc still restores (CONTROL).
  • Pinned against the real NavigationOverlay, with an ablation of the guard turning the field case red.

Refs #27545 · Refs #28116


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    and removed on Oct 30, 2026
  2. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-tesla · session_01L9qibxgfM8p8t3fo6y9eFK

    Claim: PM loop round 1 · 2026-10-31T02:53Z
    Session: session_01L9qibxgfM8p8t3fo6y9eFK (seat; card thread session_01L9qibwcrcmDwuHqtrvwstB)
    Account: os-tesla
    Branch: claude/project-thread-zmuy4v (card thread; the platform assigns the thread's branch)
    Worktree: objectstack-28156
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: ui/packages/components/src/custom/navigation-overlay.tsx, its tests, root changeset (stop on breach; explain in the report)
    Container & model: S (not mechanical), mode:thread, model: TIER_DEFAULT (default judgment tier), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: navigation-overlay.tsx (the pane's Escape handler, #27545's surface) | none — Radix gates only its own dismiss on defaultPrevented | every console reader who opens a related record in place
    Thread-read: none
    Premise: holds
    Serial constraints cleared: PR #28116 (#27524 ②) does not touch navigation-overlay.tsx (its file list read); no other live claim names it

  3. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-tesla · session_01L9qibwcrcmDwuHqtrvwstB
    os-dev-report

    {
      "issue": 28156,
      "status": "done",
      "branch": "claude/project-thread-zmuy4v",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/28294",
      "session": "session_01L9qibwcrcmDwuHqtrvwstB",
      "premise_still_valid": true,
      "summary": "The record drawer's onEscapeKeyDown (below the root, or maximized) now returns on defaultPrevented, holds the sheet open, and leaves the key to its target when escapeOwnedByTarget says a text field, editable region, or open menu/listbox/dialog owns it; escapeOwnedByTarget gains a pane argument so the sheet's own role=dialog never counts. Declared deviation from the card's one-line Wanted (seat ruling B, see below): Radix dismissable-layer 1.1.19 listens on document capture, so the guard alone fixed only window-capture markers (#28116's face), not React onKeyDown fields. Root drawer unchanged.",
      "tests": "head e2e6e5e9. New navigation-overlay-field-escape-28156.test.tsx: Tests 19 passed (19). pnpm --dir ui exec vitest run packages/components/ (lock): Test Files 1 failed | 461 passed | 1 skipped (463); the red share-filter-sort-spec-parity.test.ts needs spec .d.ts, green after env -u OS_SKIP_DTS turbo build of @object-ui/components^... (Test Files 2 passed (2)). Ablations via scripts/ablation-replace.mjs, restore blob == HEAD each, source-resolved (no dist leg): A0 main's handler -> 8 failed | 11 passed; A1 defaultPrevented return removed -> 2 failed; A2 ownership test removed -> 4 failed; A3 owner !== pane removed -> 1 failed.",
      "gates": "dispatch-gates --commands on e2e6e5e9: 34 derived, all :: exit 0 (node scripts/check-sdui-manifest.mjs among them); --ran reconcile 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Extra: pnpm --filter @object-ui/components run type-check :: exit 0; eslint on 2 changed files :: exit 0; pnpm --dir ui run check:new-line-citations :: exit 0. CI check-runs: in_progress at report time.",
      "line_budget": "3 files, +245 / -6 = 251 changed lines (under 3000)",
      "files_changed": [
        "ui/packages/components/src/custom/navigation-overlay.tsx",
        "ui/packages/components/src/__tests__/navigation-overlay-field-escape-28156.test.tsx",
        ".changeset/28156-record-pane-field-escape.md"
      ],
      "deviations": [
        "Wanted was the one-line defaultPrevented guard; shipped guard + escapeOwnedByTarget(target, sheet) on the drawer handler, per seat ruling B after the capture-order measure. Cost: Esc with focus in a text field never steps back (same as the split pane).",
        "Components suite first run red in one file from local OS_SKIP_DTS build state; re-measured after a full .d.ts build."
      ],
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "4 — reads only: issue_read get, issue_read get_comments x2, pull_request_read get; 0 writes",
      "api_writes": "1 relay write: pr_create objectstack-ai/objectstack#28294 (fleet-write run 41165491986); plus this report comment via post-stamped",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed: the root drawer (no onBack, not maximized) still dismisses on an Esc in a field unless the key was marked ahead of the layer; the card scopes only below-root and maximized"
      ]
    }

    Reported 2026-10-31T03:43Z on head e2e6e5e9, PR #28294. Seat ruling B (the guard plus the split pane's ownership test) was given in the dispatch thread at 2026-10-31T02:56Z.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uipm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions