refactor(sw): simplify storybook navigation and examples

This commit is contained in:
shkim4 committed 2026-10-20 13:17:15 +09:00
1 parent 47972c55ff
commit 2b0e1a2262
19 files changed
+438 -92

No files matched your search

@@ -21,6 +21,11 @@ export const retiredToolIds = ["sw-tool--tool-buttons"]
// SW/Header as sw-header--home-menu (sidebar) and sw-header--home-menu-* (verification tag, reachable by URL).
export const movedHomeMenuIds = Object.fromEntries(["figma-home-on:figma", "capture-default", "capture-active", "capture-outline", "capture-recent", "header-composition", "variants", "dynamic-data", "standalone", "recent-files-panel:recent-files", "property-bar-check", "capture-property-bar", "integration", "capture-responsive"]
.map(entry => entry.split(":")).map(([from, to = from]) => [`sw-home-menu--${from}`, `sw-header--home-menu-${to}`]))
// 2026-10-20 (SW Storybook simplification, user request): Figma screenshot comparisons and duplicate Variant pages that
// no test or document used were removed; the Figma originals stay in docs/sw/figma-additional/screenshots/.
export const retiredHomeMenuCompareIds = ["figma", "capture-active", "capture-outline", "capture-recent", "variants", "recent-files"].map(id => `sw-header--home-menu-${id}`)
// 2026-10-20: Property Panel · 4개 비교 moved to SW/Popup in the sidebar; its ID is unchanged (meta id "sw-header").
export const movedToPopupIds = ["sw-header--property-panel-compare"]
export const integratedChangedFiles = [
'packages/sw/README.md', 'packages/sw/package.json', 'packages/sw/build.mjs',
'packages/sw/ai/AI_USAGE.md', 'packages/sw/ai/COMPONENT_CATALOG.md', 'packages/sw/ai/COMPOSITION_PATTERNS.md', 'packages/sw/ai/DESIGN_RULES.md',
@@ -0,0 +1,64 @@
// 2026-10-20 SW Storybook simplification: sidebar order, hidden verification Stories, removed IDs, direct URL render of
// every SW Story and the moved Property Panel (SW/Popup, ID sw-header--property-panel-compare).
// Usage: build Storybook first, then node packages/sw/tests/storybook-simplification.browser.mjs [evidence-dir]
import { createRequire } from 'node:module'
import { readFile, writeFile, mkdir } from 'node:fs/promises'
import path from 'node:path'
import http from 'node:http'
const runtime = process.env.SW_RUNTIME_MODULES ?? 'C:/Users/user/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules'
const require = createRequire(path.join(runtime, 'index.js'))
const { chromium } = require('playwright')
const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-simplification-qa'); await mkdir(out, { recursive: true })
const root = path.resolve('storybook-static'), mime = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.ttf': 'font/ttf', '.woff2': 'font/woff2' }
const server = http.createServer(async (req, res) => { try { const p = new URL(req.url, 'http://localhost').pathname, file = path.resolve(root, '.' + decodeURIComponent(p === '/' ? '/index.html' : p)); res.setHeader('Content-Type', mime[path.extname(file)] ?? 'application/octet-stream'); res.end(await readFile(file)) } catch { res.writeHead(404).end() } })
await new Promise(r => server.listen(0, '127.0.0.1', r)); const base = `http://127.0.0.1:${server.address().port}`
const browser = await chromium.launch({ headless: true, executablePath: process.env.SW_CHROME_PATH ?? 'C:/Program Files/Google/Chrome/Application/chrome.exe' })
const checks = []; const check = (n, ok, d) => { checks.push({ n, ok, d }); console.log(ok ? 'PASS' : 'FAIL', n, ok ? '' : JSON.stringify(d)) }
const index = JSON.parse(await readFile(path.join(root, 'index.json'), 'utf8'))
const sw = Object.values(index.entries).filter(e => e.type === 'story' && e.title.startsWith('SW/'))
// 1. every SW story renders by direct URL without page errors
const page = await browser.newPage({ viewport: { width: 1920, height: 1000 } })
let errors = []; page.on('pageerror', e => errors.push(e.message)); page.on('console', m => { if (m.type() === 'error') errors.push(m.text()) })
const bad = []
for (const e of sw) {
errors = []
await page.goto(`${base}/iframe.html?id=${e.id}&viewMode=story`)
try { await page.locator('#storybook-root > *').first().waitFor({ timeout: 15000 }) } catch { bad.push([e.id, 'no root']) ; continue }
await page.waitForTimeout(150)
const err = await page.locator('.sb-show-errordisplay').count()
if (err || errors.length) bad.push([e.id, errors.slice(0, 2)])
}
check(`direct URL render: ${sw.length} SW Stories, no error display / page error`, bad.length === 0, bad)
// 2. removed IDs are gone
const removed = ['figma', 'capture-active', 'capture-outline', 'capture-recent', 'variants', 'recent-files'].map(id => `sw-header--home-menu-${id}`)
check('removed Home Menu IDs absent from index', removed.every(id => !index.entries[id]))
// 3. Property panel in Popup: four states
await page.goto(`${base}/iframe.html?id=sw-header--property-panel-compare&viewMode=story`)
await page.locator('[data-compare]').first().waitFor()
await page.waitForTimeout(300)
const cells = await page.locator('[data-compare]').evaluateAll(list => list.map(c => { const r = c.querySelector('.hm-sw-property').getBoundingClientRect(); return [c.dataset.compare, Math.round(r.width), Math.round(r.height)] }))
check('Popup › Property Panel: four states none/all/circle/expanded at 314 × 256/361/658/754', JSON.stringify(cells) === JSON.stringify([['none', 314, 256], ['all', 314, 361], ['circle', 314, 658], ['expanded', 314, 754]]), cells)
await page.screenshot({ path: path.join(out, 'popup-property-panel-compare.png'), fullPage: true })
// 4. manager sidebar
const m = await browser.newPage({ viewport: { width: 1600, height: 2400 } })
await m.goto(`${base}/?path=/story/sw-header--overview`)
await m.locator('[data-item-id="sw-header--overview"]').waitFor()
for (const c of ['sw-footer', 'sw-popup', 'sw-viewer', 'sw-theme']) { const b = m.locator(`[data-item-id="${c}"] > button[aria-expanded="false"]`); if (await b.count()) await b.click() }
await m.waitForTimeout(500)
const rows = await m.locator('#storybook-explorer-tree [data-item-id^="sw-"]').evaluateAll(list => list.map(e => [e.dataset.itemId, e.dataset.nodetype, e.textContent.replace('Skip to content', '').trim()]))
await writeFile(path.join(out, 'sidebar.json'), JSON.stringify(rows, null, 1))
const comps = rows.filter(r => r[1] === 'component').map(r => r[2])
check('sidebar SW order Header → Footer → Popup → Viewer → Theme', JSON.stringify(comps) === JSON.stringify(['Header', 'Footer', 'Popup', 'Viewer', 'Theme']), comps)
const hidden = ['sw-header--window-move-motion', 'sw-header--k-guide', 'sw-footer--figma-variants', 'sw-header--detail-buttons', 'sw-header--home-menu-capture-default']
check('verification Stories not in sidebar', rows.every(r => !hidden.includes(r[0])), rows.filter(r => hidden.includes(r[0])))
const popupRows = await m.locator('#storybook-explorer-tree [data-item-id]').evaluateAll(list => list.map(e => e.dataset.itemId))
const pi = popupRows.indexOf('sw-popup')
check('Popup children order: Custom Content, Multiple Popups, Property Panel', JSON.stringify(popupRows.slice(pi + 1, pi + 4)) === JSON.stringify(['sw-popup--custom-content', 'sw-popup--multiple-popups', 'sw-header--property-panel-compare']), popupRows.slice(pi + 1, pi + 4))
const headerIds = rows.filter(r => r[0].startsWith('sw-header--')).map(r => r[0])
check('Property Panel listed once (under Popup only)', headerIds.filter(id => id === 'sw-header--property-panel-compare').length === 1)
await m.locator('[data-item-id="sw-header--property-panel-compare"]').click(); await m.waitForTimeout(800)
check('sidebar link opens moved Story with same ID', m.url().includes('sw-header--property-panel-compare'), m.url())
await m.screenshot({ path: path.join(out, 'sidebar.png'), clip: { x: 0, y: 0, width: 320, height: 2400 } })
await writeFile(path.join(out, 'result.json'), JSON.stringify(checks, null, 1))
console.log(checks.filter(c => c.ok).length + '/' + checks.length + ' PASS')
await browser.close(); server.close()
@@ -8,7 +8,7 @@ import { execFileSync } from 'node:child_process'
import path from 'node:path'
import http from 'node:http'
import { parse } from '@babel/parser'
import { swStoryEntries, retiredUnusedDemoIds, integratedStoryIds, integratedChangedFiles } from './storybook-inventory.mjs'
import { swStoryEntries, retiredUnusedDemoIds, integratedStoryIds, integratedChangedFiles, movedToPopupIds } from './storybook-inventory.mjs'
const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-unused-story-removal-qa/verify')
const qa = 'docs/sw/storybook-unused-story-removal-qa/before'
@@ -92,7 +92,10 @@ try {
check('removed URL renders no SW Story', await page.locator('.hm-sw-header, .hm-sw-theme').count() === 0)
report.errors.length = 0 // the missing-story error above is expected; start the regression clean
for (const entry of afterSW) {
// 2026-10-20: Property Panel · 4개 비교 moved to SW/Popup (same sw-header-- ID), so it is added to the render pass.
const moved = movedToPopupIds.map(id => afterIndex.entries[id]).filter(Boolean)
check('inventory: moved Popup Story keeps its sw-header-- ID under SW/Popup', moved.length === movedToPopupIds.length && moved.every(e => e.title === 'SW/Popup'), moved.map(e => [e.id, e.title]))
for (const entry of [...afterSW, ...moved]) {
await goto(entry.id.replace('sw-header--', ''))
check(`render ${entry.id}`, !(await page.locator('.sb-errordisplay').isVisible()))
}