@q1travel/design-system 0.26.1 → 0.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,4 +9,4 @@
9
9
  - 对应的 canonical 依据(`contracts/` 里哪条)
10
10
  - 反例 / 踩过的错
11
11
 
12
- **现状**:空。首批范例待从 q1travel-web / chrome-extension 的已合并 PR 收录(见 `../references/coverage-gaps.md`)。
12
+ **现状**:空。首批范例待从消费方的已合并改动收录(见 `../references/coverage-gaps.md`)。
@@ -2,7 +2,7 @@
2
2
 
3
3
  用户可见文字与契约里的名词以此为准,跨页 / 表格 / 示例保持一致(`contracts/interface-guidelines.md` §Copy 要求 "consistent nouns")。
4
4
 
5
- **Coverage gap**:统一术语表尚未成形。消费者(如 q1travel-web)正在做「内部黑话清理」,清理产出的中性术语应回流到这里,成为 canonical 词表。在此之前,遇到术语一致性问题记入 `references/coverage-gaps.md`。
5
+ **Coverage gap**:统一术语表尚未成形。消费方整理出的中性术语应回流到这里,成为 canonical 词表。在此之前,遇到术语一致性问题记入 `references/coverage-gaps.md`。
6
6
 
7
7
  ## 组件文档页
8
8
 
package/README.md CHANGED
@@ -98,7 +98,7 @@ npm run dev
98
98
 
99
99
  **技术栈**:React 19 · TypeScript · Vite 7(文档站 / 库双构建管线)· lucide-react · npm
100
100
 
101
- **Token 刻度原则**:目标是刻度纯粹、正交,而不是冻结。字号、行高、控件高度、圆角等刻度以 Geist 原生档位为基准,Geist 有而本仓缺失的档位可以补齐。业务页面参考其他产品时,只借鉴页面布局、信息架构与间距节奏,并映射到现有档位;仅为贴合某个产品像素而插入孤立私有尺寸的请求,设计系统直接拒绝,无需上报。确有通用诉求、且契合现有比例与刻度演进的新档位,仍走正常的规范评审。完整规则见 `contracts/token-naming.md` 的 Scale Governance。
101
+ **Token 刻度原则**:目标是刻度纯粹、正交,而不是冻结。字号、行高、控件高度、圆角等刻度以既有原生档位序列为基准,序列中定义而本仓缺失的档位可以补齐。业务页面参考其他产品时,只借鉴页面布局、信息架构与间距节奏,并映射到现有档位;仅为贴合某个产品像素而插入孤立私有尺寸的请求,设计系统直接拒绝,无需上报。确有通用诉求、且契合现有比例与刻度演进的新档位,仍走正常的规范评审。完整规则见 `contracts/token-naming.md` 的 Scale Governance。
102
102
 
103
103
  ### Sources
104
104
 
package/bin/q1-design.mjs CHANGED
@@ -1060,8 +1060,19 @@ function printUsage() {
1060
1060
 
1061
1061
  // 直跑守卫(与 scripts/ci-path-filter.mjs 同款):被 import 时只暴露纯函数,不执行 CLI,
1062
1062
  // 这样 Q1-DS-501 的匹配逻辑可以被单测直接覆盖,而不必靠拉起子进程去反推。
1063
+ // 经 npm/pnpm 的 bin 软链启动时 argv[1] 是软链路径,而 import.meta.url 是解析后的真实路径,
1064
+ // 两侧都按 realpath 归一后再比较;pathToFileURL 负责 Windows 盘符与分隔符的 URL 形式。
1065
+ function resolveEntryUrl(entryPath) {
1066
+ try {
1067
+ return pathToFileURL(realpathSync(entryPath)).href;
1068
+ } catch {
1069
+ return pathToFileURL(entryPath).href;
1070
+ }
1071
+ }
1072
+
1063
1073
  const isDirectRun =
1064
- process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
1074
+ Boolean(process.argv[1]) &&
1075
+ resolveEntryUrl(fileURLToPath(import.meta.url)) === resolveEntryUrl(process.argv[1]);
1065
1076
 
1066
1077
  if (isDirectRun) {
1067
1078
  const subcommand = process.argv[2];
@@ -40,18 +40,18 @@ Package-owned semantics are applied after native props. `type`, `className`, `di
40
40
 
41
41
  `busyLabel` is an optional string. An empty string behaves as absent and never disables repeat-submission prevention. The original children remain the accessible-name source (and an explicit `aria-label` remains unchanged); the visible busy label is a separate decorative overlay, while the live status node is outside the button/anchor name calculation. The label slot reserves geometry for both values. Under `prefers-reduced-motion: reduce`, the spinner remains present but static; no rotation, pulse, scale, or spring animation is required to understand the state.
42
42
 
43
- ## Web Adapter Seam (`q1travel-web` #702)
43
+ ## Downstream Adapter Seam
44
44
 
45
- The package deliberately does not add product-specific `quiet`, `page`, or React Router props. The q1travel-web adapter tracked by #702 owns these measurable consumer rules:
45
+ The package deliberately does not add consumer-specific variant, size, or router props. A downstream adapter layer that maps its own button API onto the package owns these measurable rules:
46
46
 
47
- | Web consumer contract | Package base | Adapter acceptance rule |
47
+ | Downstream adapter input | Package base | Adapter acceptance rule |
48
48
  | --- | --- | --- |
49
- | `variant="quiet"` | `variant="ghost"` for structure and behavior only | Preserve the existing subtle fill, border, hover/focus, disabled states, and dark-theme tokens; do not present transparent package ghost as an equivalent visual migration. |
50
- | `size="page"` | `size="md"` for structure and behavior only | Preserve `36px` min-height, existing padding, font size, and minimum width; never map it to package `lg` (`48px`). |
51
- | `Button as={Link} to={...}` | `ButtonLink href={...}` | Generate `href` with Router `useHref(to)`, keep Router navigation handlers for SPA, modified-click, keyboard, and disabled behavior, and forward the ref to the real anchor. |
49
+ | Consumer-specific subtle variant | `variant="ghost"` for structure and behavior only | Preserve the consumer's existing fill, border, hover/focus, disabled states, and dark-theme tokens; do not present transparent package ghost as an equivalent visual migration. |
50
+ | Consumer-specific page-level size | `size="md"` for structure and behavior only | Preserve the consumer's existing min-height, padding, font size, and minimum width; never map it to package `lg` (`48px`). |
51
+ | Router-aware link button | `ButtonLink href={...}` | Generate `href` with the router's href resolver, keep router navigation handlers for SPA, modified-click, keyboard, and disabled behavior, and forward the ref to the real anchor. |
52
52
  | `leadingIcon` / `trailingIcon` | `prefix` / `suffix` | Mechanical prop rename only; preserve the decorative slot boundary. |
53
53
 
54
- The adapter must keep the package's real native `<a>` as the interactive owner; it must not move navigation roles, focus, keyboard handling, disabled semantics, or the ref onto a wrapper. Adapter implementation, SPA behavior tests, and all-site consumer evidence remain q1travel-web #702 work and are not proven by this package gate.
54
+ The adapter must keep the package's real native `<a>` as the interactive owner; it must not move navigation roles, focus, keyboard handling, disabled semantics, or the ref onto a wrapper. Adapter implementation, SPA behavior tests, and consumer evidence remain downstream adapter work and are not proven by this package gate.
55
55
 
56
56
  ## Component-Owned Semantics
57
57
 
@@ -66,8 +66,8 @@ Scan date: 2026-07-20
66
66
  The scan covers the current local checkouts named below; it is not evidence about un-fetched branches or third-party packages.
67
67
 
68
68
  - `q1travel-design-system`: recommended code examples in this source revision use `svgOnly` and `aria-label`. Deprecated names remain only where compatibility behavior is exercised or declared.
69
- - `q1travel-web`: the package is used for its stylesheet and Badge export. Button and IconButton remain local components, so this change does not migrate them.
70
- - `q1travel-chrome-ozon-price-index`: package Button consumers use text-button props and do not use `iconOnly`, `ariaLabel`, or `svgOnly`.
69
+ - Primary downstream web application: the package is used for its stylesheet and Badge export. Button and IconButton remain local components, so this change does not migrate them.
70
+ - Another downstream consumer: package Button consumers use text-button props and do not use `iconOnly`, `ariaLabel`, or `svgOnly`.
71
71
  - Unknown third-party npm consumers cannot be enumerated from q1travel repositories, so a repository-local zero-result scan is not sufficient evidence for alias removal.
72
72
 
73
73
  ## Verification Boundary
@@ -92,8 +92,8 @@ Until then, the aliases remain tested public compatibility contracts.
92
92
 
93
93
  ## Non-Goals
94
94
 
95
- - This contract does not migrate `q1travel-web`.
95
+ - This contract does not migrate downstream consumers.
96
96
  - This contract does not choose a package version, publish a package, or remove an alias.
97
- - This contract does not migrate `quiet`, `page`, or React Router adapter behavior into the package; those remain q1travel-web #702 consumer seams with the measurable rules above.
97
+ - This contract does not migrate consumer-specific variant, size, or router adapter behavior into the package; those remain downstream adapter seams with the measurable rules above.
98
98
  - This contract does not change Button size, variant, route, or existing loading compatibility beyond the state contract described above.
99
99
  - This contract does not move button semantics or event handlers to a wrapper.
@@ -34,12 +34,9 @@ The regression gate checks named text/surface pairs and button-label/solid
34
34
  pairs. It must not treat every color as text or accept unsupported color syntax
35
35
  silently. Existing browser checks cover composed component colors.
36
36
 
37
- Consumer follow-up after upgrading: q1travel-web PR #998 introduced
38
- `body.theme-dark .settings-appearance-layout { --text-tertiary: var(--q1-color-gray-700); }`.
39
- Remove that workaround and resume the normal `--q1-color-text-muted` mapping.
40
- Recheck the related appearance labels, docs supporting text, raw-color ratchet,
41
- `check-contrast-contract`, and full Web gates. Other #998 text-secondary
42
- substitutions should be reviewed individually, not mechanically reverted.
37
+ Consumer follow-up after upgrading: remove any local text-muted workaround
38
+ and resume the normal `--q1-color-text-muted` mapping; review other local
39
+ text-secondary substitutions individually rather than reverting them mechanically.
43
40
 
44
41
  ## #245: Navigation active text
45
42
 
@@ -139,7 +139,7 @@ semantic-colors:
139
139
  text-on-media: "#ffffff"
140
140
  border: "{colors.gray-400}"
141
141
  border-control: "{colors.gray-600}"
142
- border-strong: "{colors.gray-400}"
142
+ border-strong: "{colors.gray-500}"
143
143
  border-active: "{colors.primary-900}"
144
144
  border-subtle: "#2a2a2a"
145
145
  text-primary: "{colors.text-primary}"
@@ -170,6 +170,33 @@ semantic-colors:
170
170
  status-info-text: "{colors.blue-900}"
171
171
  status-info-surface: "{colors.blue-100}"
172
172
  status-info-border: "#194f81"
173
+ auth-text-primary: "#f3f4f6"
174
+ auth-text-secondary: "#d1d5db"
175
+ auth-text-muted: "#9ca3af"
176
+ auth-border-strong: "#343943"
177
+ auth-surface-card: "#17191d"
178
+ auth-surface-subtle: "#101216"
179
+ auth-surface-input: "#101216"
180
+ auth-surface-hover: "#202329"
181
+ auth-surface-disabled: "#202329"
182
+ auth-surface-media: "#04050a"
183
+ auth-action-hover: "#4434d4"
184
+ auth-action-press: "#2e2b8c"
185
+ auth-action-disabled-bg: "#3b404a"
186
+ auth-action-disabled-border: "#3b404a"
187
+ auth-action-disabled-text: "#a6acb8"
188
+ auth-focus-ring: "rgba(126, 108, 255, 0.52)"
189
+ auth-danger-border: "#d92d20"
190
+ auth-danger-surface: "#241719"
191
+ auth-error-text: "#fb7185"
192
+ auth-field-error-text: "#d32f2f"
193
+ auth-notice-border: "#3d3794"
194
+ auth-notice-surface: "#1d1b3a"
195
+ auth-notice-text: "#cbc4ff"
196
+ auth-media-overlay: "rgba(4, 5, 10, 0.46)"
197
+ auth-media-overlay-hover: "rgba(4, 5, 10, 0.66)"
198
+ auth-on-media-border: "rgba(255, 255, 255, 0.22)"
199
+ auth-on-media-border-hover: "rgba(255, 255, 255, 0.34)"
173
200
  typography:
174
201
  heading-72:
175
202
  fontFamily: Noto Sans SC
@@ -452,7 +479,7 @@ These components ship in the package. Compose from this list before writing new
452
479
  - FilterOptionPanel is searchable single-select content inside Popover, usually anchored by FilterChip. It owns initial search focus, named combobox/listbox, active-descendant arrow navigation with disabled skipping, loading/error/retry/empty/no-match states, and label/keywords includes filtering. Callers own selection, closing, business ranking and async queries through controlled query/onQueryChange and an injected filter; Escape dismissal and trigger focus return stay with Popover. It does not provide multiselect or an application footer.
453
480
  - IconButton requires an accessible name. An icon-only control without one is not deliverable.
454
481
 
455
- **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, InputOTP, Textarea, Select, Fieldset, ErrorMessage.
482
+ **Text fields.** FormField, Label, InfoTip, Input, ClearableInput, SearchInput, InputOTP, Textarea, Select, Fieldset, ErrorMessage.
456
483
 
457
484
  - InputOTP collects a fixed six-digit code in one joined group by default; `separator` explicitly enables 3+3 groups. VerificationCodeInput and VerificationCodeInputProps remain deprecated aliases for one minor version; migrate to InputOTP and InputOTPProps before their removal in the next minor version. The old `/components/verification-code-input` route redirects to `/components/input-otp`.
458
485
  - FormField owns the label, help text, and error slot. Do not assemble those three around a bare Input.
@@ -507,7 +534,7 @@ Static WCAG stroke contrast against canvas/panel (2026-10-07): light neutral 5.3
507
534
  - Tabs switch between peer views of the same subject. They are not a way to shorten a page.
508
535
  - ContextSwitcher is the two-level selection model; the consumer owns the state behind it.
509
536
 
510
- **Identity and media.** Avatar, AvatarGroup, Book, Browser, BrowserDots, BrowserControls, BrowserAddress, BrowserTabs, BrowserPlaceholder, Phone, Video, ThemeSwitcher, ProductLockup, Q1travelSymbol.
537
+ **Identity and media.** Avatar, AvatarGroup, AvatarButton, Book, Browser, BrowserDots, BrowserControls, BrowserAddress, BrowserTabs, BrowserPlaceholder, Phone, Video, ThemeSwitcher, ProductLockup, Q1travelSymbol.
511
538
 
512
539
  - ProductLockup combines a product icon, product name, and brand endorsement into one identity mark. The endorsement is smaller and uses secondary text color. Pass only the brand name through `brand`; the component renders "by" itself.
513
540
 
@@ -138,7 +138,7 @@ semantic-colors:
138
138
  surface-media: "#0a0a0a"
139
139
  text-on-media: "#ffffff"
140
140
  border: "{colors.gray-alpha-200}"
141
- border-control: "{semantic-colors.border}"
141
+ border-control: "{colors.gray-700}"
142
142
  border-strong: "{colors.gray-500}"
143
143
  border-active: "{colors.primary-700}"
144
144
  border-subtle: "rgba(203, 213, 225, 0.82)"
@@ -170,6 +170,33 @@ semantic-colors:
170
170
  status-info-text: "#0064b8"
171
171
  status-info-surface: "{colors.blue-100}"
172
172
  status-info-border: "#abcff0"
173
+ auth-text-primary: "#061b31"
174
+ auth-text-secondary: "#273951"
175
+ auth-text-muted: "#64748d"
176
+ auth-border-strong: "#d6dde6"
177
+ auth-surface-card: "#ffffff"
178
+ auth-surface-subtle: "#f8fafc"
179
+ auth-surface-input: "#ffffff"
180
+ auth-surface-hover: "#f6f9fc"
181
+ auth-surface-disabled: "#f6f9fc"
182
+ auth-surface-media: "#04050a"
183
+ auth-action-hover: "#4434d4"
184
+ auth-action-press: "#2e2b8c"
185
+ auth-action-disabled-bg: "#c7c9cc"
186
+ auth-action-disabled-border: "#c7c9cc"
187
+ auth-action-disabled-text: "#ffffff"
188
+ auth-focus-ring: "rgba(83, 58, 253, 0.42)"
189
+ auth-danger-border: "#d92d20"
190
+ auth-danger-surface: "#fff8f7"
191
+ auth-error-text: "#be123c"
192
+ auth-field-error-text: "#d32f2f"
193
+ auth-notice-border: "#b9b9f9"
194
+ auth-notice-surface: "#efebff"
195
+ auth-notice-text: "#2e2b8c"
196
+ auth-media-overlay: "rgba(4, 5, 10, 0.46)"
197
+ auth-media-overlay-hover: "rgba(4, 5, 10, 0.66)"
198
+ auth-on-media-border: "rgba(255, 255, 255, 0.22)"
199
+ auth-on-media-border-hover: "rgba(255, 255, 255, 0.34)"
173
200
  typography:
174
201
  heading-72:
175
202
  fontFamily: Noto Sans SC
@@ -452,7 +479,7 @@ These components ship in the package. Compose from this list before writing new
452
479
  - FilterOptionPanel is searchable single-select content inside Popover, usually anchored by FilterChip. It owns initial search focus, named combobox/listbox, active-descendant arrow navigation with disabled skipping, loading/error/retry/empty/no-match states, and label/keywords includes filtering. Callers own selection, closing, business ranking and async queries through controlled query/onQueryChange and an injected filter; Escape dismissal and trigger focus return stay with Popover. It does not provide multiselect or an application footer.
453
480
  - IconButton requires an accessible name. An icon-only control without one is not deliverable.
454
481
 
455
- **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, InputOTP, Textarea, Select, Fieldset, ErrorMessage.
482
+ **Text fields.** FormField, Label, InfoTip, Input, ClearableInput, SearchInput, InputOTP, Textarea, Select, Fieldset, ErrorMessage.
456
483
 
457
484
  - InputOTP collects a fixed six-digit code in one joined group by default; `separator` explicitly enables 3+3 groups. VerificationCodeInput and VerificationCodeInputProps remain deprecated aliases for one minor version; migrate to InputOTP and InputOTPProps before their removal in the next minor version. The old `/components/verification-code-input` route redirects to `/components/input-otp`.
458
485
  - FormField owns the label, help text, and error slot. Do not assemble those three around a bare Input.
@@ -507,7 +534,7 @@ Static WCAG stroke contrast against canvas/panel (2026-10-07): light neutral 5.3
507
534
  - Tabs switch between peer views of the same subject. They are not a way to shorten a page. Optional `value` / `onValueChange` let the consumer own selection and URL synchronization; `defaultValue` seeds uncontrolled selection. Invalid or disabled values render the first enabled item without notifying. See `implementation-map.md` for selection ownership.
508
535
  - ContextSwitcher is the two-level selection model; the consumer owns the state behind it.
509
536
 
510
- **Identity and media.** Avatar, AvatarGroup, Book, Browser, BrowserDots, BrowserControls, BrowserAddress, BrowserTabs, BrowserPlaceholder, Phone, Video, ThemeSwitcher, ProductLockup, Q1travelSymbol.
537
+ **Identity and media.** Avatar, AvatarGroup, AvatarButton, Book, Browser, BrowserDots, BrowserControls, BrowserAddress, BrowserTabs, BrowserPlaceholder, Phone, Video, ThemeSwitcher, ProductLockup, Q1travelSymbol.
511
538
 
512
539
  - ProductLockup combines a product icon, product name, and brand endorsement into one identity mark. The endorsement is smaller and uses secondary text color. Pass only the brand name through `brand`; the component renders "by" itself.
513
540
 
@@ -34,7 +34,7 @@ README owns the repository inventory and generated-file boundary. This file owns
34
34
  - New public tokens use `--q1-*`; existing product `--ds-*` names are legacy migration inputs, not new contract names.
35
35
  - Canonical components own repeated structure. Page-local custom structures are allowed only for visual specimens with a narrow purpose.
36
36
  - Token references are the design source for production code. Current theme resolved values are clipboard output only.
37
- - The public documentation navigation groups are `基础`, `品牌`, and `组件`. Product compositions are owned by q1travel-web.
37
+ - The public documentation navigation groups are `基础`, `品牌`, and `组件`. Product compositions are owned by downstream consumers.
38
38
  - If a capability is missing from a shared component, add the capability to that component before adding another page-local pattern.
39
39
 
40
40
  ## Documentation Section Ownership
@@ -86,7 +86,7 @@ background while keeping its border static. Public props and token values are un
86
86
 
87
87
  ## Tabs selection ownership
88
88
 
89
- Owner approved 2026-10-06: `Tabs` accepts optional `value?: string`,
89
+ Accepted 2026-10-06: `Tabs` accepts optional `value?: string`,
90
90
  `defaultValue?: string`, and `onValueChange?: (id: string) => void`. Existing
91
91
  consumers need no migration: without these props, selection starts at the first
92
92
  enabled item and preserves automatic activation, roving tabindex, and availability
@@ -98,14 +98,14 @@ changes only when the parent updates `value`. Keyboard navigation follows the
98
98
  focused enabled tab while the parent update is pending, and a later value update
99
99
  does not pull focus from the activated button. Consumers own URL synchronization.
100
100
 
101
- ## Record activity (owner approved 2026-10-06)
101
+ ## Record activity (accepted 2026-10-06)
102
102
 
103
103
  Timeline / TimelineItem (ol/li, 8px token dot or Avatar), Marker (quiet actor/time, date separator), Message (default/pending/failed, caller actions) / Mention (inline action-text on selected surface), Composer (Tier A textarea combobox/listbox, IME guard, Enter send / Shift+Enter newline), Attachment (Progress, retry/remove) are public package components. TimelineCollapsedGroup uses a native disclosure button with aria-expanded and Enter/Space; callers group runs of consecutive system events (docs example uses three or more). Marker unread uses centered action text; caller places and scrolls to the boundary. Message acknowledgements and its caller-owned actions slot display receipt state. Composer also accepts `onMentionQuery(query, {signal})` with 200ms debounce, abort/stale-result rejection, loading/empty/error states and keyboard retry; async lookup takes precedence over static members. Optional `placeholder` and `hint` preserve the input label; member instructions appear only with mention support. Han text may directly precede @, while Unicode word/email interiors do not trigger. Selection inserts text and sets the caret synchronously. Composer commands share the mention combobox/listbox keyboard model and IME guard; known slash commands invoke onCommand instead of onSubmit. Attachment paste/drop passes File[] to onAttach; legacy clicks remain argument-free. Callers own the file picker and object URLs and revoke previews on removal or unmount. Existing tokens only; data, permissions, network and notifications remain caller-owned. Composer accepts optional `triggers` ({char,label,options?,onQuery?,emptyText?,errorText?,toToken?}); single-symbol triggers share debounce, abort/stale rejection, statuses, retry, combobox/listbox keyboard and synchronous caret insertion. Explicit @ overrides legacy mentions/onMentionQuery; otherwise they remain an implicit @ trigger. Slash is reserved for unchanged commands and rejected as a trigger with a development warning. Enabled triggers alone appear in the default hint. Han text may directly precede any trigger; word/email interiors do not trigger. Selection inserts toToken(item), defaulting to char + label, and surviving selections produce {char,id,label} references alongside existing text/mentionIds; references is optional in the type for legacy constructed submissions and always present on component submissions. Editing a token invalidates its reference.
104
104
  Timeline / TimelineItem (ol/li, 8px token dot or Avatar), Marker (quiet actor/time, date separator), Message (default/pending/failed, caller actions) / Mention (inline action-text on selected surface), Composer (Tier A textarea combobox/listbox, IME guard, Enter send / Shift+Enter newline), Attachment (Progress, retry/remove) are public package components. `/patterns/record-activity` composes order facts, Switch, chronological activity and a pinned Composer. Existing tokens only; data, permissions, network and notifications remain caller-owned.
105
105
 
106
106
  ## ToggleGroup selection ownership
107
107
 
108
- Owner approved 2026-10-07: public `ToggleGroup` uses the shadcn/Radix name (Geist has no corresponding component). Its `items` API exposes `value`, `label`, optional `icon`, `disabled`, and `ariaLabel`; icon-only items require an icon and `ariaLabel`. `type="single"` accepts string `value` / `defaultValue` and `onValueChange`; `allowEmpty` defaults to false. `type="multiple"` accepts string arrays. Controlled values remain caller-owned; uncontrolled single selection falls back to the first enabled item when its value is absent or unknown and empty selection is disallowed. Disabling an existing selected item retains its pressed state and moves only the tabstop. No existing Switch consumer is migrated.
108
+ Accepted 2026-10-07: public `ToggleGroup` uses the shadcn/Radix name (no corresponding industry-common component). Its `items` API exposes `value`, `label`, optional `icon`, `disabled`, and `ariaLabel`; icon-only items require an icon and `ariaLabel`. `type="single"` accepts string `value` / `defaultValue` and `onValueChange`; `allowEmpty` defaults to false. `type="multiple"` accepts string arrays. Controlled values remain caller-owned; uncontrolled single selection falls back to the first enabled item when its value is absent or unknown and empty selection is disallowed. Disabling an existing selected item retains its pressed state and moves only the tabstop. No existing Switch consumer is migrated.
109
109
 
110
110
  Native buttons expose `aria-pressed` within `role="group"`. Roving tabindex and Arrow / Home / End move focus without selecting; Enter / Space activate through native clicks. Disabled items are skipped. Button-aligned sm32 / md40 / lg48 sizes use existing tokens, quiet selected background and primary text without a selected ring, shared focus shadow, and the shared coarse-pointer 44px TouchTarget with reserved neighbor space. The Tier A keyboard contract lives on `/components/toggle-group`; Switch retains radio semantics and Tabs retains panel semantics.
111
111
  ## Checkbox task completion circle
@@ -113,8 +113,8 @@ Native buttons expose `aria-pressed` within `role="group"`. Roving tabindex and
113
113
  - Documentation: `src/pages/CheckboxPage.tsx`, `CheckboxCircleDemo.tsx`, `src/styles/checkbox-circle-specimen.css`; circle completion is restricted to tasks/checklists and row completion treatment stays consumer-owned.
114
114
  - Verification: `scripts/check-checkbox-circle-contract.test.mjs`, `scripts/fixtures/checkbox-circle-api-consumer.tsx`, `scripts/lib/checkbox-circle-contrast.mjs`, `scripts/lib/check-checkbox-circle-browser.mjs`. Package gate covers API/default/native markup/readonly semantics/token existence and both-theme contrast; browser gate covers native toggles, focus without preview, readonly, tone fill, reduced motion and coarse hit targets.
115
115
  ## Chat messages
116
- Bubble is separate from Message: chat has optional identity and direction, while Message retains its required author, comment attachments and receipt API unchanged. MessageScroller is separate from Scroller: keyed prepend anchors and message following need ownership of the viewport; the general Scroller API remains unchanged. The shadcn chat reference supplies conversation intent only; quiet Geist surfaces map to existing q1travel panel/selected backgrounds, borders, spacing, radii and Button. No reference CSS, motion background or private tokens are shipped.
117
- The consumer mapping was checked against q1travel-web origin/main: `src/components/AssistantDrawer.jsx`, `src/components/AssistantDrawer.css`, `src/components/copilot/copilot.css`, and `src/components/assistant/useAssistantConversation.js` (local ref, no downstream edits).
116
+ Bubble is separate from Message: chat has optional identity and direction, while Message retains its required author, comment attachments and receipt API unchanged. MessageScroller is separate from Scroller: keyed prepend anchors and message following need ownership of the viewport; the general Scroller API remains unchanged. The shadcn chat reference supplies conversation intent only; quiet chat surfaces map to existing q1travel panel/selected backgrounds, borders, spacing, radii and Button. No reference CSS, motion background or private tokens are shipped.
117
+ The consumer mapping was checked against downstream chat-style implementations (read-only, no downstream edits).
118
118
  | Web capability | Package support / caller boundary |
119
119
  | --- | --- |
120
120
  | message role user / assistant | Bubble direction outgoing / incoming |
@@ -130,8 +130,8 @@ Keyboard: native viewport Arrow/PageUp/PageDown/Home/End; Tab reaches the viewpo
130
130
 
131
131
  ## Hidden field labels
132
132
 
133
- Owner approved 2026-10-08: additive `labelHidden?: boolean` (default false) keeps a real associated label through `q1-sr-only` while removing its visual row and gap. Input, Textarea, Select, InputOTP and Checkbox/Radio (including circular Checkbox) expose the shell option; bare mode rejects it. SearchInput and Combobox compositions use `FormField labelHidden`, preserving their existing APIs. Calendar/RangeField does not consume FieldShellProps and retains its existing date-trigger behavior. Errors and descriptions remain visible and connected through `aria-describedby`. Input's existing string `hideLabel` remains the password hide-action copy.
133
+ Accepted 2026-10-08: additive `labelHidden?: boolean` (default false) keeps a real associated label through `q1-sr-only` while removing its visual row and gap. Input, Textarea, Select, InputOTP and Checkbox/Radio (including circular Checkbox) expose the shell option; bare mode rejects it. SearchInput and Combobox compositions use `FormField labelHidden`, preserving their existing APIs. Calendar/RangeField does not consume FieldShellProps and retains its existing date-trigger behavior. Errors and descriptions remain visible and connected through `aria-describedby`. Input's existing string `hideLabel` remains the password hide-action copy.
134
134
 
135
135
  `FormField` establishes a relative containing block for hidden labels and empty status regions. Package consumer fixtures enforce label ownership, SSR contracts cover names/messages, and `scripts/lib/check-label-hidden-browser.mjs` checks native labels, visible errors, inline Button alignment and a deterministic 390px scrollable max-content row without page overflow.
136
136
  ## Control density scale
137
- Owner approved 2026-10-08: explicitly sized single-line Button, ButtonLink, Input, SearchInput, Select, RangeField trigger, Menu text trigger and FilterChip share sm32 / md40 / lg48 on fine pointers. SearchInput defaults to md and retains native numeric size compatibility; FilterChip defaults to sm32. Menu text triggers default to md40 after the rendered fine-pointer audit confirmed the prior omitted-size trigger measured 38–39px; icon triggers retain their prior behavior. Calendar size remains grid-only; fieldSize controls its trigger. Switch outer sm / md / lg heights align to 32 / 40 / 48px, defaulting to md; coarse-pointer 44px rules remain unchanged. FormField and Select omit empty or absent visible labels; callers supply aria-label, aria-labelledby or a native external label. Select reuses the development-only bare-field accessible-name warning.
137
+ Accepted 2026-10-08: explicitly sized single-line Button, ButtonLink, Input, SearchInput, Select, RangeField trigger, Menu text trigger and FilterChip share sm32 / md40 / lg48 on fine pointers. SearchInput defaults to md and retains native numeric size compatibility; FilterChip defaults to sm32. Menu text triggers default to md40 after the rendered fine-pointer audit confirmed the prior omitted-size trigger measured 38–39px; icon triggers retain their prior behavior. Calendar size remains grid-only; fieldSize controls its trigger. Switch outer sm / md / lg heights align to 32 / 40 / 48px, defaulting to md; coarse-pointer 44px rules remain unchanged. FormField and Select omit empty or absent visible labels; callers supply aria-label, aria-labelledby or a native external label. Select reuses the development-only bare-field accessible-name warning.
@@ -26,7 +26,7 @@ Adding a clause therefore means answering two questions before the wording: who
26
26
  - [both] MUST keep mobile control targets at least 44px where touch is expected.
27
27
  - [both] MUST use `--q1-touch-target-min` (44px) for the shared centered invisible child-span hit region of Button (all sizes, including compact consumer overrides and icon-only), CopyButton, Menu/DotsMenu triggers, Description info/hint buttons, and the independent FilterChip picker/clear buttons only under `(pointer: coarse), (any-pointer: coarse)`. Preserve every existing coarse-pointer visual-size rule. Only axes still below the minimum receive extra hit area; existing 44px targets do not expand outside their visual frame. The region covers the complete border box: canonical Button/CopyButton/Menu/DotsMenu borders are 1px, while Description borders are none. Consumers overriding a control border width must also set its private `--touch-target-border-width` to the actual per-side width (including zero for `border: none`); asymmetric borders need their own hit-region sizing. FilterChip keeps its default sm32 visual height and existing gaps/margins; its picker and clear hit regions must not overlap. Menu item rows use a real `min-height: var(--q1-touch-target-min)` under the same coarse-pointer query so neighboring rows remain independent. Focus rings stay on the visual control; child hit-region events activate that native control without replacing its pseudo-elements or overflow rules.
28
28
  - [both] MUST leave enough space for expanded targets: dense neighbors must not overlap each other's visual frames. Consumers must reserve the expansion on each side and avoid clipping ancestors; if this is impossible, regroup controls or use larger visual controls. The shared region cannot infer neighbor gaps or escape control/ancestor clipping; clipped compact controls must use a visual size of at least 44px.
29
- - [both] SHOULD deliberately depart from Geist's measured 24–32px controls without coarse-pointer enlargement: Apple HIG's 44pt guidance and WCAG 2.5.5 motivate q1travel's 44×44 CSS-pixel mobile usability target; pt and CSS pixels are different units.
29
+ - [both] SHOULD NOT ship dense measured 24–32px controls to coarse pointers without enlargement: Apple HIG's 44pt guidance and WCAG 2.5.5 motivate q1travel's 44×44 CSS-pixel mobile usability target; pt and CSS pixels are different units.
30
30
  - [both] NEVER disable browser zoom.
31
31
  - [both] MUST avoid dead zones: if a region looks clickable, the clickable element must cover it.
32
32
  - [both] MUST show a loading indicator on in-flight buttons while keeping the original label.
@@ -213,7 +213,7 @@ These copy clauses keep reader address and empty states consistent with a formal
213
213
 
214
214
  ### Component Naming
215
215
 
216
- - [repo] SHOULD use the Geist component name when Geist has the component; otherwise use the industry name, preferring shadcn/ui, then Radix. Invent a name only when neither has one. Name what the component is, not how it is built.
216
+ - [repo] SHOULD use the industry-common component name when one exists, preferring shadcn/ui, then Radix. Invent a name only when neither has one. Name what the component is, not how it is built.
217
217
  - [repo] MUST switch public names directly to the new export without deprecated aliases or old documentation route redirects; record the removal as a breaking change in CHANGELOG.
218
218
 
219
219
  ### Component Documentation Pages
@@ -26,7 +26,7 @@
26
26
  - 不修改现有 overlay 组件实现;
27
27
  - 不新增公开 `OverlayHost`、stack 或 hook API;
28
28
  - 不修改 `public/design.md`、`public/design.dark.md` 等生成镜像;
29
- - 不修改 `q1travel-web`,也不把 Web 的类名、DOM 结构或实现复制到本包;
29
+ - 不修改下游消费方,也不把下游的类名、DOM 结构或实现复制到本包;
30
30
  - 不把 foundation、四个组件接入和 Web migration 合并为一个 PR。
31
31
 
32
32
  ### 2.2 第一批纳入 foundation 的 surface
@@ -45,7 +45,7 @@
45
45
 
46
46
  ### 2.3 行为证据的使用方式
47
47
 
48
- `q1travel-web` 的 OverlayHost/GlassSurface 行为、测试和 Gate 0 文档只能作为行为证据。它们不改变本包的设计权威,也不授权 Web migration。package contract、component API 和本仓库的 token 规则优先。
48
+ 下游消费方已有的浮层宿主与玻璃表面行为、测试和文档只能作为行为证据。它们不改变本包的设计权威,也不授权 Web migration。package contract、component API 和本仓库的 token 规则优先。
49
49
 
50
50
  ## 3. 术语与记录
51
51
 
@@ -194,7 +194,7 @@ D2 Overlay Foundation GREEN runtime
194
194
  ↓ all relevant checks green
195
195
  D6 docs/browser contract completion and package PR
196
196
  ↓ separate release/consumer gate
197
- D7 q1travel-web adoption/migration
197
+ D7 downstream adoption/migration
198
198
  ```
199
199
 
200
200
  - D1 不得修改四个生产组件;D2 不得在 RED review 前开始;D3–D5 分开审查和验证。
@@ -209,7 +209,7 @@ D7 q1travel-web adoption/migration
209
209
  - **D1/D2 — Foundation RED/GREEN:已完成。** `9d0d56da527e66f3d2e793364bf0b2c9e2ebd828` 引入 shared `overlayCore`、`useOverlay` 与 Foundation runtime;当前 targeted runtime/browser contract `node --test scripts/check-overlay-foundation-red.test.mjs` 通过(2/2)。
210
210
  - **D3–D5 — 首批组件接入:实现已进入 main。** `Modal`、`DestructiveActionModal`、`Sheet`、`Drawer` 均通过 `useOverlay` 接入;Sheet 保持 `blocking=true/outsidePointer=false`,DestructiveActionModal 的 loading policy 保留。实现证据不等于已建立对应的独立 GitHub blocking tickets。
211
211
  - **D6 — package/release provenance:已完成。** 0.7.3 exact package source 为 `224f723f3a4e7bb3d5256ae2364783362b2f8eaf`,`v0.7.3` 已解析到该 source,registry provenance 已由 `b40a3b5ad49156c3d8b0409b226c15b37468f8c7` 写入 remote `main`。
212
- - **D7 — q1travel-web adoption/migration:pending。** 本次没有修改 Web,也没有把 Web migration 伪装成 package acceptance;Web 仍需独立 ticket、consumer evidence 与单独授权。
212
+ - **D7 — downstream adoption/migration:pending。** 本次没有修改 Web,也没有把 Web migration 伪装成 package acceptance;Web 仍需独立 ticket、consumer evidence 与单独授权。
213
213
  - **Ticket graph:pending。** 当前 GitHub 中没有 Foundation、Modal、Sheet、Drawer、Web migration 的独立 blocking ticket 记录;本 contract 的 D3–D7 图是交付边界,不是 GitHub tracker 已建立的 blocking edges。
214
214
 
215
215
  因此,#27 的 package-side implementation/provenance evidence 已具备;issue acceptance 仍不能在缺少独立 ticket graph、contract 状态同步与 D7 Web gate 的情况下被解释为全链路完成。
@@ -4,7 +4,7 @@ Status: accepted
4
4
 
5
5
  Decision date: 2026-08-26
6
6
 
7
- ## 2026-10-05 所有者批准的直接替换
7
+ ## 2026-10-05 已批准的直接替换
8
8
 
9
9
  本批跳过过渡流程,直接在 Token 层替换,不保留旧阴影或兼容档。以下取值取代后文的历史取值及方向性面板例外;后文保留为历史证据,不代表当前下游版本。
10
10
 
@@ -18,7 +18,7 @@ Decision date: 2026-08-26
18
18
 
19
19
  关联:q1travel-design-system#140(B2-1)、q1travel-design-system#138(界面准则 · 设计章节)
20
20
 
21
- 这份合同冻结 `--q1-shadow-popover` 与 `--q1-shadow-modal` 由单层改为分层的取值、下游影响面与回滚单位。它**不修改 q1travel-web**,不创建下游迁移票,也不声称消费方已完成接入。
21
+ 这份合同冻结 `--q1-shadow-popover` 与 `--q1-shadow-modal` 由单层改为分层的取值、下游影响面与回滚单位。它**不修改下游仓库**,不创建下游迁移票,也不声称消费方已完成接入。
22
22
 
23
23
  ## 依据
24
24
 
@@ -45,21 +45,7 @@ Decision date: 2026-08-26
45
45
 
46
46
  ## 下游影响面
47
47
 
48
- 2026-08-26 对同级仓库的只读检索结果:
49
-
50
- | 仓库 | 依赖版本 | `--q1-shadow-popover` | `--q1-shadow-modal` |
51
- | --- | --- | --- | --- |
52
- | q1travel-web | `@q1travel/design-system` 0.7.3 | **2 处** | 0 处 |
53
- | q1travel-api | 未依赖 | 0 处 | 0 处 |
54
- | q1travel-chrome-extension | 未依赖 | 0 处 | 0 处 |
55
- | q1travel-ai-search-worker | 未依赖 | 0 处 | 0 处 |
56
-
57
- q1travel-web 的两个消费点:
58
-
59
- - `src/components/CommandPalette.css:7`
60
- - `src/pages/DocsPages.css:488`
61
-
62
- (另有 `plans/001-design-system-menu-spatial-transition.md:45` 出现该 token,属计划文档,非运行代码。)
48
+ 2026-08-26 对下游消费方的只读检索结果:`--q1-shadow-popover` 下游消费点 2 处,`--q1-shadow-modal` 下游消费点 0 处。
63
49
 
64
50
  `--q1-shadow-modal` 下游零消费,已于第一步单独发布,无需下游动作。
65
51
 
@@ -67,9 +53,9 @@ q1travel-web 的两个消费点:
67
53
 
68
54
  **消费方无需修改任何代码。** 两个 token 名称、消费方式与作用位置均未变,升级包版本后阴影自动变为分层。
69
55
 
70
- 需要做的只有视觉复核:升级后确认 `CommandPalette` 与 `DocsPages` 两处浮层在明暗双主题下的观感——预期是边缘更清晰、根部更实,而不是更重或更糊。
56
+ 需要做的只有视觉复核:升级后确认 2 处下游消费点的浮层在明暗双主题下的观感——预期是边缘更清晰、根部更实,而不是更重或更糊。
71
57
 
72
- q1travel-web 当前停留在 0.7.3,与本仓 0.9.0 之间还隔着其他变更;本合同只对阴影这一项负责,不代表其他差异已评估。
58
+ 下游消费方升级时可能跨越多个版本;本合同只对阴影这一项负责,不代表其他差异已评估。
73
59
 
74
60
  ## 可接受变化
75
61
 
@@ -88,7 +74,7 @@ q1travel-web 当前停留在 0.7.3,与本仓 0.9.0 之间还隔着其他变更
88
74
 
89
75
  ## 非目标
90
76
 
91
- - 不修改 q1travel-web,不代下游提交。
77
+ - 不修改下游仓库,不代下游提交。
92
78
  - 不新增第三档阴影,不为 Drawer / Sheet 引入共享阴影 token。
93
79
  - 本次浮层阴影迁移不改动 `--q1-shadow-focus`;后续焦点环调整独立进行。
94
80
  - 不在本合同内处理 `contracts/interface-guidelines.md` Design 章节的其余条款(嵌套圆角见 #141,色相一致见 #142)。
@@ -31,7 +31,7 @@ The `0.8.0` candidate freezes two independent product models:
31
31
  - Unknown-tail: controlled `page` and `onChange`, with `hasNextPage`; it does not fabricate `total`, a last page, or numeric page-window controls.
32
32
  - `true-cursor` remains deferred until a real consumer supplies the cursor ownership, request boundary, and acceptance evidence. It is not inferred from `total`, `page`, or `hasNextPage`.
33
33
 
34
- TypeScript is the public input boundary for the mutually exclusive models. The package does not own q1travel-web data fetching, query-string state, router navigation, or cursor token parsing.
34
+ TypeScript is the public input boundary for the mutually exclusive models. The package does not own consumer data fetching, query-string state, router navigation, or cursor token parsing.
35
35
 
36
36
  ## #111 Shared slots and loading accessibility
37
37
 
@@ -81,7 +81,7 @@ The package contract must be backed by generated public declarations, a TypeScri
81
81
 
82
82
  The documentation route `/components/pagination` is retained. It documents both the product `Pagination` boundary and the separate `DocsPagination` sequence-navigation boundary.
83
83
 
84
- `DocsPagination` accepts consumer-owned real `href` values and renders native links. It does not import a q1travel-web router or decide URL construction.
84
+ `DocsPagination` accepts consumer-owned real `href` values and renders native links. It does not import a consumer router or decide URL construction.
85
85
 
86
86
  ## Compatibility, removal, and rollback
87
87
 
@@ -92,6 +92,6 @@ The documentation route `/components/pagination` is retained. It documents both
92
92
 
93
93
  ## Web migration gate
94
94
 
95
- q1travel-web migration remains a separate gate. This package candidate does not modify q1travel-web, does not claim consumer adoption, and does not publish or deploy. Web migration may start only after a verified published package release and a separately authorized consumer change.
95
+ Downstream migration remains a separate gate. This package candidate does not modify downstream consumers, does not claim consumer adoption, and does not publish or deploy. Web migration may start only after a verified published package release and a separately authorized consumer change.
96
96
 
97
97
  This contract records the public boundary; it does not perform the `0.8.0` version bump, npm publish, Web migration, issue closure, or production validation.
@@ -2,11 +2,11 @@
2
2
 
3
3
  Status: accepted
4
4
 
5
- 关联:q1travel-design-system#71、q1travel-web StatusChip consumer migration
5
+ 关联:下游 StatusChip 消费方迁移
6
6
 
7
7
  ## 目标与边界
8
8
 
9
- 这份合同把 q1travel-web 的展示型 `StatusChip` 映射到已发布的 `@q1travel/design-system` `Badge`。它只冻结 package props、语义映射、视觉取舍和回滚单位;不修改 q1travel-web,不把 `StatusChip` 兼容 alias 加入 package,也不批量迁移消费者。
9
+ 这份合同把下游消费方的展示型 `StatusChip` 映射到已发布的 `@q1travel/design-system` `Badge`。它只冻结 package props、语义映射、视觉取舍和回滚单位;不修改下游消费方,不把 `StatusChip` 兼容 alias 加入 package,也不批量迁移消费者。
10
10
 
11
11
  `Badge` 已经是胶囊形(`border-radius: 999px`),因此迁移不需要新增 pill API。#24/#58 已裁定:状态 tone 使用现有 `subtle`,`quiet` 保留为低强调元数据档,StatusChip 原有 1px 描边并入 subtle,不新增 `outline`。
12
12
 
@@ -63,7 +63,7 @@ package `Badge` 的 subtle 派生色已在 #24 的双主题实测中验证:suc
63
63
 
64
64
  ## 非目标
65
65
 
66
- - 不修改 q1travel-web。
66
+ - 不修改下游消费方。
67
67
  - 不把 StatusChip 导出到 `@q1travel/design-system`。
68
68
  - 不在本票新增 `outline`、`is-*`、page-local color token 或第三方路由依赖。
69
69
  - 不因 package specimen 通过就宣称 61 个 Web 消费文件已迁移。
@@ -8,7 +8,7 @@ Related: q1travel-design-system#25
8
8
 
9
9
  This contract freezes the first public boundary for a shared `Table`. It records
10
10
  the behavior and ownership decisions needed before implementation; it does not
11
- implement the component, change q1travel-web, create migration tickets, or
11
+ implement the component, change downstream consumers, create migration tickets, or
12
12
  claim consumer adoption.
13
13
 
14
14
  ## Public boundary
@@ -157,16 +157,16 @@ contract.
157
157
  - The current-page select-all rule remains valid when the consumer changes
158
158
  pages: off-page selected keys stay in the consumer's controlled collection,
159
159
  while the header state is recalculated from the newly visible page.
160
- - q1travel-web migration is a separate gate. It may begin only after the
160
+ - Downstream migration is a separate gate. It may begin only after the
161
161
  package export, generated types, focused tests, published package identity,
162
162
  and consumer adoption have each been independently verified.
163
- - This contract does not modify q1travel-web, publish the package, migrate a
163
+ - This contract does not modify downstream consumers, publish the package, migrate a
164
164
  consumer, close #25, or create implementation issues.
165
165
 
166
166
  ## Implementation sequence
167
167
 
168
168
  `Table core` → `Interaction` → `Layout` → `export/types/tests` → `published
169
- package verification` → `q1travel-web migration`
169
+ package verification` → `downstream migration`
170
170
 
171
171
  Each arrow is a separate acceptance gate. A passing documentation specimen,
172
172
  local package build, or source-level assertion does not establish the next gate.