diff --git a/.cursor/rules/exploration-design.mdc b/.cursor/rules/exploration-design.mdc index 85bca5e0..8ab61838 100644 --- a/.cursor/rules/exploration-design.mdc +++ b/.cursor/rules/exploration-design.mdc @@ -32,9 +32,11 @@ Reference: `SegmentedToggleUIC` — Osaka | Amsterdam on `eip-7708` and `eip-803 Mobile stacks bands (steps → toggles → examples+Run grid). Desktop: steps left; toggles + action cluster right. Omit unused slots — do not leave empty flex wrappers. -On **touch**, `ActionButtonUIC` shows the Run tooltip as **inline** copy under the button. That caption must **not** set the action-row width (otherwise the examples select collapses to a chevron). Inline hints use `w-0 min-w-full`; the Run host is `flex min-w-0`, not `inline-flex`. The examples column is `minmax(10rem, 1fr)`. +On **touch**, `ActionButtonUIC` shows the Run tooltip as **inline** copy. That caption belongs on a **full-width row under** examples + Run — never in the Run column (a `w-0 min-w-full` caption beside a short “Run” label stacks one word per line). `.e-widget-chrome-run` uses `display: contents` below `md` so the hint can take `grid-area: hint`. -**Form-style explorations** (precompile E-Component, bytecode stepper, 7594) keep examples above the editor; they do **not** use `WidgetChromeUIC`. Bytecode Run/Step/Reset use **`decorative`** hints (no inline caption on touch). 7594 Run sits on its own row under a full-width examples select — same inline-width rule still applies. +**Form-style explorations** (precompile E-Component, bytecode stepper, 7594) keep examples above the editor; they do **not** use `WidgetChromeUIC`. Bytecode Run/Step/Reset use **`decorative`** hints (no inline caption on touch). 7594 Run sits on its own row under a full-width examples select. + +**Layout / touch:** jsdom and default Cypress still report `(hover: hover) and (pointer: fine)`. After widget-chrome or Run-hint layout changes, run `cypress/e2e/widget-chrome-touch.cy.ts` (412px + `matchMedia` hover stub) before finishing. ## State chrome: selected must be obvious diff --git a/cypress/e2e/widget-chrome-touch.cy.ts b/cypress/e2e/widget-chrome-touch.cy.ts new file mode 100644 index 00000000..eb983e63 --- /dev/null +++ b/cypress/e2e/widget-chrome-touch.cy.ts @@ -0,0 +1,35 @@ +import { visitAsTouch } from '../support/touchVisit' + +/** + * Widget chrome on a phone-sized viewport with hover disabled. + * Catches Run-hint / examples-select collisions that jsdom and default Cypress miss. + */ +describe('Widget chrome on touch', () => { + it('keeps the 8038 example title readable beside Run, hint below the row', () => { + visitAsTouch('/eip-8038-state-access-gas') + cy.get('#eip-8038-c', { timeout: 10000 }).should('exist') + + cy.get('[data-testid="example-select"]') + .should('be.visible') + .and('contain.text', 'Update existing slot') + .then(($el) => { + expect($el[0].getBoundingClientRect().width).to.be.greaterThan(160) + }) + + cy.get('[data-testid="run-program"]').should('be.visible') + + cy.get('#eip-8038-c .help-hint-inline') + .should('be.visible') + .and('contain.text', 'split touch') + .then(($hint) => { + const hint = $hint[0].getBoundingClientRect() + expect(hint.width, 'hint should span the chrome row, not a word column').to.be.greaterThan( + 200, + ) + expect(hint.height, 'hint should wrap as a caption, not a tall stack').to.be.lessThan(80) + + const select = Cypress.$('[data-testid="example-select"]')[0].getBoundingClientRect() + expect(hint.top, 'hint sits under examples + Run').to.be.at.least(select.bottom - 12) + }) + }) +}) diff --git a/cypress/support/touchVisit.ts b/cypress/support/touchVisit.ts new file mode 100644 index 00000000..c528bce7 --- /dev/null +++ b/cypress/support/touchVisit.ts @@ -0,0 +1,33 @@ +/// + +/** + * Pixel-ish width plus no-hover matchMedia. Default Cypress/Electron still + * reports (hover: hover) and (pointer: fine), so touch inline hints never mount. + */ +export function visitAsTouch(path: string) { + cy.viewport(412, 915) + cy.visit(path, { + onBeforeLoad(win) { + const native = win.matchMedia.bind(win) + win.matchMedia = ((query: string) => { + if (query === '(hover: hover) and (pointer: fine)') { + return { + matches: false, + media: query, + onchange: null, + addListener() {}, + removeListener() {}, + addEventListener() {}, + removeEventListener() {}, + dispatchEvent() { + return false + }, + } as MediaQueryList + } + return native(query) + }) as typeof win.matchMedia + }, + }) +} + +export {} diff --git a/src/eComponents/ui/HelpHintInlineUIC.vue b/src/eComponents/ui/HelpHintInlineUIC.vue index 3b238850..72f9ee47 100644 --- a/src/eComponents/ui/HelpHintInlineUIC.vue +++ b/src/eComponents/ui/HelpHintInlineUIC.vue @@ -8,7 +8,7 @@ defineProps<{