Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: validate

on:
push:
branches: [main]
pull_request:

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- name: Schemas + examples (positive)
run: npm run validate
- name: Negative fixtures (must all be rejected)
run: npm run validate -- --fixtures negative
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Think of it as OpenAPI for design systems.

---

> **Status: v0.2 draft available.**
> **Status: v0.3 draft available.**
>
> The current draft is [`spec/dspack-v0.2.md`](./spec/dspack-v0.2.md), with a matching [JSON Schema](./schema/dspack.v0.2.schema.json) and a [shadcn/ui reference example](./examples/shadcn-ui.dspack.json). The [v0.1 spec](./spec/dspack-v0.1.md) and [schema](./schema/dspack.v0.1.schema.json) are preserved for reference. v0.2 is fully backward-compatible — a valid v0.1 document with `"dspack": "0.2"` validates against the v0.2 schema. This is a draft — breaking changes may occur before v1.0. Contributions to the design are welcome at any stage.
> The current draft is [`spec/dspack-v0.3.md`](./spec/dspack-v0.3.md) (written as a delta over [v0.2](./spec/dspack-v0.2.md)), with a matching [JSON Schema](./schema/dspack.v0.3.schema.json), a companion [surface schema](./schema/dspack.surface.v0_1.schema.json), and a [shadcn/ui reference example](./examples/shadcn-ui.dspack.json). v0.3 adds the machine-checkable **governance blocks** — `intents`, `rules`, `examples` — and is strictly additive: a valid v0.2 document with `"dspack": "0.3"` validates against the v0.3 schema (see the [migration guide](./spec/migration-v0.2-to-v0.3.md)). Earlier specs and schemas ([v0.2](./spec/dspack-v0.2.md), [v0.1](./spec/dspack-v0.1.md)) are preserved for reference. This is a draft — breaking changes may occur before v1.0. Contributions to the design are welcome at any stage.

---

Expand Down Expand Up @@ -120,6 +120,7 @@ The following milestones represent the current intended direction. They are not
| **v0.1 spec draft** | First complete draft of the dspack specification — _[available](./spec/dspack-v0.1.md)_ |
| **shadcn/ui example dspack** | A reference dspack file for the [shadcn/ui](https://ui.shadcn.com) component library — _[available](./examples/shadcn-ui.dspack.json)_ |
| **v0.2 spec draft** | Adds structured generation constraints: lifecycle status, accessibility, composition rules, contextual constraints, variant semantics, token hierarchy, themes, layout primitives, and anti-pattern severity — _[available](./spec/dspack-v0.2.md)_ |
| **v0.3 spec draft** | Adds the machine-checkable governance blocks: named intents, typed deterministic rules with rationales, compilable examples, and the companion dspack surface format — _[available](./spec/dspack-v0.3.md)_ |
| **ds-mcp v0 release** | First release of the reference implementation, validated against the v0.2 spec — _[available](https://github.com/aestheticfunction/ds-mcp)_ |
| **Community RFCs** | Open RFC process for proposing additions and changes to the spec |
| **v1.0 spec stabilization** | First stable, versioned release of the specification; breaking changes require a formal process after this point |
Expand Down
99 changes: 97 additions & 2 deletions examples/shadcn-ui.dspack.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "../schema/dspack.v0.2.schema.json",
"dspack": "0.2",
"$schema": "../schema/dspack.v0.3.schema.json",
"dspack": "0.3",
"name": "shadcn/ui",
"description": "A collection of reusable components built with Radix UI and Tailwind CSS. Components are copied into your project, not installed as a dependency.",
"version": "2.0.0",
Expand Down Expand Up @@ -875,6 +875,101 @@
"tags": ["accessibility", "semantics", "component-misuse"]
}
],
"intents": [
{
"id": "destructive-action",
"name": "Destructive action",
"description": "The requested UI performs an irreversible or high-consequence operation: deleting records or accounts, revoking access, removing members.",
"relatedPatterns": ["destructive-action-confirmation"]
}
],
"rules": [
{
"id": "rule.destructive-requires-alertdialog",
"type": "component-choice",
"severity": "must",
"appliesTo": { "intents": ["destructive-action"] },
"require": ["alert-dialog"],
"forbid": ["dialog"],
"rationale": "Dialog can be dismissed by clicking the overlay or pressing Escape, so a user can bypass a destructive confirmation without making a conscious choice. AlertDialog forces an explicit confirm/cancel decision and is announced with greater urgency by screen readers.",
"examples": ["ex.delete-account-confirmation"],
"tags": ["safety", "accessibility"]
},
{
"id": "rule.alertdialog-requires-cancel",
"type": "required-composition",
"severity": "must",
"component": "alert-dialog",
"requiredSubComponents": [
{ "id": "alert-dialog-cancel", "min": 1 },
{ "id": "alert-dialog-title", "min": 1 }
],
"rationale": "A confirmation without an explicit cancel action and a title naming the consequence funnels the user toward the destructive action; the title is also required for aria-labelledby.",
"examples": ["ex.delete-account-confirmation"],
"tags": ["safety", "accessibility"]
},
{
"id": "rule.button-no-interactive-descendants",
"type": "forbidden-composition",
"severity": "must",
"component": "button",
"forbiddenDescendants": ["button", "input"],
"rationale": "Nested interactive elements create ambiguous click targets and are an accessibility violation: screen readers cannot determine intent and click handling varies across browsers.",
"examples": ["ex.delete-account-confirmation"],
"tags": ["accessibility", "interaction"]
}
],
"examples": [
{
"id": "ex.delete-account-confirmation",
"intent": "destructive-action",
"name": "Delete account confirmation",
"prompt": "a screen to delete my account",
"description": "Card with a destructive entry point; AlertDialog confirmation with explicit consequence text, cancel-before-confirm, destructive confirm variant.",
"surface": {
"dspackSurface": "0.1",
"system": "shadcn/ui",
"intent": "destructive-action",
"root": {
"component": "card",
"children": [
{
"component": "alert-dialog",
"children": [
{
"component": "alert-dialog-trigger",
"children": [
{
"component": "button",
"props": { "variant": "destructive" },
"text": "Delete account"
}
]
},
{
"component": "alert-dialog-content",
"children": [
{ "component": "alert-dialog-title", "text": "Delete your account?" },
{
"component": "alert-dialog-description",
"text": "This will permanently delete your account and all associated data. This action cannot be undone."
},
{
"component": "alert-dialog-footer",
"children": [
{ "component": "alert-dialog-cancel", "text": "Cancel" },
{ "component": "alert-dialog-action", "text": "Delete account" }
]
}
]
}
]
}
]
}
}
}
],
"frameworkBindings": {
"react": {
"name": "React",
Expand Down
37 changes: 37 additions & 0 deletions fixtures/negative/duplicate-subcomponent-id.dspack.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"_comment": "CONSISTENCY: 'shared-part' is declared as a sub-component by two different components; ambiguous vocabulary must be rejected, not resolved by iteration order.",
"dspack": "0.3",
"name": "negative-fixture",
"components": {
"widget-a": {
"name": "WidgetA",
"description": "First parent.",
"composition": {
"subComponents": [
{
"id": "shared-part",
"name": "SharedPart"
}
]
}
},
"widget-b": {
"name": "WidgetB",
"description": "Second parent.",
"composition": {
"subComponents": [
{
"id": "shared-part",
"name": "SharedPart"
}
]
}
}
},
"intents": [
{
"id": "destructive-action",
"description": "Irreversible operations."
}
]
}
56 changes: 56 additions & 0 deletions fixtures/negative/example-missing-intent.dspack.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"dspack": "0.3",
"name": "negative-fixture",
"components": {
"button": {
"name": "Button",
"description": "A button.",
"props": {
"variant": {
"type": "enum",
"values": [
"default",
"destructive"
]
}
}
},
"alert-dialog": {
"name": "AlertDialog",
"description": "A confirmation dialog.",
"composition": {
"subComponents": [
{
"id": "alert-dialog-title",
"name": "AlertDialogTitle"
},
{
"id": "alert-dialog-cancel",
"name": "AlertDialogCancel"
}
]
}
}
},
"intents": [
{
"id": "destructive-action",
"description": "Irreversible operations."
}
],
"_comment": "SCHEMA: example missing the mandatory intent.",
"examples": [
{
"id": "ex.fixture",
"surface": {
"dspackSurface": "0.1",
"system": "negative-fixture",
"intent": "destructive-action",
"root": {
"component": "button",
"text": "Go"
}
}
}
]
}
60 changes: 60 additions & 0 deletions fixtures/negative/example-surface-bad-enum.dspack.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"dspack": "0.3",
"name": "negative-fixture",
"components": {
"button": {
"name": "Button",
"description": "A button.",
"props": {
"variant": {
"type": "enum",
"values": [
"default",
"destructive"
]
}
}
},
"alert-dialog": {
"name": "AlertDialog",
"description": "A confirmation dialog.",
"composition": {
"subComponents": [
{
"id": "alert-dialog-title",
"name": "AlertDialogTitle"
},
{
"id": "alert-dialog-cancel",
"name": "AlertDialogCancel"
}
]
}
}
},
"intents": [
{
"id": "destructive-action",
"description": "Irreversible operations."
}
],
"_comment": "S2: button variant 'danger' is not an allowed enum value (default | destructive).",
"examples": [
{
"id": "ex.fixture",
"surface": {
"dspackSurface": "0.1",
"system": "negative-fixture",
"intent": "destructive-action",
"root": {
"component": "button",
"props": {
"variant": "danger"
},
"text": "Delete"
}
},
"intent": "destructive-action"
}
]
}
56 changes: 56 additions & 0 deletions fixtures/negative/example-surface-invalid.dspack.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"dspack": "0.3",
"name": "negative-fixture",
"components": {
"button": {
"name": "Button",
"description": "A button.",
"props": {
"variant": {
"type": "enum",
"values": [
"default",
"destructive"
]
}
}
},
"alert-dialog": {
"name": "AlertDialog",
"description": "A confirmation dialog.",
"composition": {
"subComponents": [
{
"id": "alert-dialog-title",
"name": "AlertDialogTitle"
},
{
"id": "alert-dialog-cancel",
"name": "AlertDialogCancel"
}
]
}
}
},
"intents": [
{
"id": "destructive-action",
"description": "Irreversible operations."
}
],
"_comment": "S1: example surface root node lacks the required 'component' field.",
"examples": [
{
"id": "ex.fixture",
"surface": {
"dspackSurface": "0.1",
"system": "negative-fixture",
"intent": "destructive-action",
"root": {
"text": "orphan text node"
}
},
"intent": "destructive-action"
}
]
}
Loading
Loading