Repository navigation
docs(spec): PowerSearch's sheet presentation follows the Lab touch flow (DEC-3) - #9631
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
This spec change rewrites the current component:PowerSearch sheet flow (DEC-3, FR12–FR17, AV5). Approval waits on an engineering owner recording the decision by commenting /approve-spec <full head SHA>, and as an engineering owner you may record it yourself. Separately from that decision, a few contradictions inside the record need fixing:
- FR15 and FR15a say a commit closes only the value step and reveals Filters, while FR16 closes the value step and Add filter together, and when Add filter was the entry point there is no Filters beneath to reveal.
- The verification row's failure signal still treats "Clear all skips a read-only filter" as a failure, which FR12 now requires, and still lists a removal from the value step.
- AR6 announces "All filters removed" while read-only filters stay.
- DEC-3 widens the INV7 layer-budget exception without the measured element counts INV7 asks for, and the open questions and the date known gap still describe the old Clear all and UTC-day behaviour.
[Automated review]
| | FR13 | Add filter — from the Filters sheet's Add filter action, or directly when an interactive field with no filter opens — is a tall modal sheet named "Add filter" opened above the Filters sheet, which stays open beneath it (FR15a), so the sheet has no Back action: dismissing it — Escape, a scrim tap, a swipe — reveals Filters again, or closes the flow when Add filter was the entry point, in which case its fixed footer holds Done. Its header — the heading and, when one shows, the search field — stays fixed while the rows scroll. Without a content-search field it lists the visible fields grouped by `group` (ungrouped first, each group a named list), each a row button with the field's icon, a trailing chevron — or, for a field whose only operator takes an `empty` value, a trailing plus, since choosing it adds the filter at once (FR15) — and, once the field count reaches eight, a "Search filters" field above the list that matches labels and `typeaheadAliases` case-insensitively, caps a non-empty query's matches at `maxSearchResults`, and shows "No filters match your search" when nothing matches. With a `contentSearchFieldKey` whose default operator takes a `string` value it shows instead a "Search" searchbox using `placeholder`, with the same suggestions the popover presentation's typeahead offers listed inline as row buttons ("Search results"): fields, field-and-operator pairs, value matches, and a leading free-text item for the content field; Enter commits the first suggestion, ArrowDown and ArrowUp move between the search and the rows — arrow movement across independently focusable controls, recorded here as this record's exception to `spec:AST-020/FR15`, which adopts arrow keys for a single composite only: the searchbox-to-list continuity is what the popover presentation's combobox gives the same suggestions, and the rows stay in the Tab order as well — an IME-composing Enter does nothing, and a query with no suggestion shows "No results found" — the searchbox's `aria-controls` names the results list, or that message while nothing lists, so it always names an element; the field list's "No filters match your search" likewise shows only for a non-empty query that matches nothing, never for a config with no field. Neither search field takes focus when the sheet opens: the sheet opens with focus on its panel and the software keyboard stays down until the person taps the search; a direct vertical touch drag on the rows while a search field has focus blurs it, so the keyboard leaves and the rows can be read. A suggestion that carries a value commits `onChange([...filters, filter], 'add', filters.length)`, announces `Added {label}` inside the Filters sheet (AR6), and returns to it; any other suggestion or field row opens the value step with that field and operator, above Add filter. Every visible field lists, a field whose every operator is `nested` included: the value step edits every operator's value (FR14). | DEC-3; Lab touch-flow evidence; current typeahead source | Current contract. Implementation gap: no Add filter sheet exists. | | ||
| | FR14 | The value step is a tall modal sheet that edits every operator's value, opened above the sheet that led to it — Filters for an edit, Add filter for a creation — which stays open beneath it (FR15a); its title is the dialog's name (AR5), and for a filter whose field or operator the config no longer has it reads as the Filters row does (FR12): the field's label when the config still has the field, the raw key for whatever is gone. Its header and footer stay fixed while the body scrolls. When the field offers more than one operator the body starts with an "Operator" radio group whose options read "{field label} {operator label}" and list the field's operators, with the operator the step opened with checked — the filter's operator while editing, and while creating the operator the suggestion named (FR13) or else the field's default operator, else its first; otherwise there is no group and the title is "{field label} {operator label}" naming that operator. When no operator label applies the title is the field label. The body below the group is the editor for the checked operator's value type: the sheet-native controls of FR17, or for a `nested` value the popover presentation's built-in nested editor — the sub-filter tree with its Add action, each sub-filter's field, operator, and value controls, and removal — rendered as the popover renders it, with one difference: the step's Operator group takes the place of the editor's root group-operator picker, which is not shown, and the tree root reads the checked operator's label; a sub-filter's controls are the popover editor's own, in their default presentation (a Selector's menu, the Tokenizer's inline field), not the sheet's choice rows (FR17) or Tokenizer sheet (FR21), which govern the step's top-level value only, because the sheet passes no `experimental` option down to them; the tree is its own width container, so below 400 CSS px its rows wrap instead of overflowing the sheet, as they wrap in a narrow popover. A group is created and edited the same way in both presentations; switching the operator swaps the body. A value chosen in the body is staged, not committed: no `onChange` fires until the footer confirms it. Changing the operator keeps a staged value only when the new operator reads the same value type; an `empty` operator stages `{type: 'empty'}`. The body's sections sit on the sheet's surface when the editor is the body's only section; when the body has more than one section — the Operator group above the editor — each section is a muted container, so the grouping reads without a frame around a lone editor. The footer holds Cancel and the confirm action — `popoverSaveButtonLabel` when given, otherwise "Add filter" while creating and "Apply" while editing — disabled until the staged value is complete; the step has no Remove action: a filter is removed from its Filters row (FR12). When `components[type].Editor` is set for the checked operator's value type it replaces the body below the Operator group and the footer — the group stays outside it, so switching operators still swaps the body — receiving the props the popover presentation hands it, with the same meaning: the caller's `config`, the partial `filter`, `mode`, `saveButtonLabel`, `isReadOnly`, `onSave`, and `onCancel`, and nothing the popover does not pass; `onSave(filter)` commits it, `onSave(null)` removes the filter while editing and returns to Filters unchanged while creating. A controlled update that removes the field or operator being edited replaces the body with "This filter is no longer available", announced from its rendered element inside the sheet while the step is the open one (AR6), and keeps Cancel as the way out, the filter's trailing remove action on its Filters row being the removal path (FR12); when focus was on a control of the replaced body it moves to Cancel, so it never falls to the page body inside the open sheet (FR16). | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no value step exists. | | ||
| | FR15 | Confirming a created filter commits `onChange([...filters, filter], 'add', filters.length)`; choosing a field whose only operator takes an `empty` value commits that filter the same way from the Add filter row, with no value step. Tapping an editable Filters row opens the value step on that filter in edit mode; Apply commits `onChange(updated, 'edit', index)`; the row's trailing remove action, or a tap on an `empty`-valued filter's row, commits `onChange(rest, 'remove', index)`, where `index` is the filter's position at commit time: a controlled parent that reorders or clones its filters while the step is open is followed by object identity, then by a structurally equal filter in the original slot, then by a unique structural match; when the target is gone or ambiguous nothing is committed and the step closes back to Filters, focus landing as FR16 states for a row that no longer exists. Every commit from a step closes that step, revealing Filters. While the field is read-only, or disabled, no commit happens. | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no edit path exists. | | ||
| | FR15a | The steps stack: each step opens above the sheet that led to it, which stays open — receded and blocked as a covered sheet of the stack (`component:BottomSheet` FR5, FR6) — so the stack is Filters alone; Filters under Add filter; Filters under the value step for an edit; Filters under Add filter under the value step for a creation; or, when Add filter was the entry point, Add filter alone and then Add filter under the value step. No sheet of the flow carries a Back action: a dismissal — Escape, a scrim tap, a swipe, a platform close request — reaches only the top sheet (`component:BottomSheet` FR7), closes it, and reveals the sheet beneath, which takes focus back as FR16 states; dismissing the bottom sheet of the stack closes the flow. Cancel on the value step is that dismissal; a commit closes the step the same way. The flow therefore has one direction forward (open a step above) and one back (close the top step), and a step never replaces another in place. | DEC-3; `component:BottomSheet` FR5–FR9 | Current contract. Implementation gap: no flow exists. | |
There was a problem hiding this comment.
"a commit closes the step the same way" closes only the value step, which reveals Add filter during a creation. FR16 (next row) closes the value step and Add filter together, and when Add filter was the entry point this stack has no Filters to reveal. Could this say that a creation's commit closes both and shows Filters, opening it on direct entry?
| | FR14 | The value step is a tall modal sheet that edits every operator's value, opened above the sheet that led to it — Filters for an edit, Add filter for a creation — which stays open beneath it (FR15a); its title is the dialog's name (AR5), and for a filter whose field or operator the config no longer has it reads as the Filters row does (FR12): the field's label when the config still has the field, the raw key for whatever is gone. Its header and footer stay fixed while the body scrolls. When the field offers more than one operator the body starts with an "Operator" radio group whose options read "{field label} {operator label}" and list the field's operators, with the operator the step opened with checked — the filter's operator while editing, and while creating the operator the suggestion named (FR13) or else the field's default operator, else its first; otherwise there is no group and the title is "{field label} {operator label}" naming that operator. When no operator label applies the title is the field label. The body below the group is the editor for the checked operator's value type: the sheet-native controls of FR17, or for a `nested` value the popover presentation's built-in nested editor — the sub-filter tree with its Add action, each sub-filter's field, operator, and value controls, and removal — rendered as the popover renders it, with one difference: the step's Operator group takes the place of the editor's root group-operator picker, which is not shown, and the tree root reads the checked operator's label; a sub-filter's controls are the popover editor's own, in their default presentation (a Selector's menu, the Tokenizer's inline field), not the sheet's choice rows (FR17) or Tokenizer sheet (FR21), which govern the step's top-level value only, because the sheet passes no `experimental` option down to them; the tree is its own width container, so below 400 CSS px its rows wrap instead of overflowing the sheet, as they wrap in a narrow popover. A group is created and edited the same way in both presentations; switching the operator swaps the body. A value chosen in the body is staged, not committed: no `onChange` fires until the footer confirms it. Changing the operator keeps a staged value only when the new operator reads the same value type; an `empty` operator stages `{type: 'empty'}`. The body's sections sit on the sheet's surface when the editor is the body's only section; when the body has more than one section — the Operator group above the editor — each section is a muted container, so the grouping reads without a frame around a lone editor. The footer holds Cancel and the confirm action — `popoverSaveButtonLabel` when given, otherwise "Add filter" while creating and "Apply" while editing — disabled until the staged value is complete; the step has no Remove action: a filter is removed from its Filters row (FR12). When `components[type].Editor` is set for the checked operator's value type it replaces the body below the Operator group and the footer — the group stays outside it, so switching operators still swaps the body — receiving the props the popover presentation hands it, with the same meaning: the caller's `config`, the partial `filter`, `mode`, `saveButtonLabel`, `isReadOnly`, `onSave`, and `onCancel`, and nothing the popover does not pass; `onSave(filter)` commits it, `onSave(null)` removes the filter while editing and returns to Filters unchanged while creating. A controlled update that removes the field or operator being edited replaces the body with "This filter is no longer available", announced from its rendered element inside the sheet while the step is the open one (AR6), and keeps Cancel as the way out, the filter's trailing remove action on its Filters row being the removal path (FR12); when focus was on a control of the replaced body it moves to Cancel, so it never falls to the page body inside the open sheet (FR16). | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no value step exists. | | ||
| | FR15 | Confirming a created filter commits `onChange([...filters, filter], 'add', filters.length)`; choosing a field whose only operator takes an `empty` value commits that filter the same way from the Add filter row, with no value step. Tapping an editable Filters row opens the value step on that filter in edit mode; Apply commits `onChange(updated, 'edit', index)`; the row's trailing remove action, or a tap on an `empty`-valued filter's row, commits `onChange(rest, 'remove', index)`, where `index` is the filter's position at commit time: a controlled parent that reorders or clones its filters while the step is open is followed by object identity, then by a structurally equal filter in the original slot, then by a unique structural match; when the target is gone or ambiguous nothing is committed and the step closes back to Filters, focus landing as FR16 states for a row that no longer exists. Every commit from a step closes that step, revealing Filters. While the field is read-only, or disabled, no commit happens. | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no edit path exists. | | ||
| | FR15a | The steps stack: each step opens above the sheet that led to it, which stays open — receded and blocked as a covered sheet of the stack (`component:BottomSheet` FR5, FR6) — so the stack is Filters alone; Filters under Add filter; Filters under the value step for an edit; Filters under Add filter under the value step for a creation; or, when Add filter was the entry point, Add filter alone and then Add filter under the value step. No sheet of the flow carries a Back action: a dismissal — Escape, a scrim tap, a swipe, a platform close request — reaches only the top sheet (`component:BottomSheet` FR7), closes it, and reveals the sheet beneath, which takes focus back as FR16 states; dismissing the bottom sheet of the stack closes the flow. Cancel on the value step is that dismissal; a commit closes the step the same way. The flow therefore has one direction forward (open a step above) and one back (close the top step), and a step never replaces another in place. | DEC-3; `component:BottomSheet` FR5–FR9 | Current contract. Implementation gap: no flow exists. | | ||
| | FR16 | Focus follows the step: after a commit from the value step while creating, the value step and Add filter close and focus lands on the new filter's row in Filters; after a content-search suggestion or an `empty`-valued field row commits a filter directly, that filter's row; after Apply or Cancel from editing, the edited filter's row — followed across a controlled reorder or clone, and, when a controlled update removed it or left two filters it could be, the nearest remaining row that is a button around its former position (the following one on a tie), else Add filter; after a row's trailing remove action, the next remaining row's trailing remove action (the following row, else the previous), so a run of removals needs no re-aiming, else Add filter when no row remains; after Cancel from creation, Add filter is revealed with focus on the field row that opened the step, or on the content search when a suggestion opened it — and when a controlled update has removed that field, or the content-search field itself, the Add filter sheet's first control: its search field when one shows, else its first field row; after Add filter is dismissed, the Filters header's Add filter, or the empty state's; after a caller's `Editor` override calls `onSave(null)` while creating, the same (the flow returns to Filters unchanged, FR14); after Clear all's confirmation closes, the Filters sheet's first remaining control — a kept read-only filter's row has none, so Add filter — with the sheet still open; and, as one rule for the Filters and Add filter sheets, when a controlled update unmounts the control that holds focus — a filter's row or its remove action, a dropped field's row, Clear all gone with the last editable filter — the nearest remaining row that is a button, counted among the button rows by the removed one's index (the following one, else the last) — a plain row (a read-only filter, or every filter while the field is read-only) is passed over — else the sheet's first control (the Add filter sheet's search field, Add filter in the Filters header or empty state), else the sheet's panel, so focus never lands on the page body inside the open sheet; when a controlled update replaces the value step's body with the unavailable state while focus was on one of its controls, Cancel (FR14); after the flow closes, the trigger. A sheet that opens takes focus on its panel, not on a control, except where this row names one; the Add filter sheet's search fields never take it on open (FR13). | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no flow exists. | |
There was a problem hiding this comment.
Two focus targets here differ from the Lab flow this record cites. After a creation, Lab focuses Add filter, not the new row (its test "returns to Filters with focus on Add filter once a filter is committed"). After a removal, a read-only row has no remove action, so "else Add filter when no row remains" leaves focus undefined when only read-only rows remain. Lab passes over plain rows and falls back when no editable row remains.
| route `spec:AST-086/FR14` names for the field's position then: the modal | ||
| that holds the field, or the shared channel when none does. A result count that changes while the flow is | ||
| closing (Clear all's commit) waits the same way and the two are spoken as one | ||
| it does not. Clear all announces "All filters removed" inside the |
There was a problem hiding this comment.
With read-only filters kept (FR12), "All filters removed" tells a screen-reader user that every filter is gone while some still apply. Lab announces "Editable filters removed".
| | FR4–FR6, FR9 | Opening-path tests plus real-Chromium geometry assertions | Add from inner input; edit actual left/right/equidistant token/control; pointer/keyboard; reopen opposite side | Using pointer coordinates, modality, wrapper-only identity, stale identity, or public props fails. | `audit:PowerSearch/interaction` | | ||
| | PR1–PR4 | Instrumented unit tests plus first-frame real-Chromium capture | Open, close, reopen opposite side; hidden placement commit; first painted frame | Render-time reads, repeated reads, observers, pointer tracking, or a default-position flash fails. | `audit:PowerSearch/performance` | | ||
| | FR8, AR1–AR3 | Existing PowerSearch/editor suites plus focused browser checks | Pointer and keyboard edit, apply, cancel, Escape, light dismiss, focus return, selection, semantics | Focus, semantics, selection, dismissal, or reachability changes fail. | `audit:PowerSearch/accessibility` | | ||
| | FR10–FR21, AR4–AR6, PR5 | Focused PowerSearch sheet tests with the compact-touch query stubbed both ways, plus real-engine touch evidence (implementation gap: not yet in the tree; the Lab prototype's suite, `packages/lab/src/MobilePowerSearch/MobilePowerSearch.test.tsx`, is the current evidence for the flow the rows describe) | default popover, forced sheet, adaptive both ways (server markup and hydration as the popover field on a coarse-pointer client, then the trigger, no recoverable error, focus on the body; with `hasAutoFocus`, the autofocused input's focus ending on the trigger with no `onBlur` or `onFocus` to the caller), trigger name, Filters/Add filter/value steps, each value type, list value through the Tokenizer sheet, Clear all, read-only, disabled while open, focus after each step, focus after an `adaptive` switch from the trigger, the input, a removed control, an open editor popover, and from outside the field, a field inside an open Dialog announcing the count, Clear all, and in-sheet messages inside that Dialog and never on the page body, StrictMode result count, in-sheet live region while a sheet is open (added, removed, count, empty results), Clear all announced only after the dialog's exit, row, token and Removed text sharing one formatter, dates as the `timezoneID` day on all three (UTC, Tokyo, Honolulu, unset), summaries and relative-date labels and values from the catalog, the row and Removed through one reorderable message, an unmatched relative-date value as stored, a filter the config no longer knows listed and removable, a cleared number unstaged, choice rows as radios and checkboxes with `aria-checked`, a stale read-only filter as a plain row, a removal next to a nested row focusing that row, a nested filter removed from its step, a list value joined by the locale's narrow conjunction list in English, Simplified Chinese, Japanese, and German, an empty-result message spoken once per query from its rendered element, a controlled update that removes the edited field moving focus from the replaced body to Cancel and announcing the unavailable state in the sheet while the step is open and never from a step beneath another or retained closed, Apply and Cancel after a controlled update removed the edited filter focusing the nearest row, else Add filter, a row's trailing remove action focusing the next row's remove action, a one-tap `empty`-valued filter added from its field row and removed from its row, Clear all behind its confirmation keeping read-only filters and leaving Filters open, a dismissed step revealing the step beneath with focus restored to it, Add filter opening with its search unfocused and a touch drag on the rows blurring a focused search, a stale filter's step titled as its row reads (raw keys for what is gone, the field's label when the field remains), Cancel from creation after the opening field was removed focusing the Add filter sheet's first control, a removed focused Filters row, a removed Clear all, and a dropped focused field row each handing focus to the nearest remaining row or the sheet's first control, element counts at rest and with each of the Filters, Add filter and value sheets open, and with the Tokenizer's list-value sheet stacked above the value step, under fine and coarse pointers, nothing of the flow in the DOM at rest before a press and after the exit, a nested filter edited and created through the built-in nested editor (its sub-filter rows wrapping inside a 390 and a 414 px sheet; its root operator picker absent on a mixed field, the Operator group in its place), a nested-only field and its suggestion offered, a suggestion's operator checked over the field's default, the Operator group outside a caller's Editor override on a mixed field, a count changed during the exit spoken with Clear all's message as one catalog message after the close, a radio group's arrow keys, Home, and End moving and checking, Space and Enter checking a focused radio row and toggling a checkbox row, right-to-left layout with the row chevrons and the rows' chevrons mirrored in RTL (`design:bidirectionality/DR4`) | A field without `experimental` changes beyond what FR19 states, a value commits before the step confirms, Clear all skips a read-only filter, the trigger carries native `disabled`, focus lands elsewhere, an announcement reaches a channel outside the modal that holds the field at that moment, or the popover bundle carries the sheets. | `audit:PowerSearch/sheet` | |
There was a problem hiding this comment.
This row still describes the replaced flow. Its failure signal fails "Clear all skips a read-only filter", which FR12 now requires. It also lists "a nested filter removed from its step", although FR14 removes the step's Remove, and "a removal next to a nested row focusing that row", although FR16 now focuses the next row's remove action.
| `document.body.querySelectorAll('*').length`, at rest and with each state of | ||
| the stack open, under a fine and a coarse pointer, and recorded here when the | ||
| Core implementation exists; implementation gap: Core has no sheet flow to | ||
| measure, and the Lab prototype's counts stand in until it does. Owner exception to INV7's surface budget, which asks that a |
There was a problem hiding this comment.
INV7 accepts an exception only with the measured element count before and after. This paragraph widens the exception to every stacked state and removes the counts. It also says "the Lab prototype's counts stand in", but no Lab count is recorded, and AV5 still points at DEC-3's "measured counts".
f82f842 to
cebf7cb
Compare
|
/approve-spec cebf7cb |
There was a problem hiding this comment.
The engineering-owner approval at this head settles the new sheet flow's direction. A few contradictions inside the record, and with other current records, still need fixing before it can merge:
- FR15a says a commit closes only the top step, but FR13, FR15 and FR16 return to Filters after a commit, which on direct entry means opening Filters as Add filter closes, a same-update replace under
component:BottomSheetFR10 that FR15a and AV5 rule out. - FR16 sends focus after a removal to the next row's remove action, which a read-only row does not have, and it focuses the new row after a creation while the Lab test now listed in
verified_byexpects focus on Add filter. - The verification row still fails "Clear all skips a read-only filter", which FR12 now requires, and still lists a removal from the value step, which FR14 no longer has.
- DEC-3 now rules that the stable popover's Clear all and date editors change their
onChangeoutput, but Compatibility and migration still files DEC-3 as experimental only, whichspec:AST-017FR22 does not allow. - The description and DEC-3 say the record is Lab
MobilePowerSearchas it ships, but Lab falls back to the viewer's zone for date-only values, leaves out nested-only fields, writes its sheet strings in English, and focuses Add filter after a creation. - DEC-3 widens the INV7 layer exception to every stacked state without the element counts
spec:AST-017FR60 requires, and the Lab counts it says stand in are not recorded. - FR12's basis still cites the popover's current clear semantics, which the row now departs from, and FR17 announces three sheet-native differences but lists four.
[Automated review]
| | FR13 | Add filter — from the Filters sheet's Add filter action, or directly when an interactive field with no filter opens — is a tall modal sheet named "Add filter" opened above the Filters sheet, which stays open beneath it (FR15a), so the sheet has no Back action: dismissing it — Escape, a scrim tap, a swipe — reveals Filters again, or closes the flow when Add filter was the entry point, in which case its fixed footer holds Done. Its header — the heading and, when one shows, the search field — stays fixed while the rows scroll. Without a content-search field it lists the visible fields grouped by `group`, each group a named list: the ungrouped fields first, under the heading "General", then each group under its name in order of first appearance, each a row button with the field's icon, a trailing chevron — or, for a field whose only operator takes an `empty` value, a trailing plus, since choosing it adds the filter at once (FR15) — and, once the field count reaches eight, a "Search filters" field above the list that matches labels and `typeaheadAliases` case-insensitively, caps a non-empty query's matches at `maxSearchResults`, and shows the empty state "No filters match your search" when nothing matches. With a `contentSearchFieldKey` whose default operator takes a `string` value it shows instead a "Search" searchbox using `placeholder`, with the same suggestions the popover presentation's typeahead offers listed inline as row buttons ("Search results"): fields, field-and-operator pairs, value matches, and a leading free-text item for the content field; Enter commits the first suggestion, ArrowDown and ArrowUp move between the search and the rows — arrow movement across independently focusable controls, recorded here as this record's exception to `spec:AST-020/FR15`, which adopts arrow keys for a single composite only: the searchbox-to-list continuity is what the popover presentation's combobox gives the same suggestions, and the rows stay in the Tab order as well — an IME-composing Enter does nothing, and a query with no suggestion shows the empty state "No results found" — the searchbox's `aria-controls` names the results list, or that message while nothing lists, so it always names an element; the field list's "No filters match your search" likewise shows only for a non-empty query that matches nothing, never for a config with no field. Each of the two is a compact empty state with a search icon, that title, and the description "Try a different search.", mounted only while the Add filter sheet is the open one. Neither search field takes focus when the sheet opens: the sheet opens with focus on its panel and the software keyboard stays down until the person taps the search; a direct vertical touch drag on the rows while a search field has focus blurs it, so the keyboard leaves and the rows can be read. A suggestion that carries a value commits `onChange([...filters, filter], 'add', filters.length)`, announces `Added {label}` inside the Filters sheet (AR6), and returns to it; any other suggestion or field row opens the value step with that field and operator, above Add filter. Every visible field lists, a field whose every operator is `nested` included: the value step edits every operator's value (FR14). | DEC-3; Lab touch-flow evidence; current typeahead source | Current contract. Implementation gap: no Add filter sheet exists. | | ||
| | FR14 | The value step is a tall modal sheet that edits every operator's value, opened above the sheet that led to it — Filters for an edit, Add filter for a creation — which stays open beneath it (FR15a); its title is the dialog's name (AR5), and for a filter whose field or operator the config no longer has it reads as the Filters row does (FR12): the field's label when the config still has the field, the raw key for whatever is gone. Its header and footer stay fixed while the body scrolls. When the field offers more than one operator the body starts with an "Operator" radio group whose options read "{field label} {operator label}" and list the field's operators, with the operator the step opened with checked — the filter's operator while editing, and while creating the operator the suggestion named (FR13) or else the field's default operator, else its first; otherwise there is no group and the title is "{field label} {operator label}" naming that operator. When no operator label applies the title is the field label. The body below the group is the editor for the checked operator's value type: the sheet-native controls of FR17, or for a `nested` value the popover presentation's built-in nested editor — the sub-filter tree with its Add action, each sub-filter's field, operator, and value controls, and removal — rendered as the popover renders it, with one difference: the step's Operator group takes the place of the editor's root group-operator picker, which is not shown, and the tree root reads the checked operator's label; a sub-filter's controls are the popover editor's own, in their default presentation (a Selector's menu, the Tokenizer's inline field), not the sheet's choice rows (FR17) or Tokenizer sheet (FR21), which govern the step's top-level value only, because the sheet passes no `experimental` option down to them; the tree is its own width container, so below 400 CSS px its rows wrap instead of overflowing the sheet, as they wrap in a narrow popover. A group is created and edited the same way in both presentations; switching the operator swaps the body. A value chosen in the body is staged, not committed: no `onChange` fires until the footer confirms it. Changing the operator keeps a staged value only when the new operator reads the same value type; an `empty` operator stages `{type: 'empty'}`. The body's sections sit on the sheet's surface when the editor is the body's only section; when the body has more than one section — the Operator group above the editor — each section is a muted container, so the grouping reads without a frame around a lone editor. The footer holds Cancel and the confirm action — `popoverSaveButtonLabel` when given, otherwise "Add filter" while creating and "Apply" while editing — disabled until the staged value is complete; the step has no Remove action: a filter is removed from its Filters row (FR12). When `components[type].Editor` is set for the checked operator's value type it replaces the body below the Operator group and the footer — the group stays outside it, so switching operators still swaps the body — receiving the props the popover presentation hands it, with the same meaning: the caller's `config`, the partial `filter`, `mode`, `saveButtonLabel`, `isReadOnly`, `onSave`, and `onCancel`, and nothing the popover does not pass; `onSave(filter)` commits it, `onSave(null)` removes the filter while editing and returns to Filters unchanged while creating. A controlled update that removes the field or operator being edited replaces the body with "This filter is no longer available", announced from its rendered element inside the sheet while the step is the open one (AR6), and keeps Cancel as the way out, the filter's trailing remove action on its Filters row being the removal path (FR12); when focus was on a control of the replaced body it moves to Cancel, so it never falls to the page body inside the open sheet (FR16). | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no value step exists. | | ||
| | FR15 | Confirming a created filter commits `onChange([...filters, filter], 'add', filters.length)`; choosing a field whose only operator takes an `empty` value commits that filter the same way from the Add filter row, with no value step. Tapping an editable Filters row opens the value step on that filter in edit mode; Apply commits `onChange(updated, 'edit', index)`; the row's trailing remove action, or a tap on an `empty`-valued filter's row, commits `onChange(rest, 'remove', index)`, where `index` is the filter's position at commit time: a controlled parent that reorders or clones its filters while the step is open is followed by object identity, then by a structurally equal filter in the original slot, then by a unique structural match; when the target is gone or ambiguous nothing is committed and the step closes back to Filters, focus landing as FR16 states for a row that no longer exists. Every commit from a step closes that step, revealing Filters. While the field is read-only, or disabled, no commit happens. | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no edit path exists. | | ||
| | FR15a | The steps stack: each step opens above the sheet that led to it, which stays open — receded and blocked as a covered sheet of the stack (`component:BottomSheet` FR5, FR6) — so the stack is Filters alone; Filters under Add filter; Filters under the value step for an edit; Filters under Add filter under the value step for a creation; or, when Add filter was the entry point, Add filter alone and then Add filter under the value step. No sheet of the flow carries a Back action: a dismissal — Escape, a scrim tap, a swipe, a platform close request — reaches only the top sheet (`component:BottomSheet` FR7), closes it, and reveals the sheet beneath, which takes focus back as FR16 states; dismissing the bottom sheet of the stack closes the flow. Cancel on the value step is that dismissal; a commit closes the step the same way. The flow therefore has one direction forward (open a step above) and one back (close the top step), and a step never replaces another in place. | DEC-3; `component:BottomSheet` FR5–FR9 | Current contract. Implementation gap: no flow exists. | |
There was a problem hiding this comment.
"a commit closes the step the same way" closes only the top sheet, but FR13, FR15 and FR16 return to Filters after a commit. When Add filter was the entry point there is no Filters beneath, so Filters has to open as Add filter closes, and for a suggestion commit that is a same-update replace under component:BottomSheet FR10, which "a step never replaces another in place" rules out. Could this say a creation's commit closes the value step and Add filter together and shows Filters, opening it on direct entry, and limit "never replaces" to dismissals?
| | FR14 | The value step is a tall modal sheet that edits every operator's value, opened above the sheet that led to it — Filters for an edit, Add filter for a creation — which stays open beneath it (FR15a); its title is the dialog's name (AR5), and for a filter whose field or operator the config no longer has it reads as the Filters row does (FR12): the field's label when the config still has the field, the raw key for whatever is gone. Its header and footer stay fixed while the body scrolls. When the field offers more than one operator the body starts with an "Operator" radio group whose options read "{field label} {operator label}" and list the field's operators, with the operator the step opened with checked — the filter's operator while editing, and while creating the operator the suggestion named (FR13) or else the field's default operator, else its first; otherwise there is no group and the title is "{field label} {operator label}" naming that operator. When no operator label applies the title is the field label. The body below the group is the editor for the checked operator's value type: the sheet-native controls of FR17, or for a `nested` value the popover presentation's built-in nested editor — the sub-filter tree with its Add action, each sub-filter's field, operator, and value controls, and removal — rendered as the popover renders it, with one difference: the step's Operator group takes the place of the editor's root group-operator picker, which is not shown, and the tree root reads the checked operator's label; a sub-filter's controls are the popover editor's own, in their default presentation (a Selector's menu, the Tokenizer's inline field), not the sheet's choice rows (FR17) or Tokenizer sheet (FR21), which govern the step's top-level value only, because the sheet passes no `experimental` option down to them; the tree is its own width container, so below 400 CSS px its rows wrap instead of overflowing the sheet, as they wrap in a narrow popover. A group is created and edited the same way in both presentations; switching the operator swaps the body. A value chosen in the body is staged, not committed: no `onChange` fires until the footer confirms it. Changing the operator keeps a staged value only when the new operator reads the same value type; an `empty` operator stages `{type: 'empty'}`. The body's sections sit on the sheet's surface when the editor is the body's only section; when the body has more than one section — the Operator group above the editor — each section is a muted container, so the grouping reads without a frame around a lone editor. The footer holds Cancel and the confirm action — `popoverSaveButtonLabel` when given, otherwise "Add filter" while creating and "Apply" while editing — disabled until the staged value is complete; the step has no Remove action: a filter is removed from its Filters row (FR12). When `components[type].Editor` is set for the checked operator's value type it replaces the body below the Operator group and the footer — the group stays outside it, so switching operators still swaps the body — receiving the props the popover presentation hands it, with the same meaning: the caller's `config`, the partial `filter`, `mode`, `saveButtonLabel`, `isReadOnly`, `onSave`, and `onCancel`, and nothing the popover does not pass; `onSave(filter)` commits it, `onSave(null)` removes the filter while editing and returns to Filters unchanged while creating. A controlled update that removes the field or operator being edited replaces the body with "This filter is no longer available", announced from its rendered element inside the sheet while the step is the open one (AR6), and keeps Cancel as the way out, the filter's trailing remove action on its Filters row being the removal path (FR12); when focus was on a control of the replaced body it moves to Cancel, so it never falls to the page body inside the open sheet (FR16). | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no value step exists. | | ||
| | FR15 | Confirming a created filter commits `onChange([...filters, filter], 'add', filters.length)`; choosing a field whose only operator takes an `empty` value commits that filter the same way from the Add filter row, with no value step. Tapping an editable Filters row opens the value step on that filter in edit mode; Apply commits `onChange(updated, 'edit', index)`; the row's trailing remove action, or a tap on an `empty`-valued filter's row, commits `onChange(rest, 'remove', index)`, where `index` is the filter's position at commit time: a controlled parent that reorders or clones its filters while the step is open is followed by object identity, then by a structurally equal filter in the original slot, then by a unique structural match; when the target is gone or ambiguous nothing is committed and the step closes back to Filters, focus landing as FR16 states for a row that no longer exists. Every commit from a step closes that step, revealing Filters. While the field is read-only, or disabled, no commit happens. | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no edit path exists. | | ||
| | FR15a | The steps stack: each step opens above the sheet that led to it, which stays open — receded and blocked as a covered sheet of the stack (`component:BottomSheet` FR5, FR6) — so the stack is Filters alone; Filters under Add filter; Filters under the value step for an edit; Filters under Add filter under the value step for a creation; or, when Add filter was the entry point, Add filter alone and then Add filter under the value step. No sheet of the flow carries a Back action: a dismissal — Escape, a scrim tap, a swipe, a platform close request — reaches only the top sheet (`component:BottomSheet` FR7), closes it, and reveals the sheet beneath, which takes focus back as FR16 states; dismissing the bottom sheet of the stack closes the flow. Cancel on the value step is that dismissal; a commit closes the step the same way. The flow therefore has one direction forward (open a step above) and one back (close the top step), and a step never replaces another in place. | DEC-3; `component:BottomSheet` FR5–FR9 | Current contract. Implementation gap: no flow exists. | | ||
| | FR16 | Focus follows the step: after a commit from the value step while creating, the value step and Add filter close and focus lands on the new filter's row in Filters; after a content-search suggestion or an `empty`-valued field row commits a filter directly, that filter's row; after Apply or Cancel from editing, the edited filter's row — followed across a controlled reorder or clone, and, when a controlled update removed it or left two filters it could be, the nearest remaining row that is a button around its former position (the following one on a tie), else Add filter; after a row's trailing remove action, the next remaining row's trailing remove action (the following row, else the previous), so a run of removals needs no re-aiming, else Add filter when no row remains; after Cancel from creation, Add filter is revealed with focus on the field row that opened the step, or on the content search when a suggestion opened it — and when a controlled update has removed that field, or the content-search field itself, the Add filter sheet's first control: its search field when one shows, else its first field row; after Add filter is dismissed, the Filters header's Add filter, or the empty state's; after a caller's `Editor` override calls `onSave(null)` while creating, the same (the flow returns to Filters unchanged, FR14); after Clear all's confirmation closes, the Filters sheet's first remaining control — a kept read-only filter's row has none, so Add filter — with the sheet still open; and, as one rule for the Filters and Add filter sheets, when a controlled update unmounts the control that holds focus — a filter's row or its remove action, a dropped field's row, Clear all gone with the last editable filter — the nearest remaining row that is a button, counted among the button rows by the removed one's index (the following one, else the last) — a plain row (a read-only filter, or every filter while the field is read-only) is passed over — else the sheet's first control (the Add filter sheet's search field, Add filter in the Filters header or empty state), else the sheet's panel, so focus never lands on the page body inside the open sheet; when a controlled update replaces the value step's body with the unavailable state while focus was on one of its controls, Cancel (FR14); after the flow closes, the trigger. A sheet that opens takes focus on its panel, not on a control, except where this row names one; the Add filter sheet's search fields never take it on open (FR13). | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no flow exists. | |
There was a problem hiding this comment.
A read-only row has no remove action (FR12), so "the next remaining row's trailing remove action … else Add filter when no row remains" leaves focus undefined beside a read-only row or when only read-only rows remain. Lab passes over plain rows and falls back to Add filter when no editable row remains. Separately, after a creation Lab's suite (now in verified_by) expects focus on Add filter, not the new row.
| | PR1–PR4 | Instrumented unit tests plus first-frame real-Chromium capture | Open, close, reopen opposite side; hidden placement commit; first painted frame | Render-time reads, repeated reads, observers, pointer tracking, or a default-position flash fails. | `audit:PowerSearch/performance` | | ||
| | FR8, AR1–AR3 | Existing PowerSearch/editor suites plus focused browser checks | Pointer and keyboard edit, apply, cancel, Escape, light dismiss, focus return, selection, semantics | Focus, semantics, selection, dismissal, or reachability changes fail. | `audit:PowerSearch/accessibility` | | ||
| | FR22 | PowerSearch and Tokenizer unit tests | A read-only field with a filter: no Clear all, no token remove control, Backspace on the empty input | A read-only field removes a filter through Backspace, Clear all, or a token control. | `audit:PowerSearch/interaction` | | ||
| | FR10–FR21, AR4–AR6, PR5 | Focused PowerSearch sheet tests with the compact-touch query stubbed both ways, plus real-engine touch evidence (implementation gap: not yet in the tree; the Lab prototype's suite, `packages/lab/src/MobilePowerSearch/MobilePowerSearch.test.tsx`, is the current evidence for the flow the rows describe) | default popover, forced sheet, adaptive both ways (server markup and hydration as the popover field on a coarse-pointer client, then the trigger, no recoverable error, focus on the body; with `hasAutoFocus`, the autofocused input's focus ending on the trigger with no `onBlur` or `onFocus` to the caller), trigger name, Filters/Add filter/value steps, each value type, list value through the Tokenizer sheet, Clear all, read-only, disabled while open, focus after each step, focus after an `adaptive` switch from the trigger, the input, a removed control, an open editor popover, and from outside the field, a field inside an open Dialog announcing the count, Clear all, and in-sheet messages inside that Dialog and never on the page body, StrictMode result count, in-sheet live region while a sheet is open (added, removed, count, empty results), Clear all announced only after the dialog's exit, row, token and Removed text sharing one formatter, a date-only value as its day in `timezoneID`, else the UTC day, and a date-and-time value in `timezoneID`, else the viewer's zone, on all three (UTC, Tokyo, Honolulu, unset), summaries and relative-date labels and values from the catalog, the row and Removed through one reorderable message, an unmatched relative-date value as stored, a filter the config no longer knows listed and removable, a cleared number unstaged, choice rows as radios and checkboxes with `aria-checked`, a stale read-only filter as a plain row, a removal next to a nested row focusing that row, a nested filter removed from its step, a list value joined by the locale's narrow conjunction list in English, Simplified Chinese, Japanese, and German, an empty-result message spoken once per query from its rendered element, a controlled update that removes the edited field moving focus from the replaced body to Cancel and announcing the unavailable state in the sheet while the step is open and never from a step beneath another or retained closed, Apply and Cancel after a controlled update removed the edited filter focusing the nearest row, else Add filter, a row's trailing remove action focusing the next row's remove action, a one-tap `empty`-valued filter added from its field row and removed from its row, Clear all behind its confirmation keeping read-only filters and leaving Filters open, a dismissed step revealing the step beneath with focus restored to it, Add filter opening with its search unfocused and a touch drag on the rows blurring a focused search, a stale filter's step titled as its row reads (raw keys for what is gone, the field's label when the field remains), Cancel from creation after the opening field was removed focusing the Add filter sheet's first control, a removed focused Filters row, a removed Clear all, and a dropped focused field row each handing focus to the nearest remaining row or the sheet's first control, element counts at rest and with each of the Filters, Add filter and value sheets open, and with the Tokenizer's list-value sheet stacked above the value step, under fine and coarse pointers, nothing of the flow in the DOM at rest before a press and after the exit, a nested filter edited and created through the built-in nested editor (its sub-filter rows wrapping inside a 390 and a 414 px sheet; its root operator picker absent on a mixed field, the Operator group in its place), a nested-only field and its suggestion offered, a suggestion's operator checked over the field's default, the Operator group outside a caller's Editor override on a mixed field, a count changed during the exit spoken with Clear all's message as one catalog message after the close, a radio group's arrow keys, Home, and End moving and checking, Space and Enter checking a focused radio row and toggling a checkbox row, right-to-left layout with the row chevrons and the rows' chevrons mirrored in RTL (`design:bidirectionality/DR4`) | A field without `experimental` changes beyond what FR19 states, a value commits before the step confirms, Clear all skips a read-only filter, the trigger carries native `disabled`, focus lands elsewhere, an announcement reaches a channel outside the modal that holds the field at that moment, or the popover bundle carries the sheets. | `audit:PowerSearch/sheet` | |
There was a problem hiding this comment.
This row's failure signal still fails "Clear all skips a read-only filter", which FR12 now requires. It also keeps "a nested filter removed from its step" (FR14: the step has no Remove action) and "a removal next to a nested row focusing that row" beside FR16's remove-action target.
| sheet needs a Back action. Closed vocabularies are rows; list values edit | ||
| through the Tokenizer's sheet presentation titled with the field label; a | ||
| `nested` value uses the popover presentation's built-in nested editor; a | ||
| date-only value edits on an inline Calendar in `timezoneID`, else UTC, and a |
There was a problem hiding this comment.
Lab falls back to the viewer's zone for a date-only value when timezoneID is unset (unixSecondsToISODate and isoDateToUnixSeconds in mobilePowerSearchUtils.ts), so "else UTC" is not the flow "as MobilePowerSearch ships it". Lab also leaves out nested-only fields and writes its sheet strings in English.
| `document.body.querySelectorAll('*').length`, at rest and with each state of | ||
| the stack open, under a fine and a coarse pointer, and recorded here when the | ||
| Core implementation exists; implementation gap: Core has no sheet flow to | ||
| measure, and the Lab prototype's counts stand in until it does. Owner exception to INV7's surface budget, which asks that a |
There was a problem hiding this comment.
spec:AST-017 FR60 accepts a budget exception only "recorded with its element counts". This exception now covers every stacked state with no counts, and the "Lab prototype's counts" it says stand in are not recorded anywhere. AV5 still bounds the flow by DEC-3's "measured counts".
| Owner rulings of 2026-11-02 cover both presentations of one field. Clear all | ||
| keeps read-only filters, which are the consumer's, and reports `remove` at the | ||
| first removed index. A date-only `date_absolute` value, and each absolute part | ||
| of a `date_range`, is one day in one zone: its day in `timezoneID` when the prop | ||
| is set, else the UTC day, read and written alike. A `date_absolute` value whose | ||
| operator sets `isDateOnly: false` is an instant with a time of day, read and | ||
| written in `timezoneID`, else the viewer's zone. The sheet presentation follows | ||
| these rulings (FR11, FR12, FR17); where the popover presentation does not follow | ||
| them yet, the known gap below records it. |
There was a problem hiding this comment.
These rulings change what the stable popover's Clear all and date editors report through onChange. spec:AST-017 FR22 keeps such changes outside the experimental exception, but Compatibility and migration still files DEC-3 as the experimental option only. Either leave the popover rulings to the pull requests that change the popover, or classify each popover change there under FR5.
cebf7cb to
cf4a0ae
Compare
|
/approve-spec cf4a0ae |
There was a problem hiding this comment.
The engineering-owner approval at this head settles the sheet flow's direction. Three fixes inside the record and its description remain before it can merge:
- FR15a and AV5 cite
component:BottomSheetFR10's same-update replace for a creation committed from the value step after a direct entry, but that commit closes two sheets, one of them covered, which FR10 excludes; and FR14'sonSave(null)while creating opens Filters the same way, against FR15a's "the one place a sheet opens as others close". - The title and description still say the record is the Lab flow "as it ships" and that Lab "implements every behavior the record states", while DEC-3 lists six differences, and that list misses a seventh: AR6 announces
Added {label}for anempty-valued field row, which the Lab prototype does not. - The
spec:AST-086/FR14known gap in Open questions still routes "AR6 Clear all's after-close message" to the modal that holds the field, but AR6 now announces Clear all inside the still-open Filters sheet.
[Automated review]
| | FR13 | Add filter — from the Filters sheet's Add filter action, or directly when an interactive field with no filter opens — is a tall modal sheet named "Add filter" opened above the Filters sheet, which stays open beneath it (FR15a), so the sheet has no Back action: dismissing it — Escape, a scrim tap, a swipe — reveals Filters again, or closes the flow when Add filter was the entry point, in which case its fixed footer holds Done. Its header — the heading and, when one shows, the search field — stays fixed while the rows scroll. Without a content-search field it lists the visible fields grouped by `group`, each group a named list: the ungrouped fields first, under the heading "General", then each group under its name in order of first appearance, each a row button with the field's icon, a trailing chevron — or, for a field whose only operator takes an `empty` value, a trailing plus, since choosing it adds the filter at once (FR15) — and, once the field count reaches eight, a "Search filters" field above the list that matches labels and `typeaheadAliases` case-insensitively, caps a non-empty query's matches at `maxSearchResults`, and shows the empty state "No filters match your search" when nothing matches. With a `contentSearchFieldKey` whose default operator takes a `string` value it shows instead a "Search" searchbox using `placeholder`, with the same suggestions the popover presentation's typeahead offers listed inline as row buttons ("Search results"): fields, field-and-operator pairs, value matches, and a leading free-text item for the content field; Enter commits the first suggestion, ArrowDown and ArrowUp move between the search and the rows — arrow movement across independently focusable controls, recorded here as this record's exception to `spec:AST-020/FR15`, which adopts arrow keys for a single composite only: the searchbox-to-list continuity is what the popover presentation's combobox gives the same suggestions, and the rows stay in the Tab order as well — an IME-composing Enter does nothing, and a query with no suggestion shows the empty state "No results found" — the searchbox's `aria-controls` names the results list, or that message while nothing lists, so it always names an element; the field list's "No filters match your search" likewise shows only for a non-empty query that matches nothing, never for a config with no field. Each of the two is a compact empty state with a search icon, that title, and the description "Try a different search.", mounted only while the Add filter sheet is the open one. Neither search field takes focus when the sheet opens: the sheet opens with focus on its panel and the software keyboard stays down until the person taps the search; a direct vertical touch drag on the rows while a search field has focus blurs it, so the keyboard leaves and the rows can be read. A suggestion that carries a value commits `onChange([...filters, filter], 'add', filters.length)`, announces `Added {label}` inside the Filters sheet (AR6), and shows Filters as FR15a states; any other suggestion or field row opens the value step with that field and operator, above Add filter. Every visible field lists, a field whose every operator is `nested` included: the value step edits every operator's value (FR14). | DEC-3; Lab touch-flow evidence; current typeahead source | Current contract. Implementation gap: no Add filter sheet exists. | | ||
| | FR14 | The value step is a tall modal sheet that edits every operator's value, opened above the sheet that led to it — Filters for an edit, Add filter for a creation — which stays open beneath it (FR15a); its title is the dialog's name (AR5), and for a filter whose field or operator the config no longer has it reads as the Filters row does (FR12): the field's label when the config still has the field, the raw key for whatever is gone. Its header and footer stay fixed while the body scrolls. When the field offers more than one operator the body starts with an "Operator" radio group whose options read "{field label} {operator label}" and list the field's operators, with the operator the step opened with checked — the filter's operator while editing, and while creating the operator the suggestion named (FR13) or else the field's default operator, else its first; otherwise there is no group and the title is "{field label} {operator label}" naming that operator. When no operator label applies the title is the field label. The body below the group is the editor for the checked operator's value type: the sheet-native controls of FR17, or for a `nested` value the popover presentation's built-in nested editor — the sub-filter tree with its Add action, each sub-filter's field, operator, and value controls, and removal — rendered as the popover renders it, with one difference: the step's Operator group takes the place of the editor's root group-operator picker, which is not shown, and the tree root reads the checked operator's label; a sub-filter's controls are the popover editor's own, in their default presentation (a Selector's menu, the Tokenizer's inline field), not the sheet's choice rows (FR17) or Tokenizer sheet (FR21), which govern the step's top-level value only, because the sheet passes no `experimental` option down to them; the tree is its own width container, so below 400 CSS px its rows wrap instead of overflowing the sheet, as they wrap in a narrow popover. A group is created and edited the same way in both presentations; switching the operator swaps the body. A value chosen in the body is staged, not committed: no `onChange` fires until the footer confirms it. Changing the operator keeps a staged value only when the new operator reads the same value type; an `empty` operator stages `{type: 'empty'}`. The body's sections sit on the sheet's surface when the editor is the body's only section; when the body has more than one section — the Operator group above the editor — each section is a muted container, so the grouping reads without a frame around a lone editor. The footer holds Cancel and the confirm action — `popoverSaveButtonLabel` when given, otherwise "Add filter" while creating and "Apply" while editing — disabled until the staged value is complete; the step has no Remove action: a filter is removed from its Filters row (FR12). When `components[type].Editor` is set for the checked operator's value type it replaces the body below the Operator group and the footer — the group stays outside it, so switching operators still swaps the body — receiving the props the popover presentation hands it, with the same meaning: the caller's `config`, the partial `filter`, `mode`, `saveButtonLabel`, `isReadOnly`, `onSave`, and `onCancel`, and nothing the popover does not pass; `onSave(filter)` commits it, `onSave(null)` removes the filter while editing and returns to Filters unchanged while creating. A controlled update that removes the field or operator being edited replaces the body with "This filter is no longer available", announced from its rendered element inside the sheet while the step is the open one (AR6), and keeps Cancel as the way out, the filter's trailing remove action on its Filters row being the removal path (FR12); when focus was on a control of the replaced body it moves to Cancel, so it never falls to the page body inside the open sheet (FR16). | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no value step exists. | | ||
| | FR15 | Confirming a created filter commits `onChange([...filters, filter], 'add', filters.length)`; choosing a field whose only operator takes an `empty` value commits that filter the same way from the Add filter row, with no value step. Tapping an editable Filters row opens the value step on that filter in edit mode; Apply commits `onChange(updated, 'edit', index)`; the row's trailing remove action, or a tap on an `empty`-valued filter's row, commits `onChange(rest, 'remove', index)`, where `index` is the filter's position at commit time: a controlled parent that reorders or clones its filters while the step is open is followed by object identity, then by a structurally equal filter in the original slot, then by a unique structural match; when the target is gone or ambiguous nothing is committed and the step closes back to Filters, focus landing as FR16 states for a row that no longer exists. Every commit shows the Filters sheet as FR15a states. While the field is read-only, or disabled, no commit happens. | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no edit path exists. | | ||
| | FR15a | The steps stack: each step opens above the sheet that led to it, which stays open — receded and blocked as a covered sheet of the stack (`component:BottomSheet` FR5, FR6) — so the stack is Filters alone; Filters under Add filter; Filters under the value step for an edit; Filters under Add filter under the value step for a creation; or, when Add filter was the entry point, Add filter alone and then Add filter under the value step. No sheet of the flow carries a Back action: a dismissal — Escape, a scrim tap, a swipe, a platform close request — reaches only the top sheet (`component:BottomSheet` FR7), closes it, and reveals the sheet beneath, which takes focus back as FR16 states; dismissing the bottom sheet of the stack closes the flow. Cancel on the value step is that dismissal. A commit closes the step that made it: an edit's commit reveals Filters beneath the value step; a creation's commit, from the value step or from an Add filter row or suggestion that commits directly, closes Add filter, and the value step above it, together and shows Filters — revealed when it is open beneath them, else opened in the same update that closes them, BottomSheet's same-update replace (`component:BottomSheet` FR10), so the person sees the filter just added. The flow therefore has one direction forward (open a step above) and one back (close the top step): no dismissal replaces a step, and a creation's commit from a direct entry is the one place a sheet opens as others close. | DEC-3; `component:BottomSheet` FR5–FR9 | Current contract. Implementation gap: no flow exists. | |
There was a problem hiding this comment.
component:BottomSheet FR10 applies only when exactly one sheet closes and one opens, and keeps "a covered sheet closing, several sheets changing" on the ordinary push and pop. After a direct entry, a commit from the value step closes Add filter (covered) and the value step together, so it is not an FR10 replace; only the direct commit from an Add filter row or suggestion is. FR14's onSave(null) while creating also opens Filters as two sheets close, which this row's "the one place a sheet opens as others close" rules out.
| date-and-time control in `timezoneID`, else the viewer's zone; every other value | ||
| type uses the editor the popover presentation uses. Clear all asks for | ||
| confirmation, removes the filters the person can edit, keeps read-only ones, and | ||
| leaves Filters open. Core's contract differs from the prototype in six places: a |
There was a problem hiding this comment.
A seventh difference: AR6 announces Added {label} for an empty-valued field row, but the prototype's one-tap field path commits and focuses the new row without an announcement; only its suggestion path announces.
cf4a0ae to
e8bc534
Compare
|
/approve-spec e8bc534 |
There was a problem hiding this comment.
The engineering-owner approval at this head settles the sheet flow's direction, and the fixes from the last round have landed. Two fixes remain before it can merge:
- AV5 keeps the open flow within the popover editor's element count plus the 5-element open-layer allowance for each covered sheet, but DEC-3's INV7 exception says each covered sheet is a whole dialog over that allowance, and the prototype counts it records exceed AV5's ceiling in every stacked state, so AV5 should leave the covered sheets to DEC-3's exception.
- The description's Decision section still says every direct-entry creation opens Filters through BottomSheet FR10's same-update replace, and its authority list says FR10 is no longer used, while FR15a uses FR10 only when a lone Add filter closes.
[Automated review]
| open layer, no content mounted twice, and the open flow under a coarse pointer | ||
| within the popover presentation's open editor under a fine pointer plus the | ||
| open-layer allowance (`spec:AST-017/FR60`, `DEC-29`) for each sheet open | ||
| beneath the top one, with the counts DEC-3 records. The Tokenizer's list-value |
There was a problem hiding this comment.
This keeps the stacked flow within the popover editor's count plus 5 elements per covered sheet (339 with one covered sheet, 344 with two), but DEC-3's INV7 exception says each covered sheet is a whole dialog over that allowance, and its prototype counts (340, 413, 366, 429) already exceed it. Could AV5 bound only the single-sheet states this way and leave each sheet open beneath the top one to DEC-3's owner exception, as the earlier wording did?
e8bc534 to
7a5874f
Compare
|
/approve-spec 7a5874f |
There was a problem hiding this comment.
The engineering-owner approval at this head settles the sheet flow's direction, and both fixes from the last round have landed. Three record fixes remain before it can merge:
- FR14 still lets a caller's
Editoroverride remove a filter from the value step withonSave(null), but AR6 now announcesRemoved {label}only for row removals and FR16 names a focus target foronSave(null)only while creating, so name that removal in both rows. - DEC-3 says Core differs from the prototype in seven places, but FR14 passes an
Editoroverride nothing the popover does not while the prototype also passes ittimezoneID, so list that difference or drop the count. - The internationalization known gap in Open questions still says the trigger shows the popover token “as it is”, which FR11's
timezoneIDdate reading now contradicts for dates.
Two optional cleanups: say which element budget covers Filters beneath the Clear all confirmation, and fix the repeated “row chevrons and the rows' chevrons” in the verification row.
[Automated review]
| same FR14 route (FR19). Focus moves as FR16 states and returns to the | ||
| trigger after the flow closes. Implementation gap: no flow exists. | ||
| the flow makes — `Added {label}` for a committed suggestion or an | ||
| `empty`-valued field row, `Removed {label}` for a filter removed by its row's |
There was a problem hiding this comment.
FR14 still lets a caller's Editor override remove a filter from the value step with onSave(null) while editing, but this clause now names Removed {label} only for row removals, and FR16 names a focus target for onSave(null) only while creating. Name the override's removal here and in FR16's removal rule.
| date-and-time control in `timezoneID`, else the viewer's zone; every other value | ||
| type uses the editor the popover presentation uses. Clear all asks for | ||
| confirmation, removes the filters the person can edit, keeps read-only ones, and | ||
| leaves Filters open. Core's contract differs from the prototype in seven places: |
There was a problem hiding this comment.
The list is not complete: FR14 hands an Editor override nothing the popover does not pass, and the popover passes no timezoneID, while the prototype passes timezoneID to it. List that difference, or replace the count with “including”.
| @@ -640,15 +701,6 @@ would go with the rest). | |||
| (FR11); changing the | |||
There was a problem hiding this comment.
“Shows the popover token as it is on the trigger (FR11)” no longer holds for dates: FR11 reads a date_absolute value on the trigger in timezoneID, else the UTC day, with a time for isDateOnly: false, and says the popover's token does not. Suggest: “shows the popover token's text on the trigger, except dates, which FR11 reads in timezoneID”.
|
/approve-spec 16b80e7 |
7a5874f to
16b80e7
Compare
There was a problem hiding this comment.
The engineering-owner approval at this head settles the sheet flow's direction, and the three fixes from the last round have landed. One record fix remains before it can merge:
- FR16 sends focus to Add filter whenever the Clear all confirmation closes, including Keep filters, Escape and a scrim tap, but
component:DialogAR2 returns focus to the Clear all button that is still there, so limit that clause to a confirmed Clear all and say a dismissed confirmation returns focus to Clear all.
Optional cleanups: the internationalization known gap in Open questions starts with a doubled list marker, its last sentence has a stray ,; and leaves out FR11's fallback zones, and AR5 could name the confirmation's role and first focus (Lab uses an alert dialog with Keep filters focused first).
[Automated review]
| | FR14 | The value step is a tall modal sheet that edits every operator's value, opened above the sheet that led to it — Filters for an edit, Add filter for a creation — which stays open beneath it (FR15a); its title is the dialog's name (AR5), and for a filter whose field or operator the config no longer has it reads as the Filters row does (FR12): the field's label when the config still has the field, the raw key for whatever is gone. Its header and footer stay fixed while the body scrolls. When the field offers more than one operator the body starts with an "Operator" radio group whose options read "{field label} {operator label}" and list the field's operators, with the operator the step opened with checked — the filter's operator while editing, and while creating the operator the suggestion named (FR13) or else the field's default operator, else its first; otherwise there is no group and the title is "{field label} {operator label}" naming that operator. When no operator label applies the title is the field label. The body below the group is the editor for the checked operator's value type: the sheet-native controls of FR17, or for a `nested` value the popover presentation's built-in nested editor — the sub-filter tree with its Add action, each sub-filter's field, operator, and value controls, and removal — rendered as the popover renders it, with one difference: the step's Operator group takes the place of the editor's root group-operator picker, which is not shown, and the tree root reads the checked operator's label; a sub-filter's controls are the popover editor's own, in their default presentation (a Selector's menu, the Tokenizer's inline field), not the sheet's choice rows (FR17) or Tokenizer sheet (FR21), which govern the step's top-level value only, because the sheet passes no `experimental` option down to them; the tree is its own width container, so below 400 CSS px its rows wrap instead of overflowing the sheet, as they wrap in a narrow popover. A group is created and edited the same way in both presentations; switching the operator swaps the body. A value chosen in the body is staged, not committed: no `onChange` fires until the footer confirms it. Changing the operator keeps a staged value only when the new operator reads the same value type; an `empty` operator stages `{type: 'empty'}`. The body's sections sit on the sheet's surface when the editor is the body's only section; when the body has more than one section — the Operator group above the editor — each section is a muted container, so the grouping reads without a frame around a lone editor. The footer holds Cancel and the confirm action — `popoverSaveButtonLabel` when given, otherwise "Add filter" while creating and "Apply" while editing — disabled until the staged value is complete; the step has no Remove action: a filter is removed from its Filters row (FR12). When `components[type].Editor` is set for the checked operator's value type it replaces the body below the Operator group and the footer — the group stays outside it, so switching operators still swaps the body — receiving the props the popover presentation hands it, with the same meaning: the caller's `config`, the partial `filter`, `mode`, `saveButtonLabel`, `isReadOnly`, `onSave`, and `onCancel`, and nothing the popover does not pass; `onSave(filter)` commits it, `onSave(null)` removes the filter while editing and returns to Filters unchanged while creating. A controlled update that removes the field or operator being edited replaces the body with "This filter is no longer available", announced from its rendered element inside the sheet while the step is the open one (AR6), and keeps Cancel as the way out, the filter's trailing remove action on its Filters row being the removal path (FR12); when focus was on a control of the replaced body it moves to Cancel, so it never falls to the page body inside the open sheet (FR16). | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no value step exists. | | ||
| | FR15 | Confirming a created filter commits `onChange([...filters, filter], 'add', filters.length)`; choosing a field whose only operator takes an `empty` value commits that filter the same way from the Add filter row, with no value step. Tapping an editable Filters row opens the value step on that filter in edit mode; Apply commits `onChange(updated, 'edit', index)`; the row's trailing remove action, or a tap on an `empty`-valued filter's row, commits `onChange(rest, 'remove', index)`, where `index` is the filter's position at commit time: a controlled parent that reorders or clones its filters while the step is open is followed by object identity, then by a structurally equal filter in the original slot, then by a unique structural match; when the target is gone or ambiguous nothing is committed and the step closes back to Filters, focus landing as FR16 states for a row that no longer exists. Every commit shows the Filters sheet as FR15a states. While the field is read-only, or disabled, no commit happens. | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no edit path exists. | | ||
| | FR15a | The steps stack: each step opens above the sheet that led to it, which stays open — receded and blocked as a covered sheet of the stack (`component:BottomSheet` FR5, FR6) — so the stack is Filters alone; Filters under Add filter; Filters under the value step for an edit; Filters under Add filter under the value step for a creation; or, when Add filter was the entry point, Add filter alone and then Add filter under the value step. No sheet of the flow carries a Back action: a dismissal — Escape, a scrim tap, a swipe, a platform close request — reaches only the top sheet (`component:BottomSheet` FR7), closes it, and reveals the sheet beneath, which takes focus back as FR16 states; dismissing the bottom sheet of the stack closes the flow. Cancel on the value step is that dismissal. A commit closes the step that made it: an edit's commit reveals Filters beneath the value step; a creation's commit, from the value step or from an Add filter row or suggestion that commits directly, closes Add filter, and the value step above it, together and shows Filters, so the person sees the filter just added: Filters is revealed when it is open beneath them; after a direct entry it opens in the same update that closes them — BottomSheet's same-update replace (`component:BottomSheet` FR10) when Add filter alone closes, and the stack's ordinary closes and open (`component:BottomSheet` FR5–FR9) when the value step closes with it. A creating `Editor` override's `onSave(null)` (FR14) shows Filters the same way. The flow therefore has one direction forward (open a step above) and one back (close the top step): no dismissal replaces a step, and Filters opens as other sheets close only when a creation ends after a direct entry. | DEC-3; `component:BottomSheet` FR5–FR9 | Current contract. Implementation gap: no flow exists. | | ||
| | FR16 | Focus follows the step: after a commit from the value step while creating, the value step and Add filter close and focus lands on the Filters header's Add filter, ready for the next filter; after a content-search suggestion or an `empty`-valued field row commits a filter directly, that filter's row; after Apply or Cancel from editing, the edited filter's row — followed across a controlled reorder or clone, and, when a controlled update removed it or left two filters it could be, the nearest remaining row that is a button around its former position (the following one on a tie), else Add filter; after a row's trailing remove action, or a caller's `Editor` override's `onSave(null)` while editing, which closes the value step, the trailing remove action of the nearest remaining row that has one (the following one on a tie) — a plain row, a read-only filter's, has none and is passed over — so a run of removals needs no re-aiming, else Add filter when no such row remains; after Cancel from creation, Add filter is revealed with focus on the field row that opened the step, or on the content search when a suggestion opened it — and when a controlled update has removed that field, or the content-search field itself, the Add filter sheet's first control: its search field when one shows, else its first field row; after Add filter is dismissed, the Filters header's Add filter, or the empty state's; after a caller's `Editor` override calls `onSave(null)` while creating, the same (the flow returns to Filters unchanged, FR14); after Clear all's confirmation closes, the Filters sheet's first remaining control — a kept read-only filter's row has none, so Add filter — with the sheet still open; and, as one rule for the Filters and Add filter sheets, when a controlled update unmounts the control that holds focus — a filter's row or its remove action, a dropped field's row, Clear all gone with the last editable filter — the nearest remaining row that is a button, counted among the button rows by the removed one's index (the following one, else the last) — a plain row (a read-only filter, or every filter while the field is read-only) is passed over — else the sheet's first control (the Add filter sheet's search field, Add filter in the Filters header or empty state), else the sheet's panel, so focus never lands on the page body inside the open sheet; when a controlled update replaces the value step's body with the unavailable state while focus was on one of its controls, Cancel (FR14); after the flow closes, the trigger. A sheet that opens takes focus on its panel, not on a control, except where this row names one; the Add filter sheet's search fields never take it on open (FR13). | DEC-3; Lab touch-flow evidence | Current contract. Implementation gap: no flow exists. | |
There was a problem hiding this comment.
"after Clear all's confirmation closes, the Filters sheet's first remaining control … so Add filter" also covers Keep filters, Escape and a scrim tap, when Clear all is still there. component:Dialog AR2 returns focus to a still-connected invoking control, and Lab's confirmation does. Suggest: "after a confirmed Clear all, the Filters sheet's first remaining control …; after Keep filters, Escape or a scrim tap, Clear all".
| sheet's is a stable change outside this experimental option (Compatibility and | ||
| migration). | ||
|
|
||
| - - Known gap, pre-existing in the popover presentation, against |
There was a problem hiding this comment.
Optional: the doubled marker (- - Known gap) renders this gap as a nested bullet under an empty one; one - and no blank line above keeps it a sibling of the other known gaps.
| relative-date value as stored). The sheet presentation resolves all of these | ||
| from the catalog, or the locale's formats, on its own surfaces (FR12, FR20) | ||
| and shows the popover token's text on the trigger, except a date, which FR11 | ||
| reads in `timezoneID`,; changing the token's and the editor's own text is a |
There was a problem hiding this comment.
Optional: stray comma in "timezoneID,;". FR11 also falls back to the UTC day, or to the viewer's zone for a time of day; suggest "except a date, which FR11 reads in timezoneID or its fallback zone;".
| rows are buttons named by their visible text, and a Filters row's trailing | ||
| remove action is a button named "Remove {field label} filter" with that name | ||
| as its tooltip; the Clear all confirmation is a modal dialog named "Clear all | ||
| filters?" whose actions read "Clear filters" and "Keep filters"; a choice row |
There was a problem hiding this comment.
Optional: Lab's confirmation is an alertdialog with Keep filters focused first, the safe-first shape Tokenizer's sheet confirmation uses. Naming the role and the first focus here keeps a stray Enter from clearing the filters.
…ow (DEC-3) The owner named Lab MobilePowerSearch's current flow as the target for Core's experimental sheet presentation. The record now describes that flow as Core's contract. - Steps stack and stay open beneath the next (FR15a). A dismissal reveals the previous step, and no sheet carries Back. A creation's commit shows Filters. - Headers, search fields and footers stay fixed while only the rows scroll. - Every removable Filters row has a trailing remove action, one-tap boolean filters included. The value step has no Remove. - Add filter's search stays unfocused until tapped. Its empty results are a compact empty state, and its ungrouped fields sit under "General". - A lone editor sits on the sheet surface. The empty Filters state is one centered Add filter. - Clear all sits behind a confirmation, keeps read-only filters and leaves Filters open. - A date-only value edits as its day in timezoneID, else UTC. A value with a time of day edits in timezoneID, else the viewer's zone. DEC-3 lists where Core's contract differs from the prototype. The rules for identity, stale filters, focus recovery and announcements only from the open step are unchanged. The presentation stays unexposed, so every sheet row keeps its implementation-gap note. The sheet's Clear all matches the popover presentation's (FR23). The popover's date handling is stable behavior outside this experimental option, and the known gap lists where it differs from the sheet.
|
/approve-spec b93e733 |
16b80e7 to
b93e733
Compare
There was a problem hiding this comment.
The engineering-owner approval at this head settles the sheet flow, and the last round's fix has landed: FR16 now returns focus to Clear all when Keep filters, Escape or a scrim tap dismisses its confirmation, and moves it to Add filter only after a confirmed clear. The optional cleanups are in too (the confirmation is an alertdialog with Keep filters focused first, and the internationalization gap's list marker and date wording are fixed).
No blocking findings remain, so nothing further is needed from the review's side.
[Automated review]
Exact decision needed
Which flow Core PowerSearch's
experimental.presentationsheet surface specifies. The current record describes the first Core draft's flow: steps that replace one another in place with a Back action on Add filter, Remove in the value step's footer, Clear all that removes every filter and closes the flow, the Add filter search focused on arrival, and date editors on the UTC day. Lab'sMobilePowerSearchhas since moved on (its "refine touch filter flow" change and the keyboard and confirmation pieces shared withMobileTokenizer), and the owner has decided its current flow is what Core should ship.Existing authority searched
component:PowerSearch(docs/specs/components/PowerSearch/PowerSearch.spec.md, current): DEC-3 and FR10–FR21, AR4–AR6, PR5, AV5–AV6 own the sheet presentation; this PR changes those claims and no popover-presentation claim (FR1–FR9, DEC-1, DEC-2).component:BottomSheet(current): FR5–FR9 own the stack — open order, covered sheets receded and blocked, dismissal at the top sheet, a nested sheet presented only while its host is open, focus returning into the revealed sheet — which the new FR15a leans on rather than restating; FR10's same-update replace is used only when a direct commit from Add filter, entered directly, closes Add filter alone as Filters opens.component:BottomSheetSwitcherDEP-0015: the deprecated predecessor; the record's last mention of it as a fallback host is removed.component:TokenizerDEC-3, FR16–FR24: the list-value sheet the value step opens, now one more sheet of the same stack (FR21).spec:AST-017FR60 / DEC-29 (open-layer allowance),spec:AST-020FR15 (arrow keys, the existing exception kept),spec:AST-085FR1/FR2/FR4 (disabled trigger),spec:AST-086FR3/FR4/FR14 (in-modal announcement route).isDateOnly: falsethe time of day it names #9702 carry the same rulings for the popover presentation. The merged Lab changes are the evidence, not authority.No current claim settles which of the two flows Core specifies; DEC-3 did, and this PR revises DEC-3.
Decision
The sheet presentation follows the Lab touch flow of
MobilePowerSearch. The record states it as Core's contract, and DEC-3 lists eight places where Core's contract differs from the prototype, as "including", so the list does not claim to be complete:timezoneIDis its UTC day, where the prototype reads the viewer's zone;emptyfilter added from its field row is announced;Editoroverride receives only what the popover passes it (the prototype also passestimezoneID);The decision itself:
removewithout opening the step; a one-tap boolean (empty-valued) filter's row removes on tap and is added from its field row with no value step; the value step has no Remove. After a removal, focus lands on the remove action of the nearest remaining row that has one, passing over read-only rows, else on Add filter. After a creation commits from the value step, focus lands on the Filters header's Add filter. A direct add, from a suggestion that carries a value or a one-tapemptyfield, focuses the new row. All of this is as Lab does.timezoneID, else the UTC day. A value whose operator setsisDateOnly: falseedits on a touch-sized date-and-time input, as wall-clock time intimezoneID, else the viewer's zone (FR17).onChange(kept, 'remove', firstRemovedIndex)keeping read-only filters, leaves Filters open with focus on its first control, and announces inside the sheet once the confirmation closes. The words are "Editable filters removed" while a read-only filter remains, else "All filters removed", the popover presentation's words, with the result count when it changes.date_absolutevalue as the sheet's editors do. A date-only value is its day intimezoneID, else the UTC day. A value whose operator setsisDateOnly: falseis its date and time intimezoneID, else the viewer's zone.removereported at the first removed index, one zone for a date-only value, and the viewer's zone as the fallback for a value with a time. The owner ruled the same for the popover presentation. That change alters what the stable presentation reports throughonChange, so this experimental record does not make it (spec:AST-017/FR22). A Compatibility bullet says so, and the in-flight popover fixes classify it under FR5. The Open questions known gap lists where the popover presentation onmainstill differs: its clear control, its date editor and token, and itsisDateOnly: falseeditor. The two decided open questions (Clear all and read-only filters; a date-and-time editor) are removed. So is the known gap about dates the sheet inherited from the popover, which the rulings replace.Rejected alternatives
mainare a known gap in Open questions.Claim scope and non-goals
Only the sheet presentation's rows (FR10–FR21, FR15a, AR4–AR6, PR5, AV5–AV6, the representative states, DEC-3) and the derived gap. The popover presentation's clear control, date editors and token are not changed here. The in-flight popover fixes change them, and this record lists their difference on
mainas a known gap. Once those fixes merge, the gap's sentences go; the sheet strings' catalog keys (FR20) are unchanged; no theming target, no new prop, no change toadaptive's resolution (FR10). For the INV7 layer exception, DEC-3 now records element counts, asspec:AST-017/FR60asks:Core's own counts are recorded with its implementation.
Evidence and implementation relationship
MobilePowerSearchonmain(packages/lab/src/MobilePowerSearch/MobilePowerSearch.tsx, its test suite, now listed inverified_byas the flow's current evidence) implements the flow the record states, except the differences DEC-3 lists. The record was written against that source, not from memory.main(FR5–FR9, the nested-sheet drag fix and the replace-alignment fix).main. The Core implementation PR (feat(core): PowerSearch touch sheet flow behind experimental.presentation #8001) is being reworked to implement it, and it lifts these gap notes when it ships.Changes in this head (fifth review round)
component:DialogAR2 also requires.alertdialogthat focuses Keep filters when it opens, as Lab's does, so a stray Enter keeps the filters.,;; both are fixed. Its date exception now says "timezoneIDor its fallback zone".Changes in the previous head (fourth review round)
Editoroverride that callsonSave(null)while editing is now named in AR6, which announcesRemoved {label}, and in FR16's removal focus rule.Editoroverride receives only what the popover presentation passes it, while the prototype also passestimezoneID.timezoneID.Changes in the previous head (third review round, rebased onto
mainafter #9676)Changes in the previous head (second review round)
Editoroverride'sonSave(null)(FR14) shows Filters the same way. The closing sentence of FR15a and AV5 now say so.Added {label}for a one-tapemptyfield row. The PR title no longer says "as it ships", and the description no longer says Lab implements every behavior.spec:AST-086/FR14known gap is removed, because it is closed onmain:useAnnouncedelivers inside the enclosing Astryx modal, and Dialog and the modal Drawer provide that route. FR19's gap is reworded to what holds onmain: the closed field has no mount announcement under StrictMode and routes into its modal, and no sheet exists to carry the in-sheet route. Its old text said the guard still announced under StrictMode, which is fixed onmain.Changes in the previous head (first review round)
spec:AST-017/FR22). The known gap no longer says that the popover "follows the rulings".MobilePowerSearchships it". It lists Core's six differences from the prototype.spec:AST-017/FR60). Core's own counts are recorded with its implementation.Writing rubric results
#+ digits,/pull/,/issues/).mainbeside the rulings.Scope
component:BottomSheet's and leaves the composition free).component:BottomSheet, because dismissal, focus return and the announcement route depend on them.component:PowerSearchDEC-3), affected current claims and overlapping open work are named; the one collision is called out: the Core implementation PR, which now rebases onto this record.Validation
pnpm check:knowledge— "Knowledge templates and records are aligned."git diff --check— clean.