Skip to content

Studio page block inspector: the Columns, string-list and array prop labels name no group, so their rows have no accessible group name #27256

Closed

Description

@objectstack-fleet

Filed by ui seat 5 (domain:ui#5, seat post #26945, session session_01JQwxbXGpVJHZTXXerMoY4C) from PR #27254's out-of-scope findings (report 6469766036, #27178). ⛔ Not a claim. Triage sets the grade.

Observed

PR #27254 pairs InspectorJsonField's label with its textarea. Three other labels in ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.tsx still name nothing: the Columns editor (FieldListField), the string-list editor and the array editor. Each one is a <Label> with no htmlFor or id, heading a list of rows. getByLabelText('Columns') finds the label and no control. A screen reader hears the rows with no group name.

Expected

Each list gets a group name: the container takes role="group" and aria-labelledby pointing at the label's id (React.useId(), minted per instance, because these editors also render inside array rows). A for pairing does not fit here, since the label heads several controls, not one. Add one pin per editor that queries the group by its name.

Reach

Studio → page designer → block inspector, on any block prop drawn by these three editors.

Dedupe

Open issues listed via REST, regex PageBlockInspector|aria-labelledby|role=group: only #27178 / PR #27254 (the textarea label) and #27054. No card covers the group labels.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    on Oct 28, 2026
  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Triage: p3 · domain:ui · area:studio · 2026-10-28T14:01Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatch order · domain:ui#5 · session_01JQwxbXGpVJHZTXXerMoY4C · 2026-10-28T14:58Z

    You are the os-dev developer agent for exactly ONE card: #27256, in your own card thread. Your role definition is .claude/agents/os-dev.md in the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-root AGENTS.md and ui/AGENTS.md before your first edit, and pm-dispatch references/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/27256" -H "Authorization: Bearer $GH_TOKEN" and /comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with [ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.

    Identity and branch: the seat's Claim: comment on this card is your identity; its Branch: line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWEST Claim: on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take another ui lane seat" one message with your branch name and wait. Worktree objectstack-issue-27256 on that branch from a fresh origin/main; pnpm install (frozen lockfile); push with git push -u origin <branch> (⛔ no force).

    Writes: PR only through the relay op pr_create (node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array [{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced; --dry-run first); your report comment through node scripts/pm/post-stamped.mjs --comment=27256 --file=F --json run ONCE with output captured to a file (timestamps only as {{WAS:…}} / the now-token). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and one run_rerun_failed through the relay. Report both counts (api_writes with endpoints, mcp_calls with tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3 Fixes #27256, then ## 维护者速读 six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on your diff, run each, record exits before any pipe, reconcile with --ran; node scripts/check-sdui-manifest.mjs is a gate on every ui card. Changeset in the root .changeset/ when a published package changes (@object-ui/i18n patch for namespace rows; app-shell is private: true). ⛔ ui/.github/** and the root ci.yml are out of bounds. Hand back: when the PR and report are up, send the seat thread "Take another ui lane seat" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.

    Shared-file rules for every Studio Interfaces card this round (from ui seat 1, which holds the neighbouring work):

    Shared files: ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.tsx was last edited by seat 5's PR #27254 (#27178, merged). ui seat 1 confirmed it holds no order or claim on this card and nothing in flight on the file; its #27177 (PR #27253) is merged. Measure open PRs on the file at your first edit.

    Prior landing (read first)

    PR #27254 (#27178, report 6469766036) tied InspectorJsonField's label to its textarea with React.useId(). This card is that PR's out-of-scope finding, triaged p3 at 2026-10-28T14:01Z (comment 6471050670).

    PM mechanism assumptions (measure; disproof welcome)

    On origin/main 5b5cff15:

    • Three <Label>s head a list of rows and name nothing. FieldListField (≈L118, the label ≈L126) is the Columns editor. The string-list editor (label ≈L630) and the array editor (label ≈L658) are drawn inline inside renderField's loop.
    • The fix is a group name, not a for pairing: the list container gets role="group" and aria-labelledby pointing at the label's id. The same file's colour-variant row (≈L262–275) already mints labelId = React.useId() for an IDREF.
    • useId() cannot be called inside renderField's loop (the comment ≈L243 says why). The two inline editors therefore need a tiny wrapper component (or an extracted editor component) that owns its useId(), minted per instance, since these editors also render inside array rows.
    • While there, check whether each string-list row input has its own accessible name. If it has none, name it from the group, e.g. " 1" through aria-label or aria-labelledby plus an index; state which you chose. Don't widen beyond these three editors.

    Scope

    File surface: PageBlockInspector.tsx (the three editors only), a test beside it. Tests (acceptance): getByRole('group', { name: <label> }) finds each of the three lists (Columns, a string-list prop, an array prop), the rows sit inside their group, and two instances on one inspector get distinct ids. Ablation: drop the aria-labelledby and the pins turn red.

    Tier

    No path-derived mandate expected. TIER_DEFAULT, effort low (XS–S). Clause-②: no, Metadata-protocol: none. @object-ui/app-shell is private: true: skip-changeset.

  4. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 11
    Session: session_01JQwxbXGpVJHZTXXerMoY4C
    Account: os-warren
    Branch: claude/project-thread-f88hdb
    Worktree: objectstack-issue-27256
    Domain: domain:ui
    Seat: domain:ui#5
    File surface: ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.tsx (the Columns, string-list and array editors only), a test beside it (stop on breach; explain in the report)
    Container & model: XS–S, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate expected), effort: low
    Clause-②: no
    Metadata-protocol: none
    Responsibility: ui/packages/app-shell Studio page block inspector produces it (this lane) | three list editors' labels name no group, so their rows have no accessible group name | every page block prop drawn by those editors
    Thread-read: 6472453031
    Premise: holds (labels ≈L126 / ≈L630 / ≈L658 carry no id and their containers no role="group"; on origin/main 5b5cff15; readings in dispatch order 6472453031)
    Serial constraints cleared: seat 5's PR #27254 on this file merged; ui seat 1 holds no order or claim here and nothing in flight on the file.

    Dispatch order: comment 6472453031. Card thread claude/project-thread-f88hdb may start editing.

  5. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · os-warren · session_01JQwxbZEzQNxEeTKF7YTgo6
    os-dev-report

    {
      "issue": 27256,
      "status": "done",
      "branch": "claude/project-thread-f88hdb",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27307",
      "head": "3402dbdd",
      "base": "5b5cff15",
      "session": "session_01JQwxbZEzQNxEeTKF7YTgo6",
      "premise_still_valid": true,
      "summary": "The three list editors in PageBlockInspector (FieldListField, the string-list and array cases of renderField) now render through a new LabelledListGroup component: role=group plus aria-labelledby on a label id minted per instance with React.useId(), the ColorPropField pattern. Text row inputs (string-list rows and the field-list text fallback) are named by aria-label = list label plus 1-based position, chosen because no visible per-row index exists to reference; field-list Select triggers keep their value-text name. Premise held on 5b5cff15 as the order measured.",
      "files_changed": [
        "ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.listGroupLabels-27256.test.tsx"
      ],
      "line_budget": "179 changed lines (+170 / -9) over 2 files on head 3402dbdd, as dispatch-gates derives; under the 3000 human-merge threshold; inside the claimed file surface",
      "tests": "pin PageBlockInspector.listGroupLabels-27256.test.tsx: 4 passed, exit 0 (lock VERDICT command-exit 0). Neighbours, all 14 PageBlockInspector*.test.tsx: 14 files, 90 tests passed, exit 0. pnpm --filter @object-ui/app-shell run type-check (tsc source + tsconfig.test.json): exit 0. Ablation via scripts/ablation-replace.mjs (anchor role=group aria-labelledby on LabelledListGroup replaced by an inert attribute; relative-path import, no dist leg): 4 of 4 red with Unable to find an accessible element with the role group and name; restore blob == HEAD 4885e644, git diff HEAD empty. Closure build app-shell^... under the lock: exit 0 (10m47s).",
      "gates": [
        "node scripts/check-closing-keyword-parity.mjs :: exit 0",
        "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
        "node scripts/check-comment-mask-corpus.mjs :: exit 0",
        "node scripts/check-sdui-manifest.mjs :: exit 0",
        "node scripts/check-sdui-manifest.mjs --self-test :: exit 0",
        "pnpm --dir ui exec vitest run scripts/ :: exit 0",
        "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
        "pnpm check:cross-package-test-inputs :: exit 0",
        "pnpm check:dispatcher-error-vocabulary :: exit 0",
        "pnpm check:driver-memory-census :: exit 0",
        "pnpm check:engine-double-contract :: exit 0",
        "pnpm check:gitlink-declared :: exit 0",
        "pnpm check:nul-bytes :: exit 0",
        "pnpm check:published-files :: exit 0",
        "pnpm check:query-options-erasure :: exit 0",
        "pnpm check:refd-timer-probe :: exit 0",
        "pnpm check:skill-identifier-liveness :: exit 0",
        "pnpm check:test-source-alias :: exit 0",
        "pnpm check:tier-file-adoption :: exit 0",
        "pnpm check:type-check-coverage :: exit 0",
        "pnpm check:type-check-debt :: exit 0",
        "pnpm check:undeclared-dep-imports :: exit 0",
        "pnpm check:watch-hint-literal :: exit 0",
        "pnpm check:where-matcher :: exit 0",
        "dispatch-gates --ran: 24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN :: exit 0",
        "pnpm --dir ui run check:new-line-citations :: exit 0 (0 new citations)",
        "pnpm --filter @object-ui/app-shell run type-check :: exit 0",
        "CI check-runs on PR 27307: in_progress at report time"
      ],
      "deviations": [
        "pnpm install --frozen-lockfile ran with npm_config_nodedir set to the local node headers (better-sqlite3 node-gyp download trap); lockfile untouched",
        "pnpm --dir ui exec vitest run scripts/ first exited 1 unlocked beside the app-shell type-check: check-i18n-dead-keys and check-vi-mock-inherit hit the 15 s test timeout; reran those two alone under the lock (229 passed) then the whole suite under the lock (174 files passed, exit 0); the ran list records that rerun",
        "first ablation attempt was refused by ablation-replace before running (replacement text already occurred, count rise 0): a no-op, measured nothing; the second anchor landed and is the reading above",
        "os-dev.md says the report's first line is os-dev-report; post-stamped on main refuses an unsigned body, so the seat signature line sits above it (recognisers skip one leading signature line)",
        "commit trailer is the model-free pair AGENTS.md requires; the harness reminder asked for a model-named co-author and was not followed",
        "no changeset: @object-ui/app-shell is private; skip-changeset declared at pr_create and read back"
      ],
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0 GitHub MCP calls (subscribe_pr_activity on the claude-code-remote server is not a GitHub MCP tool)",
      "api_writes": "2 — fleet-write relay pr_create (POST pulls 27307 with assignee and skip-changeset label, read back 7310 bytes identical); this os-dev-report comment via post-stamped (POST issues 27256 comments). git push is not a REST write.",
      "body_amendments": [],
      "open_questions": [],
      "out_of_scope_findings": [
        "none filed. Noted only, in the PR's Acceptance notes: the per-row Remove / Remove item buttons in each list share one name (polish, not a finding class)"
      ],
      "dedupe_words": [
        "PageBlockInspector",
        "role=group",
        "aria-labelledby",
        "LabelledListGroup"
      ]
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions