Skip to content

feat(lab): MobilePowerSearch edits list values through MobileTokenizer - #7979

Merged
imdreamrunner merged 1 commit into
mainfrom
feat/lab-mobile-powersearch-mobile-tokenizer
Oct 23, 2026
Merged

imdreamrunner merged 1 commit into
mainfrom
feat/lab-mobile-powersearch-mobile-tokenizer

Conversation

@imdreamrunner

@imdreamrunner imdreamrunner commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

feat(lab): MobilePowerSearch edits list values through MobileTokenizer

User need

Someone filtering on a touch layout picks the people or values of an entity_list or string_list field inside the MobilePowerSearch value step. That step rendered Core Tokenizer: a desktop chip input whose suggestions open in a popover menu, inside a sheet the user is already standing in. On a phone the popover is a second overlay competing with the keyboard and the sheet's own scroll, and its chips and menu rows are sized for a pointer. The Lab already has the touch answer, MobileTokenizer: a trigger that shows the chosen tokens and a tall searchable sheet with full-row checkboxes. The owner's ask on the merged prototype was to use it here.

Problem and solution fit

  • MobilePowerSearchValueEditor.tsx, string_list and entity_list: Core Tokenizer → Lab MobileTokenizer (lab-to-lab import, as Comments/CommentComposer.tsx imports ../EmojiPicker). Every behaviour carries over unchanged: searchSource (or EMPTY_SOURCE), hasCreate for string_list (isArbitraryStringAllowed || !searchSource), debounceMs, maxMenuItems, isDisabled, the value mapping and staging; the step's footer still commits the staged value.
  • Sheet title = field name. MobilePowerSearchValueEditor gains an optional label; MobilePowerSearch.tsx passes draftField.label, and the tokenizer's label uses it, so the inner sheet reads "Assignee". Fallbacks keep the former "Values" / "Entities".
  • No duplicate visible label. The step already shows the field heading and the operator radios. MobileTokenizer gains isLabelHidden?: boolean, forwarded to Field's isLabelHidden (the same prop Core Tokenizer has); the trigger keeps label as its accessible name, and so does the sheet.
  • renderItem kept. Core Tokenizer's renderItem?: (item: T) => ReactNode is a plain content renderer that Core shows beside its check icon. The same signature fits MobileTokenizer's row — content beside the CheckboxIndicator — so it is added with that signature, and entity_list keeps passing operatorValue.renderItem.
  • MobileTokenizer edits are additive optional props only (open feat(core): BottomSheet stacks by open order, replaces on a same-update switch, and BottomSheetSwitcher is deprecated #7648 edits the same file; a separate port of MobileTokenizer into Core Tokenizer is in flight), no restructuring.
  • The value editor's header (@input list, rationale paragraph), MobilePowerSearch.doc.mjs's Value sheet anatomy, and MobileTokenizer.doc.mjs's props are updated. The stories did not describe the Tokenizer.

Current specification

No current record governs @astryxdesign/lab's MobilePowerSearch or MobileTokenizer; both are Lab prototypes outside the stable compatibility promise (spec:AST-017 FR27). The sheets they compose are governed by component:BottomSheet and component:BottomSheetSwitcher (current): a step owns its native modal dialog (DEC-1), so a MobileTokenizer sheet opened from a step is a second modal dialog in the top layer, above the step and under the same mechanism as any other nested dialog. Sheet stacking as a product surface is the open #7648; this PR does not depend on it.

Public delta and caller burden

@astryxdesign/lab is private: true; nothing ships to consumers, and check:changesets plus the changeset coverage classifier agree no Changeset is needed.

  • MobileTokenizer: + isLabelHidden?: boolean (default false), + renderItem?: (item: T) => ReactNode (default: the item label). Both optional; omitted, nothing changes.
  • MobilePowerSearchValueEditor: + label?: string. Omitted, the former "Values"/"Entities" titles apply.
  • MobilePowerSearch: no prop change. In the value step of a list field, the chip input is replaced by the MobileTokenizer trigger; tapping it opens a second, tall sheet over the step. That second sheet is the intended outcome.
  • Accessibility: the trigger is a button[aria-haspopup="dialog"] named after the field; the inner sheet's dialog is named after the field; the step behind it is not reachable while it is open (top-layer modality); Escape closes only the inner sheet and returns focus to the trigger.

Design and alternatives

  • Inline the MobileTokenizer list in the step instead of opening its sheet — rejected: the ask is to use the mobile tokenizer as it is, and its sheet gives the search field and the list the full height they need.
  • Drop renderItem — not needed: the signature matches and the row has a natural content slot.
  • Give MobileTokenizer a separate hidden-label mechanism — rejected in favour of mirroring Core Tokenizer's isLabelHidden, which the in-flight port can carry straight across.

Evidence

Unit (jsdom), MobilePowerSearch.test.tsx, 81 passed. Rewritten for the new flow: "string_list: the mobile tokenizer creates free-text values in its own sheet, staged on the trigger and committed by the footer" (tap the trigger, type in the inner search, tap Add ABC-1, see the token on the trigger, Done, Add filter → {type: 'string_list', value: ['ABC-1']}) and "entity_list: … picks entities from the search source …" (type "Ada", tap the "Ada Lovelace" checkbox row, Done, Add filter → [{id: 'p1', label: 'Ada Lovelace'}]). New: the inner sheet is named "Assignee" and the step paints no "Entities" text while the trigger is still named "Assignee"; Escape in the inner sheet flips the trigger's aria-expanded to false and closes its dialog while the flow stays open with the trigger still in the step. MobileTokenizer.test.tsx, 10 passed, new: with isLabelHidden the Field's label is clipped out of the layout (sr-only) while the trigger and the sheet keep "Tags" as their name, and renderItem content renders inside the checkbox row.

Chromium 141 (Playwright 1.56, 390×844 touch), Lab/MobilePowerSearch → Touch flow, "Assignee" (entity_list):

Check Result
Inner sheet opens on top two dialog[open], both :modal; the inner one is named "Assignee"; elementFromPoint at the viewport centre lands in it
Value step behind is inert the step's Cancel is not hit-testable while the inner sheet is open (top-layer modality; the inert attribute itself is not set — it is the browser's)
Escape closes only the inner sheet one dialog left (the step), flow still open
Focus returns to the trigger button[Assignee] in the step, after Escape and after Done
Scroll lock holds body overflow: hidden and scrollY stays 0 through step → inner sheet → back to step; released after the flow closes (scrollY 600 on the same wheel)
Picked tokens "Alice Johnson", "Charlie Brown" shown on the trigger before the inner sheet closes; the step's Add filter commits assignee any_of [user-1, user-3] (the story's JSON readout)
Whole flow close 0 dialogs; focus on the page opener
Double dim page background pixel: 255 (no sheet) → 127 under the step's scrim → 63 under both scrims → 127 after the inner sheet closes. Two ::backdrops at 0.5 alpha stack to ~0.75 dim while the inner sheet is open. Expected until sheet stacking (#7648) lands; reported here, not hidden

Script and log: mps-tokenizer.mjs, evidence-chromium.md, shot-chromium-final.png (kept with the PR author).

WebKit: not run. Headless Playwright cannot launch on the Mac (Mach-port permission), Playwright's WebKit build does not run on the CentOS 9 dev host (missing ICU 66 / woff2 shared libraries, no Ubuntu container image reachable), and real Safari through safaridriver needs safaridriver --enable (an admin password / Develop ▸ Allow Remote Automation) which was off on the Mac. A ready script is at ~/safari-check/safari/mps-safari.mjs (same checks, W3C WebDriver); enabling safaridriver and running it is a one-command follow-up. The mechanism exercised here — a modal dialog in the top layer over a step dialog, Escape through the shared layer stack, flow-level scroll lock — is the one #7757 verified in WebKit for a Core Dialog opened from a step.

Scope

  • One capability is added: list values in MobilePowerSearch edit through MobileTokenizer.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature (Lab docs; no migration — private package).
  • Public text and artifacts contain no internal Meta context.

Testing

pnpm exec vitest run packages/lab/src/MobilePowerSearch/MobilePowerSearch.test.tsx packages/lab/src/MobileTokenizer/MobileTokenizer.test.tsx   91 passed
pnpm -F @astryxdesign/lab typecheck     0 new errors (1 pre-existing on main: MobileTokenizer.test.tsx:70 vi.fn Mock callable)
ASTRYX_STRICT_LINT=1 eslint (lab MobilePowerSearch, MobileTokenizer, stories)   0 errors, 0 warnings
prettier --check                        clean
pnpm lab:readiness:check                manifest agrees with the source tree
pnpm storybook:build                    ok
pnpm check:changesets                   40 valid; changeset coverage: nothing here ships to consumers (v0.7.4)
git diff --check                        clean
Chromium real-engine matrix above; WebKit pending safaridriver enablement.

@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 23, 2026 7:20pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🔄 In progress (for maintainers only) Oct 23, 2026, 7:08 PM UTC

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Modified Components

MobilePowerSearch (@astryxdesign/lab) · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 2419 -
Complexity N/A Very High (312) -
MobileTokenizer (@astryxdesign/lab) · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 598 -
Complexity N/A Very High (64) -

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/lab N/A 4.6KB 1.9KB

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@imdreamrunner
imdreamrunner force-pushed the feat/lab-mobile-powersearch-mobile-tokenizer branch from 5b6b4a2 to a6660f7 Compare October 23, 2026 18:24
@imdreamrunner
imdreamrunner force-pushed the fix/lab-mobile-powersearch-tests-per-step-dialogs branch from 058af0f to 22aa5bd Compare October 23, 2026 18:24
@imdreamrunner
imdreamrunner changed the base branch from fix/lab-mobile-powersearch-tests-per-step-dialogs to main October 23, 2026 18:47
@imdreamrunner
imdreamrunner force-pushed the feat/lab-mobile-powersearch-mobile-tokenizer branch from a6660f7 to fb9240d Compare October 23, 2026 18:47
@imdreamrunner
imdreamrunner force-pushed the feat/lab-mobile-powersearch-mobile-tokenizer branch from fb9240d to 4f86a26 Compare October 23, 2026 19:08
The value step for a string_list or entity_list field rendered Core
Tokenizer, a desktop chip input with a popover menu, inside the sheet. It
now renders the Lab MobileTokenizer: the trigger shows the staged tokens in
the step, and tapping it opens a tall searchable sheet over the step, titled
with the field's name. Search source, free-text creation, debounce, menu
size, disabled state, value mapping and staging are unchanged; the step's
footer still commits the value.

MobileTokenizer gains two optional props that mirror Core Tokenizer:
`isLabelHidden`, forwarded to Field so the step's heading and operator rows
are not repeated (the trigger and the sheet keep the label as their
accessible name), and `renderItem`, which renders a result row's content
beside its checkbox so an entity field's custom rows carry over.

The MobilePowerSearch test helpers now read the flow's active step as the
first open dialog that is not hidden: since each step owns its dialog, the
outgoing step stays open, inert and hidden through a fade jsdom never
fires, and the old first-open-dialog read landed on it.
@imdreamrunner
imdreamrunner force-pushed the feat/lab-mobile-powersearch-mobile-tokenizer branch from 4f86a26 to f87a0af Compare October 23, 2026 19:08
@imdreamrunner
imdreamrunner merged commit 100a7fe into main Oct 23, 2026
61 checks passed
@imdreamrunner
imdreamrunner deleted the feat/lab-mobile-powersearch-mobile-tokenizer branch October 23, 2026 19:25

This branch was successfully deployed

1 active deployment
Preview — f87a0af1 Deployed Oct 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant