docs(sw): AI rules, example E, tests and evidence for the popup / footer / header changes

AI documents (packages/sw/ai)
- SWPopupPropertyRow, SWSelect, SWSlider in the catalog, usage table,
  composition patterns and design rules; SWViewerSlider noted as SWSlider panel.
- Rule: the SWHeader layer / color dropdowns are header-only; popup layer /
  color / line style use SWPopupPropertyRow (property window pickers).
- Footer rules: gra-border-orange surfaces, Model / Layout text unchanged on
  hover, side button hover, vacant surface limited to the slot row.
- Header rules: measured More growing behavior (hysteresis fix A).
- Example E (custom popup) uses SWPopupPropertyRow, SWSelect and SWSlider
  instead of SWHeaderLayerMenu in the popup.

Tests (packages/sw/tests)
- New footer-popup-viewer-refinement.browser.mjs (47 checks: footer stories,
  hover / gradient / tab text / pocket, popup pickers vs property window rows,
  SWSlider, unit SWSelect parity, Popup B, header trace, viewer title art).
- integrated-popup / integrated-consumer: popup pickers and SWSelect instead of
  the header dropdown and the HM UI Select (same overlay / callback / Escape
  requirements).
- integrated-improvement: default sections skip the retired SW/Tool story
  ('tool' still runs by name); storybook-inventory / simplification record the
  retired footer IDs; theme-inheritance widths use sw-footer--full-footer.

Docs (docs/sw)
- sw-footer-popup-viewer-refinement-2026-10-20.md: causes, decisions (the
  first-pass "header dropdown in popup" change is marked withdrawn),
  verification and baseline failures; evidence footer-popup-viewer-refinement-qa
  (47/47). Footer mapping document updated for the removed sections.

Baseline failures (unchanged by this work): home-menu-integration 1,
storybook-unused-removal 5 (10-19 snapshot comparisons), range-cross-browser and
property-panel-comparison open stories retired on 2026-10-19.
This commit is contained in:
shkim4 committed 2026-10-20 15:20:27 +09:00
1 parent 126a35d60d
commit 454472d344
24 files changed
+1211 -52

No files matched your search

+18 -15
View File
@@ -18,31 +18,34 @@ export async function popupChecks({ page, goto, check, log, rect, out, report })
check('popup content: SWPopupRow is the property row (72px label, 32px row, 28px box)', rowStyle.label === '72px' && rowStyle.minH === '32px' && rowStyle.box === '28px', rowStyle)
await settings.locator('[data-ui-switch]').click()
check('popup content: HM UI Switch works inside', (await log()).includes('snap=false'))
await settings.locator('[data-ui-select]').click()
await page.getByRole('option', { name: 'cm' }).click()
check('popup content: HM UI Select (portal) works inside', (await log()).includes('unit=cm'))
// 2026-10-20: the unit select is SWSelect (the Viewer 축척 지정 select), no longer the HM UI Select.
await settings.locator('[data-sw-select="unit"]').selectOption('cm')
check('popup content: SWSelect (unit) works inside', (await log()).includes('unit=cm') && await settings.getByLabel('단위').inputValue() === 'cm')
await settings.locator('[data-ui-slider] [role="slider"]').focus()
await page.keyboard.press('ArrowRight')
await page.waitForTimeout(100)
check('popup content: HM UI Slider works inside', /opacity=70/.test(await log()), await log())
await settings.locator('[data-ui-apply]').click()
check('popup content: HM UI Button works inside', (await log()).includes('apply'))
const layerField = settings.locator('[data-popup-layer] button.hm-sw-header__field')
// 2026-10-20: popup layer / color are the property window pickers (SWPopupPropertyRow); SWHeader field dropdowns are
// header-only. The same clipping / overlay / callback / Escape requirements apply to the popup pickers.
check('popup dropdown: no SWHeader field dropdown inside the popups', await page.locator('[data-popups] .hm-sw-header__field').count() === 0)
const layerField = settings.locator('[data-property-id="layer"] .hm-sw-property__trigger')
await layerField.click()
const fieldBox = await rect(layerField), menu = settings.locator('.hm-sw-header__layer-menu')
const fieldBox = await rect(settings.locator('[data-property-id="layer"] .hm-sw-property__value')), menu = page.locator('.hm-sw-property__drop[data-picker="layer"]')
const menuBox = await rect(menu)
const hit = await menu.locator('.hm-sw-header__layer-row').last().evaluate(row => { const r = row.getBoundingClientRect(); const e = document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2); return row.contains(e) })
check('popup dropdown: SW layer dropdown opens right under its field (fixed overlay) and is not clipped', await menu.evaluate(e => getComputedStyle(e).position) === 'fixed' && Math.abs(menuBox.y - (fieldBox.y + fieldBox.h)) < 1 && Math.abs(menuBox.x - fieldBox.x) < 1 && hit, { fieldBox, menuBox, hit })
check('popup dropdown: popup layer picker opens right under its field (fixed overlay, right edge) and is not clipped', await menu.evaluate(e => getComputedStyle(e).position) === 'fixed' && Math.abs(menuBox.y - (fieldBox.y + fieldBox.h)) < 1 && Math.abs((menuBox.x + menuBox.w) - (fieldBox.x + fieldBox.w)) < 1 && hit, { fieldBox, menuBox, hit })
await page.locator('[data-popups]').screenshot({ path: path.join(out, 'popup-custom-dropdown.png') })
await menu.locator('.hm-sw-header__layer-item', { hasText: 'DIM' }).click()
check('popup dropdown: selection callback, list closes, popup stays', (await log()).includes('layer=dim') && await settings.locator('.hm-sw-header__layer-menu').count() === 0 && await settings.isVisible())
await settings.locator('[data-popup-color] button.hm-sw-header__field').click()
const colorPanel = settings.locator('.hm-sw-header__menu-panel')
check('popup dropdown: SW color dropdown becomes a fixed overlay', await colorPanel.evaluate(e => getComputedStyle(e).position) === 'fixed')
check('popup dropdown: selection callback, list closes, popup stays', (await log()).includes('layer=dim') && await menu.count() === 0 && await settings.isVisible())
await settings.locator('[data-property-id="color"] .hm-sw-property__swatch-area').click()
const colorPanel = page.locator('.hm-sw-property__drop[data-picker="color"]')
check('popup dropdown: popup color picker becomes a fixed overlay', await colorPanel.evaluate(e => getComputedStyle(e).position) === 'fixed')
await colorPanel.getByLabel('초록').click()
check('popup dropdown: color chip callback', (await log()).includes('color=green'))
await page.keyboard.press('Escape')
check('popup dropdown: Escape closes the dropdown only', await settings.locator('.hm-sw-header__menu-panel').count() === 0 && await settings.isVisible())
check('popup dropdown: color chip callback', (await log()).includes('color=0,255,0'), await log())
if (await colorPanel.count()) { await colorPanel.getByLabel('초록').focus(); await page.keyboard.press('Escape') }
check('popup dropdown: Escape closes the dropdown only', await colorPanel.count() === 0 && await settings.isVisible())
const object = page.locator('[data-popup="object"]')
check('object popup: read-only rows', await object.getByLabel('이름').evaluate(e => e.readOnly) && await object.getByLabel('길이').evaluate(e => e.readOnly))
@@ -62,8 +65,8 @@ export async function popupChecks({ page, goto, check, log, rect, out, report })
await goto('sw-popup--multiple-popups')
const popup = id => page.locator(`[data-popup="${id}"]`)
const b = popup('b')
await b.locator('[data-popup-layer] button.hm-sw-header__field').click()
const bMenu = b.locator('.hm-sw-header__layer-menu'), bHit = await bMenu.locator('.hm-sw-header__layer-footer').evaluate(row => { const r = row.getBoundingClientRect(); return row.contains(document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2)) })
await b.locator('[data-property-id="layer"] .hm-sw-property__trigger').click()
const bMenu = page.locator('.hm-sw-property__drop[data-picker="layer"]'), bHit = await bMenu.locator('.hm-sw-header__layer-footer').evaluate(row => { const r = row.getBoundingClientRect(); return row.contains(document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2)) })
const bBox = await rect(b), menuB = await rect(bMenu)
check('short popup: dropdown extends past the popup and stays fully visible (no clipping)', menuB.y + menuB.h > bBox.y + bBox.h && bHit, { bBox, menuB, bHit })
await page.locator('[data-stack]').screenshot({ path: path.join(out, 'popup-short-dropdown.png') })