feat: 移除测试 保持原始功能纯粹

This commit is contained in:
风雨火
2026-03-13 18:04:24 +08:00
parent 7297d46fa0
commit 26620a446a
8 changed files with 41 additions and 696 deletions
+19 -35
View File
@@ -1,58 +1,42 @@
---
name: add-test-page
description: Add a standalone feature validation page to this repo when a new capability needs a demo, smoke test, or isolated basic functional verification.
description: Create a temporary standalone validation page on a spike branch when architecture or feature behavior needs isolated visual verification.
---
Use this skill when the user asks to add a standalone validation page, demo route, smoke test page, or a basic feature verification page.
Use this skill when the user asks to add a standalone validation page, demo route, smoke test page, or an isolated visual verification surface.
## Read First
## Branch Rule
- `src/features/test-lab/registry.ts`
- `src/features/test-lab/components/TestLabShell.vue`
- `src/features/test-lab/pages/TailwindV4TestPage.vue`
- `src/features/test-lab/__tests__/registry.spec.ts`
- Do not add architecture validation pages directly on the main product branch.
- Prefer a dedicated spike branch such as `spike/tailwind-v4-lab` or `spike/router-layout-check`.
- If the current branch is intended for production-facing work, move the validation work to a spike branch before adding temporary pages or routes.
## Repo Contract
- The home route is not a test page. Keep `/` reserved for the product-facing homepage or dashboard.
- Keep the shell generic. Do not add feature-specific content to `src/App.vue` or `src/features/test-lab/components/TestLabShell.vue`.
- Create one page component per feature under `src/features/test-lab/pages/<FeatureName>TestPage.vue`.
- Register the page in `src/features/test-lab/registry.ts`. Routes are generated from the registry, so the slug becomes the URL name.
- Test pages must live under `/lab/<slug>` and stay isolated from homepage concerns.
- Temporary validation routes must not become the default entry path of the app.
- Keep experiment code visibly isolated under a temporary area such as `src/spikes/<topic>/`.
- Name temporary routes clearly, for example `/__spike/tailwind-v4` or `/__spike/router-layout`.
- Keep the page self-contained. Use inline mock data or local state instead of real APIs unless the feature explicitly requires integration verification.
- Add or update tests so the registry contract and the new page's key text remain discoverable by Vitest.
- Add only the smallest tests needed to lock the intended architecture rule or render contract.
- Before merging back, delete one-off validation pages and routes unless they provide lasting regression value.
## Required Metadata
## What Can Merge Back
Every registry item must provide:
- `slug`: unique kebab-case identifier
- `title`: short feature name
- `summary`: one-line scope statement
- `goal`: explicit validation target
- `checks`: at least 3 visible requirements
- `manualChecks`: at least 2 browser actions or observations
- `status`: `ready` or `draft`
- `component`: the Vue page component
## Page Shape
Each test page should include these sections:
1. A hero block that states what is being validated.
2. A live demo area that exposes the core feature.
3. Evidence cards for key states, variants, or edge cases.
4. A checklist area that mirrors `checks` and `manualChecks`.
5. A success criterion that says what counts as "working".
- Skill updates
- Reusable scaffolding
- Naming and routing conventions
- Minimal contract tests that protect the final architecture
- Documentation or comments that clarify the validated rule
## Implementation Rules
- Make the verification visual. A test page is not a prose document.
- Prefer deterministic state toggles so the reviewer can force each state on demand.
- If the feature introduces styles or tokens, show the exact utility classes in the page.
- If the feature introduces styles or tokens, show the exact utility classes in the spike page.
- If the feature introduces interaction, include hover, focus, active, or toggled states.
- If the feature introduces responsive behavior, include a breakpoint probe.
- If the feature is not ready, keep the page status at `draft` instead of hiding incomplete coverage.
- End the spike by extracting conclusions, then remove temporary assets from the merge candidate.
## Validation
+4 -5
View File
@@ -20,10 +20,10 @@ describe('App', () => {
expect(router.currentRoute.value.name).toBe('home')
expect(wrapper.text()).toContain('Agentic Vue3 Forge')
expect(wrapper.text()).toContain('Open Validation Lab')
expect(wrapper.text()).toContain('Mainline Ready')
})
it('keeps the validation page under the lab route', async () => {
it('does not keep architecture validation routes in the main branch', async () => {
const router = createAppRouter(createMemoryHistory())
const wrapper = mount(App, {
global: {
@@ -34,8 +34,7 @@ describe('App', () => {
await router.push('/lab')
await flushPromises()
expect(router.currentRoute.value.name).toBe('tailwind-v4')
expect(wrapper.text()).toContain('Tailwind CSS v4 smoke test')
expect(wrapper.text()).toContain('Utilities should feel obvious.')
expect(router.currentRoute.value.name).toBe('home')
expect(wrapper.text()).toContain('spike 分支')
})
})
+18 -24
View File
@@ -1,27 +1,23 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import { defaultFeatureTestPage } from '@/features/test-lab/registry'
const operatingPrinciples = [
{
title: 'Structure before scale',
detail: '先把目录、路由和约束立住,再往里持续填模块。',
title: 'Primary branch stays clean',
detail: '当前分支只放正式架构和业务代码,不承接一次性验证页面。',
},
{
title: 'Visible validation',
detail: '每个新功能都能落到单独的实验页,不把验证逻辑混进业务首页。',
title: 'Spike first for architecture',
detail: '框架、样式链路、目录试验先去 spike 分支验证,确认后只回收规则沉淀。',
},
{
title: 'AI-friendly clarity',
detail: '命名、边界和测试都保持可搜索、可接手、可回归。',
title: 'Keep only reusable residue',
detail: '合回主线的应该是 skill、约定、脚手架和必要 contract test,而不是实验页面。',
},
]
const nextSteps = [
'补业务首页模块和真实导航',
'按功能继续扩展 test-lab 页面注册表',
'逐步补上 query、store、api 的最小测试',
'补正式首页导航和业务模块入口',
'为核心模块定义最小 contract test',
'把架构验证流程固化到 spike 分支约定里',
]
</script>
@@ -44,22 +40,21 @@ const nextSteps = [
<p class="text-xs uppercase tracking-[0.36em] text-ink/45">Agentic Vue3 Forge</p>
<div class="space-y-4">
<h1 class="max-w-4xl font-display text-5xl leading-none text-balance sm:text-6xl">
Vue3 工程底座先跑起来验证页留在侧边战场
主分支先保持纯净架构验证放到独立 spike 分支
</h1>
<p class="max-w-2xl text-sm leading-7 text-ink/68 sm:text-base">
首页先保持干净承载项目定位和后续导航功能验证统一放进 test
lab避免首页被临时实验内容侵占
这个首页先承担项目定位和正式入口临时实验样式验证路由试探都不直接留在当前分支
只把最终确认过的规则和可复用结构带回来
</p>
</div>
</div>
<div class="flex flex-wrap gap-3">
<RouterLink
:to="{ name: defaultFeatureTestPage.slug }"
class="inline-flex items-center justify-center rounded-full bg-ink px-5 py-3 text-sm uppercase tracking-[0.24em] text-paper transition hover:-translate-y-0.5 hover:bg-ink/92 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ink/20"
<span
class="inline-flex items-center justify-center rounded-full bg-ink px-5 py-3 text-sm uppercase tracking-[0.24em] text-paper"
>
Open Validation Lab
</RouterLink>
Mainline Ready
</span>
<a
href="https://vuejs.org/"
target="_blank"
@@ -96,10 +91,9 @@ const nextSteps = [
</p>
</div>
<div class="rounded-[24px] border border-paper/12 bg-paper/8 p-4">
<p class="font-display text-3xl leading-none text-paper">Homepage stays clean</p>
<p class="font-display text-3xl leading-none text-paper">Spike-driven validation</p>
<p class="mt-3 text-sm leading-7 text-paper/68">
业务入口与验证入口分离首页以后可以换成正式 landing
dashboard不影响测试路由
架构试验不常驻主线需要 smoke page `spike/*` 分支开临时页面和路由
</p>
</div>
</div>
@@ -1,31 +0,0 @@
import { describe, expect, it } from 'vitest'
import { featureTestPages } from '@/features/test-lab/registry'
describe('feature test page registry', () => {
it('contains at least one page', () => {
expect(featureTestPages.length).toBeGreaterThan(0)
})
it('enforces the test page contract', () => {
const seenSlugs = new Set<string>()
for (const page of featureTestPages) {
expect(page.slug).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
expect(seenSlugs.has(page.slug)).toBe(false)
expect(page.title.trim()).not.toBe('')
expect(page.summary.trim()).not.toBe('')
expect(page.goal.trim()).not.toBe('')
expect(page.status).toMatch(/^(ready|draft)$/)
expect(page.component).toBeTruthy()
expect(page.checks.length).toBeGreaterThanOrEqual(3)
expect(page.manualChecks.length).toBeGreaterThanOrEqual(2)
for (const item of [...page.checks, ...page.manualChecks]) {
expect(item.trim()).not.toBe('')
}
seenSlugs.add(page.slug)
}
})
})
@@ -1,139 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
import { RouterLink, RouterView, useRoute } from 'vue-router'
import { featureTestPages, type FeatureTestPage } from '@/features/test-lab/registry'
const route = useRoute()
const currentPage = computed(() => route.meta.page as FeatureTestPage | undefined)
const currentRouteName = computed(() => route.name?.toString() ?? '')
const labRules = [
{
title: '独立成页',
description: '每个新功能单独放一个验证页,不把实验代码混进业务组件。',
},
{
title: '覆盖关键态',
description: '至少展示默认态、交互态、响应式或边界态中的三类证据。',
},
{
title: '配最小单测',
description: '注册表和关键文案要能被 Vitest 扫到,防止约定失效。',
},
]
</script>
<template>
<div class="min-h-screen">
<div
class="mx-auto flex min-h-screen max-w-7xl flex-col gap-5 px-4 py-4 sm:px-6 lg:flex-row lg:px-8 lg:py-8"
>
<aside
class="flex w-full shrink-0 flex-col gap-5 rounded-[32px] border border-paper/15 bg-paper/8 p-5 shadow-panel backdrop-blur-sm lg:w-[320px]"
>
<div class="space-y-3">
<p class="text-xs uppercase tracking-[0.36em] text-paper/55">Forge Validation Lab</p>
<div class="space-y-2">
<h1 class="font-display text-3xl leading-none text-paper sm:text-4xl">功能验证台</h1>
<p class="max-w-sm text-sm leading-6 text-paper/70">
为每个新能力留一个可视化可交互可回归的最小验证页
</p>
</div>
</div>
<nav class="space-y-3">
<RouterLink
v-for="page in featureTestPages"
:key="page.slug"
:to="{ name: page.slug }"
class="group block rounded-[24px] border px-4 py-4 transition duration-200"
:class="
currentRouteName === page.slug
? 'border-signal/45 bg-signal/16 text-paper'
: 'border-paper/12 bg-paper/6 text-paper/70 hover:border-paper/25 hover:bg-paper/10 hover:text-paper'
"
>
<div class="flex items-start justify-between gap-3">
<div class="space-y-2">
<p class="font-display text-xl leading-none">{{ page.title }}</p>
<p class="text-sm leading-6 opacity-80">{{ page.summary }}</p>
</div>
<span
class="rounded-full border px-2.5 py-1 text-[11px] uppercase tracking-[0.28em]"
:class="
page.status === 'ready'
? 'border-signal/35 bg-signal/12 text-paper'
: 'border-clay/30 bg-clay/15 text-paper/80'
"
>
{{ page.status }}
</span>
</div>
</RouterLink>
</nav>
<section class="space-y-3 rounded-[28px] border border-paper/12 bg-ink/35 p-4">
<p class="text-xs uppercase tracking-[0.32em] text-paper/45">规则</p>
<div
v-for="rule in labRules"
:key="rule.title"
class="rounded-[20px] border border-paper/8 bg-paper/6 p-3"
>
<p class="font-display text-lg text-paper">{{ rule.title }}</p>
<p class="mt-1 text-sm leading-6 text-paper/70">{{ rule.description }}</p>
</div>
</section>
</aside>
<main class="flex min-w-0 flex-1 flex-col gap-5">
<header
class="overflow-hidden rounded-[32px] border border-paper/14 bg-paper/90 text-ink shadow-panel"
>
<div
class="grid gap-5 p-6 sm:p-8 xl:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)] xl:items-end"
>
<div class="space-y-4">
<p class="text-xs uppercase tracking-[0.34em] text-ink/45">
Current Validation Target
</p>
<div class="space-y-3">
<h2 class="font-display text-4xl leading-none text-balance sm:text-5xl">
{{ currentPage?.title ?? 'No page selected' }}
</h2>
<p class="max-w-2xl text-sm leading-7 text-ink/70 sm:text-base">
{{
currentPage?.goal ?? 'Add a page in the registry to start validating a feature.'
}}
</p>
</div>
</div>
<div class="grid gap-3 sm:grid-cols-3 xl:grid-cols-1">
<div class="rounded-[24px] border border-ink/10 bg-ink/5 p-4">
<p class="text-xs uppercase tracking-[0.28em] text-ink/45">Pages</p>
<p class="mt-3 font-display text-3xl">{{ featureTestPages.length }}</p>
<p class="mt-1 text-sm text-ink/55">一个功能一张验证页</p>
</div>
<div class="rounded-[24px] border border-ink/10 bg-ink/5 p-4">
<p class="text-xs uppercase tracking-[0.28em] text-ink/45">Checks</p>
<p class="mt-3 font-display text-3xl">{{ currentPage?.checks.length ?? 0 }}</p>
<p class="mt-1 text-sm text-ink/55">默认要点直接可见</p>
</div>
<div class="rounded-[24px] border border-ink/10 bg-ink/5 p-4">
<p class="text-xs uppercase tracking-[0.28em] text-ink/45">Status</p>
<p class="mt-3 font-display text-3xl uppercase">
{{ currentPage?.status ?? 'draft' }}
</p>
<p class="mt-1 text-sm text-ink/55">准备好后再并入业务页</p>
</div>
</div>
</div>
</header>
<RouterView />
</main>
</div>
</div>
</template>
@@ -1,403 +0,0 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import type { FeatureTestPage } from '@/features/test-lab/registry'
const route = useRoute()
const page = computed(() => route.meta.page as FeatureTestPage | undefined)
const density = ref<'comfortable' | 'compact'>('comfortable')
const tone = ref<'signal' | 'clay'>('signal')
const showOverlay = ref(true)
const densityLabel = computed(() =>
density.value === 'comfortable' ? 'Comfortable spacing' : 'Compact spacing',
)
const framePaddingClass = computed(() =>
density.value === 'comfortable' ? 'gap-5 p-6 sm:p-7' : 'gap-3 p-4 sm:p-5',
)
const previewSurfaceClass = computed(() =>
tone.value === 'signal'
? 'bg-[linear-gradient(135deg,rgba(111,188,220,0.18),rgba(247,244,238,0.96))]'
: 'bg-[linear-gradient(135deg,rgba(197,143,99,0.18),rgba(247,244,238,0.96))]',
)
const progressValue = computed(() => (density.value === 'comfortable' ? 86 : 68))
const probeCards = [
{
title: 'Typography token',
detail: 'font-display + tracking-[0.32em]',
note: '标题与正文应该有明显的气质差异,不是同一套默认字重。',
},
{
title: 'State variants',
detail: 'hover / focus-visible / peer-checked / group-hover',
note: '悬浮、聚焦和切换时必须给出清晰反馈,方便肉眼判断状态类是否编译成功。',
},
{
title: 'Arbitrary values',
detail: 'rounded-[32px] + bg-[linear-gradient(...)]',
note: '这类任意值最容易暴露扫描和构建问题,验证页里必须显式出现。',
},
]
const responsiveProbes = [
{
label: 'Spacing',
hint: 'px-4 sm:px-6 xl:px-8',
},
{
label: 'Grid',
hint: 'md:grid-cols-2 xl:grid-cols-4',
},
{
label: 'Type',
hint: 'text-sm sm:text-base',
},
{
label: 'Visibility',
hint: 'hidden sm:inline-flex',
},
]
</script>
<template>
<article v-if="page" class="grid gap-5">
<section class="grid gap-5 xl:grid-cols-[minmax(0,1.15fr)_minmax(340px,0.85fr)]">
<div
class="rounded-[32px] border border-paper/14 bg-paper/92 p-6 text-ink shadow-panel sm:p-8"
>
<div class="space-y-6">
<div class="space-y-3">
<p class="text-xs uppercase tracking-[0.34em] text-ink/45">
Tailwind CSS v4 smoke test
</p>
<div class="space-y-3">
<h3 class="font-display text-4xl leading-none text-balance sm:text-5xl">
Tailwind v4 is wired through Vite.
</h3>
<p class="max-w-3xl text-sm leading-7 text-ink/70 sm:text-base">
{{ page.summary }}
</p>
</div>
</div>
<div class="flex flex-wrap gap-2">
<span
class="rounded-full border border-ink/10 bg-ink/5 px-3 py-1 text-xs uppercase tracking-[0.24em]"
>
font-display
</span>
<span
class="rounded-full border border-ink/10 bg-ink/5 px-3 py-1 text-xs uppercase tracking-[0.24em]"
>
text-signal
</span>
<span
class="rounded-full border border-ink/10 bg-ink/5 px-3 py-1 text-xs uppercase tracking-[0.24em]"
>
shadow-panel
</span>
<span
class="rounded-full border border-ink/10 bg-ink/5 px-3 py-1 text-xs uppercase tracking-[0.24em]"
>
rounded-[32px]
</span>
<span
class="rounded-full border border-ink/10 bg-ink/5 px-3 py-1 text-xs uppercase tracking-[0.24em]"
>
peer-checked
</span>
</div>
<div class="grid gap-3 lg:grid-cols-[repeat(2,minmax(0,1fr))_minmax(220px,0.9fr)]">
<div class="rounded-[24px] border border-ink/10 bg-ink/5 p-4">
<p class="text-xs uppercase tracking-[0.28em] text-ink/45">Accent tone</p>
<div class="mt-3 flex flex-wrap gap-2">
<button
type="button"
class="rounded-full border px-4 py-2 text-sm transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-signal/35"
:class="
tone === 'signal'
? 'border-signal/40 bg-signal/12 text-ink'
: 'border-ink/10 bg-white text-ink/75'
"
@click="tone = 'signal'"
>
Signal accent
</button>
<button
type="button"
class="rounded-full border px-4 py-2 text-sm transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-clay/35"
:class="
tone === 'clay'
? 'border-clay/40 bg-clay/16 text-ink'
: 'border-ink/10 bg-white text-ink/75'
"
@click="tone = 'clay'"
>
Clay accent
</button>
</div>
</div>
<div class="rounded-[24px] border border-ink/10 bg-ink/5 p-4">
<p class="text-xs uppercase tracking-[0.28em] text-ink/45">Density</p>
<div class="mt-3 flex flex-wrap gap-2">
<button
type="button"
class="rounded-full border px-4 py-2 text-sm transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-signal/35"
:class="
density === 'comfortable'
? 'border-ink bg-ink text-paper'
: 'border-ink/10 bg-white text-ink/75'
"
@click="density = 'comfortable'"
>
Comfortable
</button>
<button
type="button"
class="rounded-full border px-4 py-2 text-sm transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-signal/35"
:class="
density === 'compact'
? 'border-ink bg-ink text-paper'
: 'border-ink/10 bg-white text-ink/75'
"
@click="density = 'compact'"
>
Compact
</button>
</div>
</div>
<label class="rounded-[24px] border border-ink/10 bg-ink/5 p-4">
<p class="text-xs uppercase tracking-[0.28em] text-ink/45">Overlay</p>
<div class="mt-3 flex items-center justify-between gap-4">
<span class="text-sm text-ink/75">Highlight overlay</span>
<span class="relative inline-flex items-center">
<input v-model="showOverlay" type="checkbox" class="peer sr-only" />
<span
class="h-6 w-11 rounded-full bg-ink/15 transition peer-checked:bg-signal/35"
/>
<span
class="pointer-events-none absolute left-1 top-1 size-4 rounded-full bg-paper shadow-sm transition peer-checked:translate-x-5"
/>
</span>
</div>
</label>
</div>
</div>
</div>
<div
:class="previewSurfaceClass"
class="group relative overflow-hidden rounded-[32px] border border-paper/14 p-1 shadow-panel"
>
<div
v-if="showOverlay"
class="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(129,199,229,0.26),transparent_36%),radial-gradient(circle_at_bottom_left,rgba(203,153,112,0.16),transparent_34%)]"
/>
<div
:class="framePaddingClass"
class="relative grid h-full rounded-[28px] border border-ink/8 bg-paper/86 backdrop-blur-sm"
>
<div class="flex flex-wrap items-center gap-3">
<span
class="rounded-full border px-3 py-1 text-[11px] uppercase tracking-[0.28em]"
:class="
tone === 'signal'
? 'border-signal/30 bg-signal/12 text-ink'
: 'border-clay/40 bg-clay/16 text-ink'
"
>
{{ tone === 'signal' ? 'signal token' : 'clay token' }}
</span>
<span class="text-xs uppercase tracking-[0.28em] text-ink/45">
{{ densityLabel }}
</span>
</div>
<div class="grid gap-4 md:grid-cols-[1.45fr_0.55fr]">
<div class="space-y-3">
<h4 class="font-display text-3xl leading-none sm:text-4xl">
Utilities should feel obvious.
</h4>
<p class="max-w-xl text-sm leading-7 text-ink/70">
这块同时验证自定义主题 token任意值圆角阴影响应式布局和状态类是否全部生效
</p>
<div class="space-y-3 rounded-[24px] border border-ink/10 bg-white/65 p-4">
<div class="flex items-center justify-between gap-3">
<span class="text-xs uppercase tracking-[0.28em] text-ink/45">Build signal</span>
<span
class="rounded-full bg-ink px-3 py-1 text-xs uppercase tracking-[0.24em] text-paper"
>
ok
</span>
</div>
<div class="h-3 overflow-hidden rounded-full bg-ink/10">
<div
class="h-full rounded-full bg-ink transition-all duration-500"
:style="{ width: `${progressValue}%` }"
/>
</div>
<div class="flex flex-wrap gap-2">
<span class="rounded-full bg-ink/6 px-3 py-1 text-xs">rounded-[32px]</span>
<span class="rounded-full bg-ink/6 px-3 py-1 text-xs">shadow-panel</span>
<span class="rounded-full bg-ink/6 px-3 py-1 text-xs">sm:grid-cols-2</span>
</div>
</div>
</div>
<div class="grid gap-3">
<div
class="rounded-[24px] border border-ink/10 bg-ink/5 p-4 transition duration-300 group-hover:-translate-y-1"
>
<p class="text-xs uppercase tracking-[0.28em] text-ink/45">Hover probe</p>
<p class="mt-3 font-display text-2xl leading-none">group-hover</p>
<p class="mt-2 text-sm leading-6 text-ink/70">
把鼠标移到整张卡上这块应当轻微上浮
</p>
</div>
<div class="rounded-[24px] border border-ink/10 bg-ink/5 p-4">
<p class="text-xs uppercase tracking-[0.28em] text-ink/45">Focus probe</p>
<button
type="button"
class="mt-3 rounded-full border border-ink/10 bg-white px-4 py-2 text-sm text-ink transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-signal/35"
>
Press Tab to focus
</button>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="grid gap-5 lg:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]">
<div
class="rounded-[32px] border border-paper/14 bg-paper/92 p-6 text-ink shadow-panel sm:p-7"
>
<p class="text-xs uppercase tracking-[0.32em] text-ink/45">Utility probes</p>
<div class="mt-4 grid gap-3 md:grid-cols-3">
<div
v-for="probe in probeCards"
:key="probe.title"
class="rounded-[24px] border border-ink/10 bg-ink/5 p-4"
>
<p class="font-display text-2xl leading-none">{{ probe.title }}</p>
<p
class="mt-3 rounded-full bg-white px-3 py-1 text-xs uppercase tracking-[0.24em] text-ink/60"
>
{{ probe.detail }}
</p>
<p class="mt-4 text-sm leading-7 text-ink/70">{{ probe.note }}</p>
</div>
</div>
<div class="mt-6 rounded-[28px] border border-ink/10 bg-ink/5 p-4">
<p class="text-xs uppercase tracking-[0.28em] text-ink/45">Responsive probes</p>
<div class="mt-4 flex flex-wrap gap-2">
<span
class="rounded-full bg-ink px-3 py-1 text-xs uppercase tracking-[0.24em] text-paper"
>
base
</span>
<span
class="hidden rounded-full bg-signal px-3 py-1 text-xs uppercase tracking-[0.24em] text-ink sm:inline-flex"
>
sm
</span>
<span
class="hidden rounded-full bg-clay px-3 py-1 text-xs uppercase tracking-[0.24em] text-ink md:inline-flex"
>
md
</span>
<span
class="hidden rounded-full bg-ink px-3 py-1 text-xs uppercase tracking-[0.24em] text-paper lg:inline-flex"
>
lg
</span>
<span
class="hidden rounded-full border border-ink/15 bg-white px-3 py-1 text-xs uppercase tracking-[0.24em] text-ink xl:inline-flex"
>
xl
</span>
</div>
<div class="mt-5 grid gap-3 md:grid-cols-2 xl:grid-cols-4">
<div
v-for="probe in responsiveProbes"
:key="probe.label"
class="rounded-[22px] border border-ink/10 bg-white/70 p-4"
>
<p class="font-display text-xl leading-none">{{ probe.label }}</p>
<p class="mt-3 text-xs uppercase tracking-[0.24em] text-ink/45">{{ probe.hint }}</p>
</div>
</div>
<p class="mt-4 text-sm leading-7 text-ink/70">
拖动窗口时上面的断点徽标会逐级出现下面的卡片布局也会从 1 列切到 2 列和 4
</p>
</div>
</div>
<div
class="rounded-[32px] border border-paper/14 bg-paper/92 p-6 text-ink shadow-panel sm:p-7"
>
<p class="text-xs uppercase tracking-[0.32em] text-ink/45">Checklists</p>
<div class="mt-4 grid gap-4 xl:grid-cols-2">
<div class="rounded-[24px] border border-ink/10 bg-ink/5 p-4">
<p class="font-display text-2xl leading-none">Required checks</p>
<ul class="mt-4 space-y-3">
<li
v-for="item in page.checks"
:key="item"
class="flex items-start gap-3 text-sm leading-7 text-ink/75"
>
<span class="mt-2 size-2 shrink-0 rounded-full bg-signal" />
<span>{{ item }}</span>
</li>
</ul>
</div>
<div class="rounded-[24px] border border-ink/10 bg-ink/5 p-4">
<p class="font-display text-2xl leading-none">Manual checks</p>
<ul class="mt-4 space-y-3">
<li
v-for="item in page.manualChecks"
:key="item"
class="flex items-start gap-3 text-sm leading-7 text-ink/75"
>
<span class="mt-2 size-2 shrink-0 rounded-full bg-clay" />
<span>{{ item }}</span>
</li>
</ul>
</div>
</div>
<div
class="mt-6 rounded-[24px] border border-ink/10 bg-[linear-gradient(135deg,rgba(17,24,39,0.05),rgba(199,145,108,0.16))] p-5"
>
<p class="text-xs uppercase tracking-[0.28em] text-ink/45">Success criteria</p>
<p class="mt-3 max-w-2xl font-display text-3xl leading-none">
页面有证据不是只有说明
</p>
<p class="mt-3 max-w-2xl text-sm leading-7 text-ink/70">
如果颜色字型交互和响应式都能在这个页面里被快速观察到后面给任何新功能补最小验证页都会稳定很多
</p>
</div>
</div>
</section>
</article>
</template>
-46
View File
@@ -1,46 +0,0 @@
import type { Component } from 'vue'
import TailwindV4TestPage from '@/features/test-lab/pages/TailwindV4TestPage.vue'
export type FeatureTestPageStatus = 'ready' | 'draft'
export interface FeatureTestPage {
slug: string
title: string
summary: string
goal: string
checks: string[]
manualChecks: string[]
status: FeatureTestPageStatus
component: Component
}
export const featureTestPages: FeatureTestPage[] = [
{
slug: 'tailwind-v4',
title: 'Tailwind CSS v4',
summary: 'Validate tokens, utilities, states, and responsive behavior in a single page.',
goal: 'Confirm Tailwind CSS v4 is wired through Vite and available inside Vue SFCs.',
checks: [
'Custom theme tokens such as `font-display`, `text-signal`, and `shadow-panel` should render.',
'Interactive states including `hover`, `focus-visible`, `peer-checked`, and `group-hover` should react.',
'Responsive utilities should reveal extra badges and change the card grid as the viewport grows.',
'Arbitrary values such as `rounded-[32px]` and `bg-[linear-gradient(...)]` should compile without missing styles.',
],
manualChecks: [
'Click the tone and density controls to verify stateful class switches update immediately.',
'Toggle the overlay switch and confirm the preview chrome appears and disappears without layout shifts.',
'Resize the window across `sm`, `md`, `lg`, and `xl` widths to observe the breakpoint badges and grid changes.',
],
status: 'ready',
component: TailwindV4TestPage,
},
]
const firstFeatureTestPage = featureTestPages[0]
if (!firstFeatureTestPage) {
throw new Error('featureTestPages must contain at least one page')
}
export const defaultFeatureTestPage: FeatureTestPage = firstFeatureTestPage
-13
View File
@@ -1,14 +1,6 @@
import { createRouter, createWebHistory, type RouterHistory } from 'vue-router'
import HomePage from '@/features/home/pages/HomePage.vue'
import { defaultFeatureTestPage, featureTestPages } from '@/features/test-lab/registry'
const featureTestRoutes = featureTestPages.map((page) => ({
path: `/lab/${page.slug}`,
name: page.slug,
component: page.component,
meta: { page },
}))
export function createAppRouter(
history: RouterHistory = createWebHistory(import.meta.env.BASE_URL),
@@ -21,11 +13,6 @@ export function createAppRouter(
name: 'home',
component: HomePage,
},
{
path: '/lab',
redirect: { name: defaultFeatureTestPage.slug },
},
...featureTestRoutes,
{
path: '/:pathMatch(.*)*',
redirect: { name: 'home' },