@chromvoid/headless-ui 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/a11y-contracts/index.d.ts +23 -0
- package/dist/a11y-contracts/index.js +1 -0
- package/dist/accordion/index.d.ts +78 -0
- package/dist/accordion/index.js +264 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +1 -0
- package/dist/alert/index.d.ts +33 -0
- package/dist/alert/index.js +54 -0
- package/dist/alert-dialog/index.d.ts +69 -0
- package/dist/alert-dialog/index.js +94 -0
- package/dist/badge/index.d.ts +48 -0
- package/dist/badge/index.js +89 -0
- package/dist/breadcrumb/index.d.ts +55 -0
- package/dist/breadcrumb/index.js +77 -0
- package/dist/button/index.d.ts +46 -0
- package/dist/button/index.js +86 -0
- package/dist/callout/index.d.ts +41 -0
- package/dist/callout/index.js +63 -0
- package/dist/card/index.d.ts +54 -0
- package/dist/card/index.js +103 -0
- package/dist/carousel/index.d.ts +98 -0
- package/dist/carousel/index.js +243 -0
- package/dist/checkbox/index.d.ts +50 -0
- package/dist/checkbox/index.js +87 -0
- package/dist/combobox/index.d.ts +114 -0
- package/dist/combobox/index.js +431 -0
- package/dist/command-palette/index.d.ts +73 -0
- package/dist/command-palette/index.js +147 -0
- package/dist/context-menu/index.d.ts +111 -0
- package/dist/context-menu/index.js +372 -0
- package/dist/copy-button/index.d.ts +62 -0
- package/dist/copy-button/index.js +183 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.js +2 -0
- package/dist/core/selection.d.ts +5 -0
- package/dist/core/selection.js +39 -0
- package/dist/core/value-range.d.ts +49 -0
- package/dist/core/value-range.js +134 -0
- package/dist/date-picker/index.d.ts +210 -0
- package/dist/date-picker/index.js +895 -0
- package/dist/dialog/index.d.ts +95 -0
- package/dist/dialog/index.js +153 -0
- package/dist/disclosure/index.d.ts +52 -0
- package/dist/disclosure/index.js +159 -0
- package/dist/drawer/index.d.ts +30 -0
- package/dist/drawer/index.js +39 -0
- package/dist/feed/index.d.ts +77 -0
- package/dist/feed/index.js +260 -0
- package/dist/grid/index.d.ts +103 -0
- package/dist/grid/index.js +415 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +51 -0
- package/dist/input/index.d.ts +86 -0
- package/dist/input/index.js +156 -0
- package/dist/interactions/composite-navigation.d.ts +69 -0
- package/dist/interactions/composite-navigation.js +169 -0
- package/dist/interactions/index.d.ts +15 -0
- package/dist/interactions/index.js +4 -0
- package/dist/interactions/keyboard-intents.d.ts +16 -0
- package/dist/interactions/keyboard-intents.js +33 -0
- package/dist/interactions/overlay-focus.d.ts +40 -0
- package/dist/interactions/overlay-focus.js +93 -0
- package/dist/interactions/typeahead.d.ts +20 -0
- package/dist/interactions/typeahead.js +41 -0
- package/dist/landmarks/index.d.ts +39 -0
- package/dist/landmarks/index.js +58 -0
- package/dist/link/index.d.ts +34 -0
- package/dist/link/index.js +39 -0
- package/dist/listbox/index.d.ts +92 -0
- package/dist/listbox/index.js +337 -0
- package/dist/menu/index.d.ts +132 -0
- package/dist/menu/index.js +541 -0
- package/dist/menu-button/index.d.ts +71 -0
- package/dist/menu-button/index.js +121 -0
- package/dist/meter/index.d.ts +45 -0
- package/dist/meter/index.js +106 -0
- package/dist/number/index.d.ts +113 -0
- package/dist/number/index.js +252 -0
- package/dist/popover/index.d.ts +70 -0
- package/dist/popover/index.js +126 -0
- package/dist/progress/index.d.ts +49 -0
- package/dist/progress/index.js +79 -0
- package/dist/radio-group/index.d.ts +61 -0
- package/dist/radio-group/index.js +150 -0
- package/dist/select/index.d.ts +92 -0
- package/dist/select/index.js +239 -0
- package/dist/sidebar/index.d.ts +74 -0
- package/dist/sidebar/index.js +186 -0
- package/dist/slider/index.d.ts +61 -0
- package/dist/slider/index.js +150 -0
- package/dist/slider-multi-thumb/index.d.ts +70 -0
- package/dist/slider-multi-thumb/index.js +222 -0
- package/dist/spinbutton/index.d.ts +75 -0
- package/dist/spinbutton/index.js +214 -0
- package/dist/spinner/index.d.ts +1 -0
- package/dist/spinner/index.js +1 -0
- package/dist/spinner/spinner.d.ts +23 -0
- package/dist/spinner/spinner.js +25 -0
- package/dist/switch/index.d.ts +40 -0
- package/dist/switch/index.js +61 -0
- package/dist/table/index.d.ts +117 -0
- package/dist/table/index.js +377 -0
- package/dist/tabs/index.d.ts +63 -0
- package/dist/tabs/index.js +174 -0
- package/dist/textarea/index.d.ts +68 -0
- package/dist/textarea/index.js +137 -0
- package/dist/toast/index.d.ts +67 -0
- package/dist/toast/index.js +145 -0
- package/dist/toolbar/index.d.ts +59 -0
- package/dist/toolbar/index.js +139 -0
- package/dist/tooltip/index.d.ts +52 -0
- package/dist/tooltip/index.js +169 -0
- package/dist/treegrid/index.d.ts +101 -0
- package/dist/treegrid/index.js +463 -0
- package/dist/treeview/index.d.ts +68 -0
- package/dist/treeview/index.js +370 -0
- package/dist/window-splitter/index.d.ts +65 -0
- package/dist/window-splitter/index.js +204 -0
- package/package.json +92 -0
- package/specs/ADR-001-headless-architecture.md +461 -0
- package/specs/ADR-002-repo-release-model.md +108 -0
- package/specs/ADR-003-public-api-versioning.md +136 -0
- package/specs/ADR-004-focus-selection-policy.md +117 -0
- package/specs/IMPLEMENTATION-ROADMAP.md +237 -0
- package/specs/ISSUE-BACKLOG.md +681 -0
- package/specs/RELEASE-CANDIDATE.md +30 -0
- package/specs/components/accordion.md +130 -0
- package/specs/components/alert-dialog.md +72 -0
- package/specs/components/alert.md +65 -0
- package/specs/components/badge.md +220 -0
- package/specs/components/breadcrumb.md +74 -0
- package/specs/components/button.md +115 -0
- package/specs/components/callout.md +195 -0
- package/specs/components/card.md +280 -0
- package/specs/components/carousel.md +140 -0
- package/specs/components/checkbox.md +172 -0
- package/specs/components/combobox.md +423 -0
- package/specs/components/command-palette.md +92 -0
- package/specs/components/context-menu.md +556 -0
- package/specs/components/copy-button.md +293 -0
- package/specs/components/date-picker.md +400 -0
- package/specs/components/dialog.md +298 -0
- package/specs/components/disclosure.md +257 -0
- package/specs/components/drawer.md +353 -0
- package/specs/components/feed.md +265 -0
- package/specs/components/grid.md +186 -0
- package/specs/components/input.md +254 -0
- package/specs/components/landmarks.md +136 -0
- package/specs/components/link.md +134 -0
- package/specs/components/listbox.md +351 -0
- package/specs/components/menu-button.md +76 -0
- package/specs/components/menu.md +623 -0
- package/specs/components/meter.md +149 -0
- package/specs/components/number.md +393 -0
- package/specs/components/popover.md +252 -0
- package/specs/components/progress.md +188 -0
- package/specs/components/radio-group.md +151 -0
- package/specs/components/select.md +144 -0
- package/specs/components/sidebar.md +321 -0
- package/specs/components/slider-multi-thumb.md +78 -0
- package/specs/components/slider.md +84 -0
- package/specs/components/spinbutton.md +140 -0
- package/specs/components/spinner.md +132 -0
- package/specs/components/switch.md +175 -0
- package/specs/components/table.md +403 -0
- package/specs/components/tabs.md +265 -0
- package/specs/components/textarea.md +185 -0
- package/specs/components/toast.md +198 -0
- package/specs/components/toolbar.md +278 -0
- package/specs/components/tooltip.md +252 -0
- package/specs/components/treegrid.md +281 -0
- package/specs/components/treeview.md +91 -0
- package/specs/components/window-splitter.md +297 -0
- package/specs/ops/git-shard-sync.md +107 -0
- package/specs/ops/release-checklist.md +76 -0
- package/specs/release/GAP-TO-GREEN-ISSUES.md +88 -0
- package/specs/release/api-freeze-candidate.md +54 -0
- package/specs/release/changelog-automation.md +76 -0
- package/specs/release/changelog.generated.md +53 -0
- package/specs/release/changelog.patch.generated.md +46 -0
- package/specs/release/consumer-integration.md +53 -0
- package/specs/release/migration-notes-pre-v1.md +40 -0
- package/specs/release/mvp-changelog.md +57 -0
- package/specs/release/release-notes-template.md +61 -0
- package/specs/release/release-rehearsal.md +113 -0
- package/specs/release/semver-deprecation-dry-run.md +89 -0
- package/specs/release/shard-release-drill-report.md +50 -0
- package/specs/release/shard-release-follow-ups.md +31 -0
- package/specs/signals.md +208 -0
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chromvoid/headless-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless interaction and accessibility contracts for future UI kit",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"a11y",
|
|
7
|
+
"apg",
|
|
8
|
+
"headless",
|
|
9
|
+
"reatom"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/chromvoid/headless-ui#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/chromvoid/headless-ui/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "ChromVoid Team",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/chromvoid/headless-ui.git"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"README.md",
|
|
25
|
+
"specs"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./*": {
|
|
39
|
+
"types": "./dist/*/index.d.ts",
|
|
40
|
+
"import": "./dist/*/index.js",
|
|
41
|
+
"default": "./dist/*/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./core/*": {
|
|
44
|
+
"types": "./dist/core/*.d.ts",
|
|
45
|
+
"import": "./dist/core/*.js",
|
|
46
|
+
"default": "./dist/core/*.js"
|
|
47
|
+
},
|
|
48
|
+
"./interactions/*": {
|
|
49
|
+
"types": "./dist/interactions/*.d.ts",
|
|
50
|
+
"import": "./dist/interactions/*.js",
|
|
51
|
+
"default": "./dist/interactions/*.js"
|
|
52
|
+
},
|
|
53
|
+
"./package.json": "./package.json"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && node ./scripts/postbuild.mjs",
|
|
60
|
+
"check:bundle": "npm run build && node ./scripts/check-bundle-contract.mjs",
|
|
61
|
+
"check:exports": "npm run build && node ./scripts/check-package-exports.mjs",
|
|
62
|
+
"check:pack": "npm pack --dry-run",
|
|
63
|
+
"format": "oxfmt --write README.md package.json tsconfig.json tsconfig.build.json vitest.config.ts .oxlintrc.json .oxfmtrc.json src scripts specs",
|
|
64
|
+
"lint": "npm run lint:types && npm run lint:oxlint && npm run lint:format && npm run lint:boundaries",
|
|
65
|
+
"lint:boundaries": "node ./scripts/check-standalone-boundaries.mjs",
|
|
66
|
+
"lint:format": "oxfmt --check README.md package.json tsconfig.json tsconfig.build.json vitest.config.ts .oxlintrc.json .oxfmtrc.json src scripts specs",
|
|
67
|
+
"lint:oxlint": "oxlint --config ./.oxlintrc.json src scripts vitest.config.ts",
|
|
68
|
+
"lint:release-governance": "node ./scripts/check-release-governance.mjs",
|
|
69
|
+
"lint:types": "tsc --noEmit -p tsconfig.json",
|
|
70
|
+
"prepack": "npm run build",
|
|
71
|
+
"prepublishOnly": "npm run lint && npm run test && npm run check:pack",
|
|
72
|
+
"release:changelog": "node ./scripts/generate-changelog.mjs --mode full --out ./specs/release/changelog.generated.md && oxfmt --write ./specs/release/changelog.generated.md",
|
|
73
|
+
"release:changelog:patch": "node ./scripts/generate-changelog.mjs --mode patch --out ./specs/release/changelog.patch.generated.md && oxfmt --write ./specs/release/changelog.patch.generated.md",
|
|
74
|
+
"test": "npm run test:unit && npm run check:exports && npm run check:bundle",
|
|
75
|
+
"test:unit": "vitest run --config ./vitest.config.ts",
|
|
76
|
+
"test:watch": "vitest --config ./vitest.config.ts --watch"
|
|
77
|
+
},
|
|
78
|
+
"dependencies": {
|
|
79
|
+
"@reatom/core": "^1000.15.0"
|
|
80
|
+
},
|
|
81
|
+
"devDependencies": {
|
|
82
|
+
"@types/node": "^24.11.0",
|
|
83
|
+
"esbuild": "^0.27.4",
|
|
84
|
+
"oxfmt": "^0.41.0",
|
|
85
|
+
"oxlint": "^1.51.0",
|
|
86
|
+
"typescript": "^5.9.3",
|
|
87
|
+
"vitest": "^3.2.4"
|
|
88
|
+
},
|
|
89
|
+
"engines": {
|
|
90
|
+
"node": ">=20.0.0"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
# ADR-001: Architecture of an Independent Headless Package
|
|
2
|
+
|
|
3
|
+
> **Status**: Accepted
|
|
4
|
+
> **Version**: 2026-02-08-r6
|
|
5
|
+
> **Date**: 2026-02-08
|
|
6
|
+
> **Authors**: Team ChromVoid
|
|
7
|
+
> **Related Documents**:
|
|
8
|
+
>
|
|
9
|
+
> - [packages/headless/README.md](../README.md) - package goal
|
|
10
|
+
> - [WAI-ARIA APG patterns](https://www.w3.org/WAI/ARIA/apg/patterns/) - accessibility behavior patterns
|
|
11
|
+
> - [WAI-ARIA APG keyboard interface](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/) - keyboard and focus rules
|
|
12
|
+
> - [Reatom docs](https://www.reatom.dev/) - state management model
|
|
13
|
+
> - [ADR-002-repo-release-model](./ADR-002-repo-release-model.md) - git-shard and release ownership
|
|
14
|
+
> - [ADR-003-public-api-versioning](./ADR-003-public-api-versioning.md) - SemVer and deprecation policy
|
|
15
|
+
> - [ADR-004-focus-selection-policy](./ADR-004-focus-selection-policy.md) - shared focus/selection policy
|
|
16
|
+
>
|
|
17
|
+
> **Note**: `packages/headless` in this monorepo is a development mirror.
|
|
18
|
+
> The canonical source, versioning, and publishing flow live in a separate public git-shard repository.
|
|
19
|
+
|
|
20
|
+
## Context
|
|
21
|
+
|
|
22
|
+
We need a highly independent headless package for a future UI kit:
|
|
23
|
+
|
|
24
|
+
- no visual layer;
|
|
25
|
+
- Reatom v1000 as the state runtime;
|
|
26
|
+
- behavior contracts aligned with WAI-ARIA APG.
|
|
27
|
+
|
|
28
|
+
The package is developed in this monorepo for local integration speed,
|
|
29
|
+
but it must stay independent from internal architecture and internal modules.
|
|
30
|
+
|
|
31
|
+
## Problem
|
|
32
|
+
|
|
33
|
+
If this package is treated as a regular internal monorepo module, we get:
|
|
34
|
+
|
|
35
|
+
- hidden dependencies on `@project/*`, `apps/*`, and internal scripts;
|
|
36
|
+
- low portability outside this repository;
|
|
37
|
+
- release and publishing ambiguity;
|
|
38
|
+
- API evolution coupled to one product architecture.
|
|
39
|
+
|
|
40
|
+
## Goals
|
|
41
|
+
|
|
42
|
+
1. Lock architectural independence as a hard requirement.
|
|
43
|
+
2. Define module boundaries: `core -> interactions -> a11y-contracts -> adapters`.
|
|
44
|
+
3. Define a stable public API shape (`createX`, `get*Props`, `actions/selectors`).
|
|
45
|
+
4. Lock repository model: public git-shard is the canonical source.
|
|
46
|
+
5. Define standalone build/test/release expectations.
|
|
47
|
+
|
|
48
|
+
## Non-Goals
|
|
49
|
+
|
|
50
|
+
- Adapting architecture to current internal monorepo modules.
|
|
51
|
+
- Using internal dependencies such as `@project/*`.
|
|
52
|
+
- Migrating existing apps in this ADR.
|
|
53
|
+
- Implementing all UI widgets in this ADR.
|
|
54
|
+
|
|
55
|
+
## Decision
|
|
56
|
+
|
|
57
|
+
### 1. Repository Model and Ownership
|
|
58
|
+
|
|
59
|
+
1. `packages/headless` in this monorepo is a development mirror.
|
|
60
|
+
2. Canonical code history, tags, and releases are managed in a separate public git-shard repository.
|
|
61
|
+
3. Publishing to package registries is done only from git-shard.
|
|
62
|
+
|
|
63
|
+
**MUST NOT**: use the monorepo as a release source of truth for this package.
|
|
64
|
+
|
|
65
|
+
### 2. Independence Constraints (Hard)
|
|
66
|
+
|
|
67
|
+
The package **MUST**:
|
|
68
|
+
|
|
69
|
+
- build and test in an isolated checkout without this monorepo;
|
|
70
|
+
- use only public dependencies;
|
|
71
|
+
- have package-local scripts, specs, tests, and release flow.
|
|
72
|
+
|
|
73
|
+
The package **MUST NOT**:
|
|
74
|
+
|
|
75
|
+
- import `@project/*`, `apps/*`, or files outside package boundaries;
|
|
76
|
+
- rely on monorepo lifecycle scripts as required runtime/release steps;
|
|
77
|
+
- encode product-specific assumptions.
|
|
78
|
+
|
|
79
|
+
### 3. Layered Architecture
|
|
80
|
+
|
|
81
|
+
The package follows one-way dependencies:
|
|
82
|
+
|
|
83
|
+
1. `core/`
|
|
84
|
+
|
|
85
|
+
- state and invariants on Reatom;
|
|
86
|
+
- no DOM or framework imports.
|
|
87
|
+
|
|
88
|
+
2. `interactions/`
|
|
89
|
+
|
|
90
|
+
- reusable keyboard/pointer intent logic;
|
|
91
|
+
- no rendering concerns.
|
|
92
|
+
|
|
93
|
+
3. `a11y-contracts/`
|
|
94
|
+
|
|
95
|
+
- APG-oriented role and aria contracts;
|
|
96
|
+
- focus and selection semantics.
|
|
97
|
+
|
|
98
|
+
4. `adapters/`
|
|
99
|
+
|
|
100
|
+
- thin mapping to presentation layers;
|
|
101
|
+
- no business logic.
|
|
102
|
+
|
|
103
|
+
`core` does not depend on `adapters`, concrete app code, or design system code.
|
|
104
|
+
|
|
105
|
+
### 4. Public API
|
|
106
|
+
|
|
107
|
+
Stable API shape:
|
|
108
|
+
|
|
109
|
+
- `createX(options)`;
|
|
110
|
+
- `state` and `selectors`;
|
|
111
|
+
- `actions` and `api` (or `contracts` for grouped prop getters);
|
|
112
|
+
- `get*Props()`.
|
|
113
|
+
|
|
114
|
+
#### API Terminology Mapping
|
|
115
|
+
|
|
116
|
+
To ensure consistency across components, we use the following canonical mapping:
|
|
117
|
+
|
|
118
|
+
- **State**: Raw reactive signals (Reatom atoms).
|
|
119
|
+
- **Selectors**: Computed derivations of state (Reatom computed).
|
|
120
|
+
- **Actions**: Methods that mutate state or trigger side effects.
|
|
121
|
+
- **Contracts / API**: Grouped prop getters (`get*Props`) that return ARIA-compliant attribute sets.
|
|
122
|
+
|
|
123
|
+
#### Selectors Guidance
|
|
124
|
+
|
|
125
|
+
Selectors MUST be used for any derived state logic to ensure:
|
|
126
|
+
|
|
127
|
+
1. Components only re-render when the specific derived value changes.
|
|
128
|
+
2. Business logic is decoupled from the raw state structure.
|
|
129
|
+
3. Complex calculations are memoized via Reatom's `computed`.
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
|
|
133
|
+
- `createListbox(options) -> { state, selectors, actions, getRootProps, getOptionProps }`.
|
|
134
|
+
|
|
135
|
+
### 5. State Runtime Policy
|
|
136
|
+
|
|
137
|
+
- **MUST**: Reatom v1000.
|
|
138
|
+
- **MUST NOT**: `@statx/*` in headless core.
|
|
139
|
+
|
|
140
|
+
### 6. Testing and Verification
|
|
141
|
+
|
|
142
|
+
Implementation strategy:
|
|
143
|
+
|
|
144
|
+
- TDD (red-green-refactor);
|
|
145
|
+
- unit tests for transitions, invariants, and contracts;
|
|
146
|
+
- integration tests for adapter behavior;
|
|
147
|
+
- agent-executed QA scenarios for keyboard and focus flows.
|
|
148
|
+
|
|
149
|
+
Additional rule:
|
|
150
|
+
|
|
151
|
+
- standalone test execution (outside monorepo coupling) is mandatory.
|
|
152
|
+
|
|
153
|
+
### 7. Component Packaging Convention
|
|
154
|
+
|
|
155
|
+
For every headless component:
|
|
156
|
+
|
|
157
|
+
- use a dedicated directory: `src/<component>/`;
|
|
158
|
+
- expose a component entrypoint: `src/<component>/index.ts`;
|
|
159
|
+
- keep unit tests near component code: `src/<component>/<component>.test.ts`;
|
|
160
|
+
- add a component spec file: `specs/components/<component>.md`.
|
|
161
|
+
|
|
162
|
+
## Alternatives
|
|
163
|
+
|
|
164
|
+
### A. Keep the package fully internal in this monorepo
|
|
165
|
+
|
|
166
|
+
**Rejected**:
|
|
167
|
+
|
|
168
|
+
- conflicts with independent/public package goals;
|
|
169
|
+
- increases risk of hidden internal coupling.
|
|
170
|
+
|
|
171
|
+
### B. Build as monorepo-only first, split later
|
|
172
|
+
|
|
173
|
+
**Rejected**:
|
|
174
|
+
|
|
175
|
+
- split migration is expensive and often breaks API;
|
|
176
|
+
- increases architecture debt.
|
|
177
|
+
|
|
178
|
+
### C. Mix headless logic into current UI modules
|
|
179
|
+
|
|
180
|
+
**Rejected**:
|
|
181
|
+
|
|
182
|
+
- breaks package independence;
|
|
183
|
+
- reduces reuse outside current product boundaries.
|
|
184
|
+
|
|
185
|
+
## Consequences
|
|
186
|
+
|
|
187
|
+
### Positive
|
|
188
|
+
|
|
189
|
+
- portable package reusable outside this repository;
|
|
190
|
+
- release lifecycle independent from monorepo cadence;
|
|
191
|
+
- architecture decisions are not constrained by one internal system;
|
|
192
|
+
- better fit for public distribution.
|
|
193
|
+
|
|
194
|
+
### Negative
|
|
195
|
+
|
|
196
|
+
- additional sync overhead between mirror and git-shard;
|
|
197
|
+
- separate CI/CD and release governance required;
|
|
198
|
+
- local development requires strict import-boundary discipline.
|
|
199
|
+
|
|
200
|
+
## Comprehensive Implementation Roadmap
|
|
201
|
+
|
|
202
|
+
This section is the execution blueprint for component delivery, test hardening,
|
|
203
|
+
and release readiness. It is intentionally explicit to reduce architectural drift.
|
|
204
|
+
|
|
205
|
+
Issue-ready decomposition is maintained in:
|
|
206
|
+
|
|
207
|
+
- [ISSUE-BACKLOG.md](./ISSUE-BACKLOG.md)
|
|
208
|
+
|
|
209
|
+
### Program-Level Principles
|
|
210
|
+
|
|
211
|
+
1. Build in vertical slices (`state -> interactions -> a11y contracts -> tests -> docs`).
|
|
212
|
+
2. Do not introduce shared abstractions before at least two components need them.
|
|
213
|
+
3. Keep APG behavior contracts as first-class public API.
|
|
214
|
+
4. Every behavior change requires test updates in the same change set.
|
|
215
|
+
5. Release only from git-shard after all package gates pass.
|
|
216
|
+
|
|
217
|
+
### Workstreams
|
|
218
|
+
|
|
219
|
+
- **Component Workstream**: component directories, component APIs, specs.
|
|
220
|
+
- **Testing Workstream**: unit/contract/integration tests and regression suites.
|
|
221
|
+
- **Tooling Workstream**: lint, boundary checks, CI jobs, release automation.
|
|
222
|
+
- **Documentation Workstream**: ADR updates, component specs, migration notes.
|
|
223
|
+
|
|
224
|
+
### Delivery Waves
|
|
225
|
+
|
|
226
|
+
| Wave | Scope | Primary Output | Exit Criteria |
|
|
227
|
+
| ---- | ------------------- | ------------------------------------------------------------------ | -------------------------------------------------------- |
|
|
228
|
+
| 0 | Foundation Baseline | standalone package skeleton + guardrails | lint/test/boundary gates are green |
|
|
229
|
+
| 1 | Listbox Hardening | production-ready listbox contract | APG key behavior and advanced tests complete |
|
|
230
|
+
| 2 | Shared Primitives | reusable interaction helpers extracted from listbox/combobox needs | at least 2 consumers use each extracted primitive |
|
|
231
|
+
| 3 | Combobox | APG combobox headless contract | keyboard + filtering + active-descendant contract stable |
|
|
232
|
+
| 4 | Menu | menu button + menu contract | open/close/nav/dismiss behavior stable |
|
|
233
|
+
| 5 | Tabs | tabs + tabpanel contract | manual and automatic activation modes verified |
|
|
234
|
+
| 6 | Treeview | hierarchical navigation contract | expansion and structural ARIA invariants verified |
|
|
235
|
+
| 7 | Release Readiness | first public stable release line | SemVer/deprecation/release process validated end-to-end |
|
|
236
|
+
|
|
237
|
+
### Wave 0: Foundation Baseline
|
|
238
|
+
|
|
239
|
+
**Status**: complete
|
|
240
|
+
|
|
241
|
+
Deliverables:
|
|
242
|
+
|
|
243
|
+
- package skeleton with layered directories
|
|
244
|
+
- package-local lint, format, boundary checks
|
|
245
|
+
- dedicated CI workflow for headless paths
|
|
246
|
+
- ADR-001..ADR-004 baseline architecture docs
|
|
247
|
+
|
|
248
|
+
Completion notes:
|
|
249
|
+
|
|
250
|
+
- git-shard mirror automation policy documented in `specs/ops/git-shard-sync.md`
|
|
251
|
+
- shard release checklist documented in `specs/ops/release-checklist.md`
|
|
252
|
+
|
|
253
|
+
### Wave 1: Listbox Hardening
|
|
254
|
+
|
|
255
|
+
Deliverables:
|
|
256
|
+
|
|
257
|
+
- finalize `createListbox` behavior contract
|
|
258
|
+
- add missing APG-compliant interactions:
|
|
259
|
+
- typeahead (single char + buffered sequence)
|
|
260
|
+
- optional range selection (`Shift+Arrow`, `Shift+Space`) in multi-select mode
|
|
261
|
+
- orientation parity and edge-case handling
|
|
262
|
+
- finalize `specs/components/listbox.md`
|
|
263
|
+
|
|
264
|
+
Required test matrix additions:
|
|
265
|
+
|
|
266
|
+
- typeahead buffer lifecycle and matching order
|
|
267
|
+
- range selection semantics and invariant checks
|
|
268
|
+
- horizontal keyboard map parity
|
|
269
|
+
- disabled-option behavior for all interaction paths
|
|
270
|
+
- focus strategy parity (`roving-tabindex` vs `aria-activedescendant`)
|
|
271
|
+
|
|
272
|
+
Exit criteria:
|
|
273
|
+
|
|
274
|
+
- listbox test suite covers all APG-required core behavior for supported modes
|
|
275
|
+
- no TODO/FIXME placeholders in listbox contract docs
|
|
276
|
+
|
|
277
|
+
### Wave 2: Shared Primitives Extraction
|
|
278
|
+
|
|
279
|
+
Scope:
|
|
280
|
+
|
|
281
|
+
- extract only proven shared logic from completed components
|
|
282
|
+
- no speculative utility modules
|
|
283
|
+
|
|
284
|
+
Candidate primitives:
|
|
285
|
+
|
|
286
|
+
- keyboard intent mapping
|
|
287
|
+
- typeahead engine
|
|
288
|
+
- roving index and active-descendant bookkeeping
|
|
289
|
+
- selection reducers (single/multi/range)
|
|
290
|
+
|
|
291
|
+
Exit criteria:
|
|
292
|
+
|
|
293
|
+
- each primitive has at least two component consumers
|
|
294
|
+
- each primitive has direct unit tests and contract-level tests via components
|
|
295
|
+
|
|
296
|
+
### Wave 3: Combobox
|
|
297
|
+
|
|
298
|
+
Target files:
|
|
299
|
+
|
|
300
|
+
- `src/combobox/index.ts`
|
|
301
|
+
- `src/combobox/combobox.test.ts`
|
|
302
|
+
- `specs/components/combobox.md`
|
|
303
|
+
|
|
304
|
+
Core behaviors:
|
|
305
|
+
|
|
306
|
+
- controlled/uncontrolled input value contract
|
|
307
|
+
- popup open/close semantics
|
|
308
|
+
- active option tracking via `aria-activedescendant`
|
|
309
|
+
- option commit behavior (`Enter`, click, selection actions)
|
|
310
|
+
|
|
311
|
+
Tests:
|
|
312
|
+
|
|
313
|
+
- keyboard navigation across input and listbox states
|
|
314
|
+
- input filtering hooks contract
|
|
315
|
+
- aria linkage integrity (`aria-controls`, `aria-activedescendant`)
|
|
316
|
+
- disabled and empty-result states
|
|
317
|
+
|
|
318
|
+
Exit criteria:
|
|
319
|
+
|
|
320
|
+
- combobox behavior is APG-aligned for supported mode
|
|
321
|
+
- filtering and commit behavior are deterministic and documented
|
|
322
|
+
|
|
323
|
+
### Wave 4: Menu
|
|
324
|
+
|
|
325
|
+
Target files:
|
|
326
|
+
|
|
327
|
+
- `src/menu/index.ts`
|
|
328
|
+
- `src/menu/menu.test.ts`
|
|
329
|
+
- `specs/components/menu.md`
|
|
330
|
+
|
|
331
|
+
Core behaviors:
|
|
332
|
+
|
|
333
|
+
- trigger-open-close model
|
|
334
|
+
- arrow navigation and wrapping policy
|
|
335
|
+
- dismissal semantics (`Escape`, outside interactions)
|
|
336
|
+
|
|
337
|
+
Tests:
|
|
338
|
+
|
|
339
|
+
- open source (keyboard vs pointer) behavior
|
|
340
|
+
- focus return policy on close
|
|
341
|
+
- disabled item and activation rules
|
|
342
|
+
|
|
343
|
+
Exit criteria:
|
|
344
|
+
|
|
345
|
+
- deterministic menu lifecycle and key contract across supported modes
|
|
346
|
+
|
|
347
|
+
### Wave 5: Tabs
|
|
348
|
+
|
|
349
|
+
Target files:
|
|
350
|
+
|
|
351
|
+
- `src/tabs/index.ts`
|
|
352
|
+
- `src/tabs/tabs.test.ts`
|
|
353
|
+
- `specs/components/tabs.md`
|
|
354
|
+
|
|
355
|
+
Core behaviors:
|
|
356
|
+
|
|
357
|
+
- manual vs automatic activation modes
|
|
358
|
+
- orientation-aware key navigation
|
|
359
|
+
- tab/panel relationship contracts
|
|
360
|
+
|
|
361
|
+
Tests:
|
|
362
|
+
|
|
363
|
+
- activation mode deltas
|
|
364
|
+
- disabled tab traversal
|
|
365
|
+
- role/aria linkage contract assertions
|
|
366
|
+
|
|
367
|
+
Exit criteria:
|
|
368
|
+
|
|
369
|
+
- tabs behavior contract documented and stable for both activation modes
|
|
370
|
+
|
|
371
|
+
### Wave 6: Treeview
|
|
372
|
+
|
|
373
|
+
Target files:
|
|
374
|
+
|
|
375
|
+
- `src/treeview/index.ts`
|
|
376
|
+
- `src/treeview/treeview.test.ts`
|
|
377
|
+
- `specs/components/treeview.md`
|
|
378
|
+
|
|
379
|
+
Core behaviors:
|
|
380
|
+
|
|
381
|
+
- hierarchical expansion/collapse semantics
|
|
382
|
+
- visible-node focus traversal
|
|
383
|
+
- optional multi-select behavior model
|
|
384
|
+
|
|
385
|
+
Tests:
|
|
386
|
+
|
|
387
|
+
- structural ARIA metadata (`aria-level`, `aria-posinset`, `aria-setsize`)
|
|
388
|
+
- expansion keyboard behavior (`ArrowLeft`, `ArrowRight`)
|
|
389
|
+
- selected/focused node invariants under collapse
|
|
390
|
+
|
|
391
|
+
Exit criteria:
|
|
392
|
+
|
|
393
|
+
- tree behavior is deterministic and APG-compliant for supported subset
|
|
394
|
+
|
|
395
|
+
### Wave 7: Release Readiness and Stabilization
|
|
396
|
+
|
|
397
|
+
Scope:
|
|
398
|
+
|
|
399
|
+
- API freeze candidate and release candidate process
|
|
400
|
+
- SemVer and deprecation process dry-run
|
|
401
|
+
- shard-only release pipeline validation
|
|
402
|
+
|
|
403
|
+
Deliverables:
|
|
404
|
+
|
|
405
|
+
- release checklist in shard
|
|
406
|
+
- migration notes for any pre-1.0 breaking cleanup
|
|
407
|
+
- first stable tag candidate strategy
|
|
408
|
+
|
|
409
|
+
Exit criteria:
|
|
410
|
+
|
|
411
|
+
- full release drill succeeds from git-shard with no monorepo coupling
|
|
412
|
+
|
|
413
|
+
### Testing Roadmap by Layer
|
|
414
|
+
|
|
415
|
+
1. **State Tests**: transitions, invariants, reducer-like behavior.
|
|
416
|
+
2. **Interaction Tests**: keyboard intent mapping and edge key sequences.
|
|
417
|
+
3. **A11y Contract Tests**: exact role/aria/tabindex prop contracts.
|
|
418
|
+
4. **Integration Tests**: component-level behavior with realistic option sets.
|
|
419
|
+
5. **Regression Tests**: one test per fixed bug before closure.
|
|
420
|
+
|
|
421
|
+
### Definition of Done (Per Component)
|
|
422
|
+
|
|
423
|
+
1. Component code in `src/<component>/`.
|
|
424
|
+
2. Component spec in `specs/components/<component>.md`.
|
|
425
|
+
3. Export wired in `src/index.ts`.
|
|
426
|
+
4. Full test matrix for supported behavior.
|
|
427
|
+
5. `npm run lint` is green.
|
|
428
|
+
6. `npm run test` is green.
|
|
429
|
+
7. Boundary check remains green.
|
|
430
|
+
|
|
431
|
+
### Prioritized Execution Order
|
|
432
|
+
|
|
433
|
+
1. Listbox hardening
|
|
434
|
+
2. Combobox
|
|
435
|
+
3. Menu
|
|
436
|
+
4. Tabs
|
|
437
|
+
5. Treeview
|
|
438
|
+
|
|
439
|
+
This order maximizes reuse of interaction primitives while minimizing rework.
|
|
440
|
+
|
|
441
|
+
### Risk Register and Mitigations
|
|
442
|
+
|
|
443
|
+
- **Risk**: over-abstraction too early.
|
|
444
|
+
- **Mitigation**: extract shared primitives only after two concrete consumers.
|
|
445
|
+
- **Risk**: APG drift between components.
|
|
446
|
+
- **Mitigation**: enforce ADR-004 invariants + contract tests.
|
|
447
|
+
- **Risk**: hidden monorepo coupling.
|
|
448
|
+
- **Mitigation**: keep boundary script mandatory in CI and release gates.
|
|
449
|
+
- **Risk**: behavior-breaking changes slip into minor releases.
|
|
450
|
+
- **Mitigation**: enforce ADR-003 release classification checklist.
|
|
451
|
+
|
|
452
|
+
## Change History
|
|
453
|
+
|
|
454
|
+
| Date | Change |
|
|
455
|
+
| ---------- | ---------------------------------------------------------------------------------------------------- |
|
|
456
|
+
| 2026-02-08 | Initial draft created (r1) |
|
|
457
|
+
| 2026-02-08 | Reframed as standalone package with git-shard ownership (r2) |
|
|
458
|
+
| 2026-02-08 | Rewritten to full English and aligned with current conventions (r3) |
|
|
459
|
+
| 2026-02-08 | Expanded with a comprehensive multi-wave roadmap, test matrix, exit criteria, and risk register (r4) |
|
|
460
|
+
| 2026-02-08 | Added issue-ready backlog linkage and execution decomposition (r5) |
|
|
461
|
+
| 2026-02-08 | Marked ADR as accepted and synced Wave 0 completion details to current implementation state (r6) |
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# ADR-002: Repository and Release Model for the Headless Package
|
|
2
|
+
|
|
3
|
+
> **Status**: Draft
|
|
4
|
+
> **Version**: 2026-02-08-r2
|
|
5
|
+
> **Date**: 2026-02-08
|
|
6
|
+
> **Authors**: Team ChromVoid
|
|
7
|
+
> **Related Documents**:
|
|
8
|
+
>
|
|
9
|
+
> - [ADR-001-headless-architecture](./ADR-001-headless-architecture.md) - architectural independence baseline
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
`packages/headless` is developed in the monorepo for local integration convenience.
|
|
14
|
+
The package must still be published as an independent public package from a separate git-shard repository.
|
|
15
|
+
|
|
16
|
+
Without a clear model, mirror and shard will drift, and releases will become hard to reproduce.
|
|
17
|
+
|
|
18
|
+
## Problem
|
|
19
|
+
|
|
20
|
+
We need a formal source-of-truth and release flow that answers:
|
|
21
|
+
|
|
22
|
+
- where canonical history lives;
|
|
23
|
+
- where npm publishing happens;
|
|
24
|
+
- how changes sync between mirror and shard;
|
|
25
|
+
- how to prevent accidental releases from monorepo.
|
|
26
|
+
|
|
27
|
+
## Goals
|
|
28
|
+
|
|
29
|
+
1. Set git-shard as the only canonical source.
|
|
30
|
+
2. Define release flow from git-shard only.
|
|
31
|
+
3. Define sync rules between monorepo mirror and shard.
|
|
32
|
+
4. Reduce drift and accidental release risk.
|
|
33
|
+
|
|
34
|
+
## Non-Goals
|
|
35
|
+
|
|
36
|
+
- Hosting-specific CI/CD details.
|
|
37
|
+
- Full sync automation in this ADR.
|
|
38
|
+
- Governance for unrelated monorepo packages.
|
|
39
|
+
|
|
40
|
+
## Decision
|
|
41
|
+
|
|
42
|
+
### 1. Source of Truth
|
|
43
|
+
|
|
44
|
+
1. Public git-shard repository is the single canonical source for `headless`.
|
|
45
|
+
2. `packages/headless` in monorepo is mirror-only development workspace.
|
|
46
|
+
|
|
47
|
+
### 2. Release Ownership
|
|
48
|
+
|
|
49
|
+
1. `npm publish` runs only from git-shard.
|
|
50
|
+
2. Release tags (`vX.Y.Z`) are created only in git-shard.
|
|
51
|
+
3. Monorepo release of this package is forbidden.
|
|
52
|
+
|
|
53
|
+
### 3. Sync Model
|
|
54
|
+
|
|
55
|
+
- `monorepo -> shard`: allowed for feature work and integration-driven changes.
|
|
56
|
+
- `shard -> monorepo`: required after every release and architecture-level change.
|
|
57
|
+
|
|
58
|
+
MUST:
|
|
59
|
+
|
|
60
|
+
- keep public API equivalent between mirror and shard;
|
|
61
|
+
- sync ADR/spec updates in both directions;
|
|
62
|
+
- run boundary, lint, and test checks before sync.
|
|
63
|
+
|
|
64
|
+
### 4. Minimal Release Gate (in git-shard)
|
|
65
|
+
|
|
66
|
+
Before release:
|
|
67
|
+
|
|
68
|
+
1. run `lint` (types + oxlint + oxfmt + boundaries);
|
|
69
|
+
2. run package tests;
|
|
70
|
+
3. update changelog;
|
|
71
|
+
4. bump version according to SemVer;
|
|
72
|
+
5. create release tag and release notes.
|
|
73
|
+
|
|
74
|
+
## Alternatives
|
|
75
|
+
|
|
76
|
+
### A. Release directly from monorepo
|
|
77
|
+
|
|
78
|
+
**Rejected**:
|
|
79
|
+
|
|
80
|
+
- breaks independent package model;
|
|
81
|
+
- increases risk of leaking internal dependencies.
|
|
82
|
+
|
|
83
|
+
### B. Keep only git-shard and remove monorepo mirror
|
|
84
|
+
|
|
85
|
+
**Rejected**:
|
|
86
|
+
|
|
87
|
+
- hurts local integration workflow;
|
|
88
|
+
- increases collaboration friction with product development.
|
|
89
|
+
|
|
90
|
+
## Consequences
|
|
91
|
+
|
|
92
|
+
### Positive
|
|
93
|
+
|
|
94
|
+
- clear release ownership;
|
|
95
|
+
- reproducible publication flow;
|
|
96
|
+
- lower risk of accidental monorepo coupling.
|
|
97
|
+
|
|
98
|
+
### Negative
|
|
99
|
+
|
|
100
|
+
- ongoing sync overhead;
|
|
101
|
+
- additional operational governance for changelog, tagging, and release discipline.
|
|
102
|
+
|
|
103
|
+
## Change History
|
|
104
|
+
|
|
105
|
+
| Date | Change |
|
|
106
|
+
| ---------- | ------------------------------------------------------ |
|
|
107
|
+
| 2026-02-08 | Initial draft created (r1) |
|
|
108
|
+
| 2026-02-08 | Rewritten to full English and terminology cleanup (r2) |
|