From 2ae8f394378dbf1a7351e430fbc61753e20adb8c Mon Sep 17 00:00:00 2001 From: jack755051 Date: Sat, 22 Aug 2026 12:39:17 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(docs):=20P29=20Phase=204=20=E2=80=94?= =?UTF-8?q?=20component=20docs=20scan=20efficiency=20and=20evidence=20surf?= =?UTF-8?q?ace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Converges the metadata/evidence layer built up in earlier Phase 4 work into a single component reference surface across all 52 component pages: header promotes install command/package path into the primary manifest panel with Installation/API/Recent changes jump anchors (Radio's split group/item API gets its own #api-group anchor); previewer keeps Preview/Source simultaneously visible under 01/02 labels instead of hiding either behind tabs; API reference gains a row-count header, numbering, and type code pills; recent changes becomes a 3-entry compact release strip. Co-Authored-By: Claude Sonnet 5 --- DEVLOG.md | 14 ++ TODOLIST.md | 18 +- apps/docs/DOCS_VISUAL_SYSTEM.md | 5 + apps/docs/e2e/component-page.spec.ts | 34 ++++ .../e2e/phase4-visual-verification.spec.ts | 51 ++++- apps/docs/src/app/i18n/locales/en/common.ts | 14 +- apps/docs/src/app/i18n/locales/zh/common.ts | 11 + .../component-page-api-table.component.ts | 112 +++++++--- ...component-page-code-previewer.component.ts | 72 ++++++- .../component-page-header.component.ts | 191 ++++++++++++------ ...component-page-recent-changes.component.ts | 79 +++++--- .../components/radio/radio-page.component.ts | 1 + 12 files changed, 465 insertions(+), 137 deletions(-) diff --git a/DEVLOG.md b/DEVLOG.md index 37666696..5bc9867c 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -661,6 +661,20 @@ P9 golden fixture 掃完 53 元件後發現一批長期存在的 registry 宣告 **範圍限制**:Component Docs 全面掃描效率與工程證據尚未完成,所以本輪只用 `/components/button` 作為 representative component page;其餘 component docs 完成後,應用同一套驗證重新覆蓋。 +## P29 Phase 4 — Component Docs 掃描效率與工程證據收斂(2026-08-22) + +**已完成**:本輪將先前已建立的 metadata/evidence 資料層收斂成一套明確的 component reference surface,改動全部落在共用 `component-page-*` primitives,因此一次覆蓋 52 個 component pages: + +- Header 把 install command 與 package path 提升成主要 manifest panel;registry name、shipped status、shared deps、SSR/browser boundary、a11y、keyboard、state model 與 latest version 收斂成第二層 evidence chips,並新增 Installation/API/Recent changes 快速 anchor。Radio 因 API 拆成 group/item 兩張表,專用 anchor 指到 `#api-group`。 +- Previewer 採 `01 Preview` / `02 Source` 兩區清楚標頭與 rendered/copy-ready 狀態,並把預覽高度壓到 desktop 320px、mobile 280px。沒有重新引入 Preview/Code tabs:先前使用者已要求撤回會隱藏 code 的 tabs,所以這次保留 demo/source 同時可見,用層級而非隱藏來提升掃描效率。 +- API reference 新增 row count/reference header、編號、type code pill 與更緊湊的 desktop columns;mobile 維持 cards,但將 type/default 改成雙欄、description 獨立收尾,長型別與預設值仍可斷行。 +- Recent changes 從一般頁尾面板改成最多 3 筆的 compact release strip,保留 version/date/type/breaking/text 與完整 changelog 入口。 +- 安裝指令在手機版改用 surface 內橫向捲動,避免 CLI token 在單字中間斷行;頁面本身仍維持零水平 overflow。 + +**視覺觀察**:Button 作為簡單基準時,manifest 與 Preview/Source 層級可在第一屏辨識;Dialog 的 keyboard/a11y/state evidence 可正常換行;Table 的寬 demo 與高密度 API 沒有撐破 content rail。Light/dark 的 command surface 均保持深色 code 語彙,mint 只用在 status/index/active signal。Mobile header 會把 command/path 疊成單欄,evidence chips 與 jump links 自然換行;API cards 在 360px/390px 仍可掃描。 + +**驗證**:`pnpm exec tsc --noEmit`、scoped ESLint、`git diff --check` 通過;Angular development bundle 冷啟動成功。Playwright 更新 component-page assertions 來覆蓋 install command、Installation/API anchors、Preview/Source 同時可見、API reference surface、recent changes 與 Radio 的 `#api-group`;Phase 4 視覺矩陣擴充為 Button/Dialog/Table 的 desktop light、desktop dark、360px、390px,並對 header/previewer/API/release strip 另拍局部截圖。Desktop Chromium **11 passed**、mobile Chromium **11 passed**。 + --- ## P30 — `checkbox` Enter 鍵「無法切換」查證後確認不是缺口 diff --git a/TODOLIST.md b/TODOLIST.md index 1f9400fd..25b1ae96 100644 --- a/TODOLIST.md +++ b/TODOLIST.md @@ -44,18 +44,18 @@ Phase 4 已解封:Playwright 截圖 + `Read` 工具可以實際檢視 home(light #### Component Docs — 掃描效率與工程證據 -- [ ] 翻新 component docs:component page header、examples、installation、API table、recent changes 超出「符合規範」以外的視覺層次與掃描效率提升 -- [ ] Component header 補強工程 metadata 呈現:registry name、install command、package path、stability/status、updated/recent changes affordance -- [ ] Example previewer 強化 Preview / Code / Install / API 的切換與視覺階層,讓使用者更快定位可複製資訊 -- [ ] API table 朝 dense reference surface 調整:提高欄位掃描效率,但保留 mobile card layout 的可讀性 -- [ ] Recent changes 改成 compact release strip,避免像頁尾附錄 -- [ ] 補一致的 evidence chips:a11y、keyboard support、controlled/uncontrolled、SSR/browser-only、registry deps 等,把 Sanring 的工程品質變成可見資產 +- [x] 翻新 component docs:component page header、examples、installation、API table、recent changes 超出「符合規範」以外的視覺層次與掃描效率提升 +- [x] Component header 補強工程 metadata 呈現:registry name、install command、package path、stability/status、updated/recent changes affordance +- [x] Example previewer 強化 Preview / Code / Install / API 的切換與視覺階層,讓使用者更快定位可複製資訊 +- [x] API table 朝 dense reference surface 調整:提高欄位掃描效率,但保留 mobile card layout 的可讀性 +- [x] Recent changes 改成 compact release strip,避免像頁尾附錄 +- [x] 補一致的 evidence chips:a11y、keyboard support、controlled/uncontrolled、SSR/browser-only、registry deps 等,把 Sanring 的工程品質變成可見資產 #### Verification — 視覺驗證 -- [ ] 每次 Phase 4 改動後用 Playwright 重拍 home light/dark/mobile、代表性 long-form page、代表性 component page -- [ ] 檢查 `360px` / `390px` 無水平 overflow,長 command/code line 不撐破版面,中英文文案長度不互相遮擋 -- [ ] 完成後將具體設計決策、截圖觀察與驗證結果同步到 `DEVLOG.md` +- [x] 每次 Phase 4 改動後用 Playwright 重拍 home light/dark/mobile、代表性 long-form page、代表性 component page +- [x] 檢查 `360px` / `390px` 無水平 overflow,長 command/code line 不撐破版面,中英文文案長度不互相遮擋 +- [x] 完成後將具體設計決策、截圖觀察與驗證結果同步到 `DEVLOG.md` --- diff --git a/apps/docs/DOCS_VISUAL_SYSTEM.md b/apps/docs/DOCS_VISUAL_SYSTEM.md index 590a8feb..99bdef55 100644 --- a/apps/docs/DOCS_VISUAL_SYSTEM.md +++ b/apps/docs/DOCS_VISUAL_SYSTEM.md @@ -292,6 +292,10 @@ Rules: - Major preview container uses `--sanring-radius-lg`. - Stage must have stable min height and responsive padding. +- Preview and source stay simultaneously visible. Do not hide either zone behind tabs; side-by-side + comparison while scrolling is more useful than reducing vertical space. +- Label the zones as `01 Preview` and `02 Source`; installation and API remain one-hop anchor targets + from the component header. - Code block scrolls horizontally internally; it must not widen the page. - Copy code action is always visible and keyboard accessible. @@ -312,6 +316,7 @@ Mobile: ### Recent Changes Recent changes are a supporting surface, not the main page ending. +Render at most three entries in a compact release strip; link to the changelog for full history. - Limit to current component. - Keep compact rows. diff --git a/apps/docs/e2e/component-page.spec.ts b/apps/docs/e2e/component-page.spec.ts index ba8883bc..f6bca059 100644 --- a/apps/docs/e2e/component-page.spec.ts +++ b/apps/docs/e2e/component-page.spec.ts @@ -7,6 +7,13 @@ test.describe('component page', () => { await expect(page.locator('h1')).toContainText('Button'); await expect(page.locator('#basic')).toBeVisible(); await expect(page.locator('#api')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Copy install command' })).toBeVisible(); + await expect(page.locator('a[href="#installation"]').first()).toBeVisible(); + await expect(page.locator('a[href="#api"]').first()).toBeVisible(); + await expect(page.getByRole('group', { name: 'Preview' }).first()).toBeVisible(); + await expect(page.getByRole('group', { name: 'Source' }).first()).toBeVisible(); + await expect(page.getByText('Reference surface')).toBeVisible(); + await expect(page.locator('#recent-changes')).toBeVisible(); }); test('code block copy action is keyboard accessible', async ({ page }) => { @@ -17,4 +24,31 @@ test.describe('component page', () => { await copyButton.focus(); await expect(copyButton).toBeFocused(); }); + + test('keeps component reference pages inside 360px and 390px viewports', async ({ page }) => { + for (const width of [360, 390]) { + await page.setViewportSize({ width, height: 800 }); + + for (const route of ['/components/button', '/components/dialog', '/components/table']) { + await page.goto(route); + await expect(page.locator('h1')).toBeVisible(); + + const { scrollWidth, clientWidth } = await page.evaluate(() => ({ + scrollWidth: document.documentElement.scrollWidth, + clientWidth: document.documentElement.clientWidth, + })); + + expect( + scrollWidth, + `${route} overflows at ${width}px: ${scrollWidth}px > ${clientWidth}px`, + ).toBeLessThanOrEqual(clientWidth); + } + } + }); + + test('uses the radio group API anchor for its split reference tables', async ({ page }) => { + await page.goto('/components/radio'); + + await expect(page.locator('a[href="#api-group"]').first()).toBeVisible(); + }); }); diff --git a/apps/docs/e2e/phase4-visual-verification.spec.ts b/apps/docs/e2e/phase4-visual-verification.spec.ts index 48c79f1e..8e7a5620 100644 --- a/apps/docs/e2e/phase4-visual-verification.spec.ts +++ b/apps/docs/e2e/phase4-visual-verification.spec.ts @@ -1,14 +1,18 @@ import { expect, test, type Page } from '@playwright/test'; const LONG_FORM_ROUTES = ['/introduction', '/cli', '/registry', '/mcp', '/theming']; -const REPRESENTATIVE_ROUTES = ['/', ...LONG_FORM_ROUTES, '/components/button']; +const COMPONENT_ROUTES = ['/components/button', '/components/dialog', '/components/table']; +const REPRESENTATIVE_ROUTES = ['/', ...LONG_FORM_ROUTES, ...COMPONENT_ROUTES]; async function expectNoPageOverflow(page: Page) { const result = await page.evaluate(() => ({ scrollWidth: document.documentElement.scrollWidth, clientWidth: document.documentElement.clientWidth, })); - expect(result.scrollWidth, `horizontal overflow: ${result.scrollWidth}px > ${result.clientWidth}px`).toBeLessThanOrEqual(result.clientWidth); + expect( + result.scrollWidth, + `horizontal overflow: ${result.scrollWidth}px > ${result.clientWidth}px`, + ).toBeLessThanOrEqual(result.clientWidth); } test.describe('Phase 4 visual verification', () => { @@ -19,7 +23,9 @@ test.describe('Phase 4 visual verification', () => { await page.goto(route); await expect(page.locator('h1')).toBeVisible(); await expectNoPageOverflow(page); - await page.screenshot({ path: `/tmp/sanring-phase4-light-${route === '/' ? 'home' : route.slice(1).replaceAll('/', '-')}.png` }); + await page.screenshot({ + path: `/tmp/sanring-phase4-light-${route === '/' ? 'home' : route.slice(1).replaceAll('/', '-')}.png`, + }); } }); @@ -32,18 +38,51 @@ test.describe('Phase 4 visual verification', () => { await page.screenshot({ path: '/tmp/sanring-phase4-dark-home.png' }); }); + test('captures representative component pages in dark theme', async ({ page }) => { + for (const route of COMPONENT_ROUTES) { + await page.goto(route); + await page.getByRole('button', { name: 'Dark theme' }).click(); + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); + await expect(page.locator('h1')).toBeVisible(); + await expectNoPageOverflow(page); + await page.screenshot({ + path: `/tmp/sanring-phase4-dark-${route.slice(1).replaceAll('/', '-')}.png`, + }); + } + }); + for (const width of [360, 390]) { - test(`captures home and CLI at ${width}px without overflow`, async ({ page }) => { + test(`captures representative pages at ${width}px without overflow`, async ({ page }) => { await page.setViewportSize({ width, height: 800 }); - for (const route of ['/', '/cli']) { + for (const route of ['/', '/cli', ...COMPONENT_ROUTES]) { await page.goto(route); await expect(page.locator('h1')).toBeVisible(); await expectNoPageOverflow(page); - await page.screenshot({ path: `/tmp/sanring-phase4-${width}-${route === '/' ? 'home' : 'cli'}.png` }); + await page.screenshot({ + path: `/tmp/sanring-phase4-${width}-${ + route === '/' ? 'home' : route.slice(1).replaceAll('/', '-') + }.png`, + }); } }); } + test('captures component header, API, and release surfaces', async ({ page }) => { + await page.goto('/components/button'); + await expect(page.locator('h1')).toBeVisible(); + + await page.locator('app-component-page-header').screenshot({ + path: '/tmp/sanring-phase4-component-header.png', + }); + await page.locator('#basic app-component-page-code-previewer').screenshot({ + path: '/tmp/sanring-phase4-component-previewer.png', + }); + await page.locator('#api').screenshot({ path: '/tmp/sanring-phase4-component-api.png' }); + await page + .locator('#recent-changes') + .screenshot({ path: '/tmp/sanring-phase4-component-recent-changes.png' }); + }); + test('keeps long code lines inside a scrollable code surface', async ({ page }) => { await page.goto('/cli'); const uncontainedOverflow = await page.locator('pre, code').evaluateAll((elements) => diff --git a/apps/docs/src/app/i18n/locales/en/common.ts b/apps/docs/src/app/i18n/locales/en/common.ts index c0607a45..ba78cf6c 100644 --- a/apps/docs/src/app/i18n/locales/en/common.ts +++ b/apps/docs/src/app/i18n/locales/en/common.ts @@ -93,10 +93,15 @@ export const commonTranslations = { 'components.updatedEmpty': 'No updated components yet.', 'components.allTitle': 'All components', 'component.recentChanges.title': 'Recent changes', - 'component.recentChanges.description': 'Latest registry and documentation updates for this component.', + 'component.recentChanges.description': + 'Latest registry and documentation updates for this component.', + 'component.recentChanges.signal': 'Release signal', 'component.recentChanges.viewAll': 'View changelog', 'component.header.registry': 'Registry', 'component.header.shipped': 'Shipped', + 'component.header.installCommand': 'Install command', + 'component.header.copyInstall': 'Copy install command', + 'component.header.copyReady': 'copy-ready', 'component.header.packagePath': 'Path', 'component.header.ssrSafe': 'SSR-safe', 'component.header.browserOnly': 'Browser-only', @@ -107,8 +112,12 @@ export const commonTranslations = { 'component.header.stateful': 'Stateful', 'component.header.cva': 'CVA-integrated', 'component.header.updated': 'Updated', + 'component.header.quickAccess': 'Jump to', 'component.header.jumpInstall': 'Installation', 'component.header.jumpApi': 'API', + 'component.previewer.preview': 'Preview', + 'component.previewer.rendered': 'rendered output', + 'component.previewer.source': 'Source', 'components.disabledNotice.title': 'Why are some components greyed out?', 'components.disabledNotice.description': "They're still in development — their docs page exists but isn't ready to use yet, so it isn't linked from this list. Track progress on the", @@ -129,6 +138,9 @@ export const commonTranslations = { 'docs.api.type': 'Type', 'docs.api.default': 'Default', 'docs.api.description': 'Description', + 'docs.api.surface': 'Reference surface', + 'docs.api.members': 'members', + 'docs.api.caption': 'Component API properties, types, defaults, and descriptions', 'docs.keyboard.key': 'Key', 'docs.keyboard.action': 'Action', 'docs.usage.imports.convenience': 'Convenience import', diff --git a/apps/docs/src/app/i18n/locales/zh/common.ts b/apps/docs/src/app/i18n/locales/zh/common.ts index e6aef08e..5f993aee 100644 --- a/apps/docs/src/app/i18n/locales/zh/common.ts +++ b/apps/docs/src/app/i18n/locales/zh/common.ts @@ -92,9 +92,13 @@ export const commonTranslations = { 'components.allTitle': '所有元件', 'component.recentChanges.title': '近期變更', 'component.recentChanges.description': '這個元件最近的 registry 與文件更新。', + 'component.recentChanges.signal': '釋出訊號', 'component.recentChanges.viewAll': '查看完整 changelog', 'component.header.registry': 'Registry', 'component.header.shipped': '已上線', + 'component.header.installCommand': '安裝指令', + 'component.header.copyInstall': '複製安裝指令', + 'component.header.copyReady': '可複製', 'component.header.packagePath': '路徑', 'component.header.ssrSafe': 'SSR 安全', 'component.header.browserOnly': '僅限瀏覽器', @@ -105,8 +109,12 @@ export const commonTranslations = { 'component.header.stateful': '有內部狀態', 'component.header.cva': '支援 CVA', 'component.header.updated': '最近更新', + 'component.header.quickAccess': '快速定位', 'component.header.jumpInstall': '安裝方式', 'component.header.jumpApi': 'API', + 'component.previewer.preview': '預覽', + 'component.previewer.rendered': '即時輸出', + 'component.previewer.source': '程式碼', 'components.disabledNotice.title': '為什麼有些元件是灰色的?', 'components.disabledNotice.description': '它們還在開發中——文件頁面已經存在,但內容還沒準備好,所以清單裡先不開放點擊。可以到', @@ -127,6 +135,9 @@ export const commonTranslations = { 'docs.api.type': '型別', 'docs.api.default': '預設值', 'docs.api.description': '說明', + 'docs.api.surface': '參考面板', + 'docs.api.members': '個成員', + 'docs.api.caption': '元件 API 的屬性、型別、預設值與說明', 'docs.keyboard.key': '按鍵', 'docs.keyboard.action': '操作說明', 'docs.usage.imports.convenience': '便利整包匯入', diff --git a/apps/docs/src/app/layouts/component-page/component-page-api-table.component.ts b/apps/docs/src/app/layouts/component-page/component-page-api-table.component.ts index bbf75201..bb6575a9 100644 --- a/apps/docs/src/app/layouts/component-page/component-page-api-table.component.ts +++ b/apps/docs/src/app/layouts/component-page/component-page-api-table.component.ts @@ -6,17 +6,40 @@ import { I18nService } from '../../i18n/i18n.service'; selector: 'app-component-page-api-table', standalone: true, template: ` -