create-backbone-template 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.
Files changed (182) hide show
  1. package/README.md +33 -0
  2. package/bin/create-backbone-template.js +5 -0
  3. package/package.json +30 -0
  4. package/src/create-backbone-template.js +204 -0
  5. package/template/.agents/skills/agent-browser/SKILL.md +55 -0
  6. package/template/.agents/skills/create-plan/SKILL.md +52 -0
  7. package/template/.agents/skills/create-plan/agents/openai.yaml +4 -0
  8. package/template/.agents/skills/create-pr-presentation/SKILL.md +86 -0
  9. package/template/.agents/skills/create-pr-presentation/agents/openai.yaml +4 -0
  10. package/template/.agents/skills/implement-plan/SKILL.md +26 -0
  11. package/template/.agents/skills/implement-plan/agents/openai.yaml +4 -0
  12. package/template/.agents/skills/review-plan/SKILL.md +38 -0
  13. package/template/.agents/skills/review-plan/agents/openai.yaml +4 -0
  14. package/template/.env.schema +30 -0
  15. package/template/.env.test +6 -0
  16. package/template/.oxlintrc.json +67 -0
  17. package/template/.vscode/extensions.json +3 -0
  18. package/template/.vscode/settings.json +23 -0
  19. package/template/AGENTS.md +55 -0
  20. package/template/Cargo.lock +2648 -0
  21. package/template/Cargo.toml +29 -0
  22. package/template/Justfile +140 -0
  23. package/template/README.md +72 -0
  24. package/template/TODO.md +1 -0
  25. package/template/_gitignore +12 -0
  26. package/template/buf.gen.yaml +7 -0
  27. package/template/buf.yaml +10 -0
  28. package/template/client/.oxfmtrc.json +8 -0
  29. package/template/client/.oxlintrc.json +57 -0
  30. package/template/client/README.md +19 -0
  31. package/template/client/_gitignore +5 -0
  32. package/template/client/index.html +12 -0
  33. package/template/client/package.json +47 -0
  34. package/template/client/packages/design-system/package.json +19 -0
  35. package/template/client/packages/design-system/src/index.ts +2 -0
  36. package/template/client/packages/design-system-basic/package.json +18 -0
  37. package/template/client/packages/design-system-basic/src/button.stories.tsx +50 -0
  38. package/template/client/packages/design-system-basic/src/button.tsx +26 -0
  39. package/template/client/packages/design-system-basic/src/empty-state.stories.tsx +18 -0
  40. package/template/client/packages/design-system-basic/src/empty-state.tsx +17 -0
  41. package/template/client/packages/design-system-basic/src/form-field.stories.tsx +15 -0
  42. package/template/client/packages/design-system-basic/src/form-field.tsx +10 -0
  43. package/template/client/packages/design-system-basic/src/form.stories.tsx +27 -0
  44. package/template/client/packages/design-system-basic/src/form.tsx +9 -0
  45. package/template/client/packages/design-system-basic/src/heading.stories.tsx +14 -0
  46. package/template/client/packages/design-system-basic/src/heading.tsx +25 -0
  47. package/template/client/packages/design-system-basic/src/index.tsx +15 -0
  48. package/template/client/packages/design-system-basic/src/inline.stories.tsx +13 -0
  49. package/template/client/packages/design-system-basic/src/inline.tsx +5 -0
  50. package/template/client/packages/design-system-basic/src/layout.stories.tsx +24 -0
  51. package/template/client/packages/design-system-basic/src/layout.tsx +14 -0
  52. package/template/client/packages/design-system-basic/src/loader.stories.tsx +8 -0
  53. package/template/client/packages/design-system-basic/src/loader.tsx +11 -0
  54. package/template/client/packages/design-system-basic/src/navigation.stories.tsx +16 -0
  55. package/template/client/packages/design-system-basic/src/navigation.tsx +18 -0
  56. package/template/client/packages/design-system-basic/src/notice.stories.tsx +13 -0
  57. package/template/client/packages/design-system-basic/src/notice.tsx +5 -0
  58. package/template/client/packages/design-system-basic/src/stack.stories.tsx +17 -0
  59. package/template/client/packages/design-system-basic/src/stack.tsx +5 -0
  60. package/template/client/packages/design-system-basic/src/styles.css +254 -0
  61. package/template/client/packages/design-system-basic/src/text-input.stories.tsx +13 -0
  62. package/template/client/packages/design-system-basic/src/text-input.tsx +5 -0
  63. package/template/client/packages/design-system-basic/src/text.stories.tsx +21 -0
  64. package/template/client/packages/design-system-basic/src/text.tsx +5 -0
  65. package/template/client/packages/design-system-contract/package.json +15 -0
  66. package/template/client/packages/design-system-contract/src/button.ts +10 -0
  67. package/template/client/packages/design-system-contract/src/empty-state.ts +9 -0
  68. package/template/client/packages/design-system-contract/src/form-field.ts +9 -0
  69. package/template/client/packages/design-system-contract/src/form.ts +9 -0
  70. package/template/client/packages/design-system-contract/src/heading.ts +9 -0
  71. package/template/client/packages/design-system-contract/src/index.ts +13 -0
  72. package/template/client/packages/design-system-contract/src/inline.ts +7 -0
  73. package/template/client/packages/design-system-contract/src/layout.ts +8 -0
  74. package/template/client/packages/design-system-contract/src/loader.ts +7 -0
  75. package/template/client/packages/design-system-contract/src/navigation.ts +13 -0
  76. package/template/client/packages/design-system-contract/src/notice.ts +8 -0
  77. package/template/client/packages/design-system-contract/src/stack.ts +8 -0
  78. package/template/client/packages/design-system-contract/src/text-input.ts +5 -0
  79. package/template/client/packages/design-system-contract/src/text.ts +9 -0
  80. package/template/client/packages/design-system-lint/fixtures/invalid/external-ui-import.tsx +5 -0
  81. package/template/client/packages/design-system-lint/fixtures/invalid/raw-dom-jsx.tsx +3 -0
  82. package/template/client/packages/design-system-lint/fixtures/invalid/two-violations.tsx +7 -0
  83. package/template/client/packages/design-system-lint/fixtures/valid/design-system-only.tsx +13 -0
  84. package/template/client/packages/design-system-lint/package.json +23 -0
  85. package/template/client/packages/design-system-lint/src/check-design-system-architecture.ts +22 -0
  86. package/template/client/packages/design-system-lint/src/design-system-architecture.ts +286 -0
  87. package/template/client/packages/design-system-lint/src/oxlint-plugin.ts +11 -0
  88. package/template/client/packages/design-system-lint/src/page-architecture.ts +382 -0
  89. package/template/client/packages/design-system-lint/src/rules.ts +111 -0
  90. package/template/client/packages/design-system-lint/test/design-system-architecture.test.ts +243 -0
  91. package/template/client/packages/design-system-lint/test/oxlint-fixtures.test.ts +159 -0
  92. package/template/client/packages/design-system-lint/test/page-architecture.test.ts +175 -0
  93. package/template/client/packages/design-system-lint/test/rules.test.ts +65 -0
  94. package/template/client/packages/design-system-lint/tsconfig.json +29 -0
  95. package/template/client/src/App.tsx +77 -0
  96. package/template/client/src/design-system-components.test.tsx +75 -0
  97. package/template/client/src/gen/helloworld/v1/helloworld_pb.ts +63 -0
  98. package/template/client/src/main.tsx +18 -0
  99. package/template/client/src/pages/hello/hello-page.stories.tsx +20 -0
  100. package/template/client/src/pages/hello/hello-page.test.tsx +90 -0
  101. package/template/client/src/pages/hello/hello-page.tsx +126 -0
  102. package/template/client/src/pages/page.ts +20 -0
  103. package/template/client/src/testing/create-preview-events.test.ts +36 -0
  104. package/template/client/src/testing/create-preview-events.ts +30 -0
  105. package/template/client/src/vite-env.d.ts +1 -0
  106. package/template/client/tsconfig.json +32 -0
  107. package/template/client/vite.config.ts +21 -0
  108. package/template/client/vite.ladle.config.ts +5 -0
  109. package/template/e2e/.gherkin-lintrc +20 -0
  110. package/template/e2e/.oxfmtrc.json +15 -0
  111. package/template/e2e/.oxlintrc.json +37 -0
  112. package/template/e2e/_gitignore +4 -0
  113. package/template/e2e/features/helloworld.feature +10 -0
  114. package/template/e2e/package.json +42 -0
  115. package/template/e2e/playwright.config.ts +16 -0
  116. package/template/e2e/support/app-gherkin.ts +4 -0
  117. package/template/e2e/support/fixtures.ts +236 -0
  118. package/template/e2e/support/gherkin-fixtures/duplicate-id.feature +9 -0
  119. package/template/e2e/support/gherkin-fixtures/duplicate-id.spec.ts +7 -0
  120. package/template/e2e/support/gherkin-fixtures/extra-implementation.spec.ts +7 -0
  121. package/template/e2e/support/gherkin-fixtures/extra-step.spec.ts +10 -0
  122. package/template/e2e/support/gherkin-fixtures/happy-path.spec.ts +4 -0
  123. package/template/e2e/support/gherkin-fixtures/missing-id.feature +4 -0
  124. package/template/e2e/support/gherkin-fixtures/missing-id.spec.ts +7 -0
  125. package/template/e2e/support/gherkin-fixtures/missing-implementation.spec.ts +7 -0
  126. package/template/e2e/support/gherkin-fixtures/missing-step.spec.ts +7 -0
  127. package/template/e2e/support/gherkin-fixtures/playwright.config.ts +7 -0
  128. package/template/e2e/support/gherkin-fixtures/scenario-outline.feature +9 -0
  129. package/template/e2e/support/gherkin-fixtures/scenario-outline.spec.ts +7 -0
  130. package/template/e2e/support/gherkin-fixtures/step-mismatch.spec.ts +9 -0
  131. package/template/e2e/support/gherkin-fixtures/valid-implementations.ts +23 -0
  132. package/template/e2e/support/gherkin-fixtures/valid-scenarios.feature +26 -0
  133. package/template/e2e/support/gherkin.test.ts +184 -0
  134. package/template/e2e/support/gherkin.ts +321 -0
  135. package/template/e2e/support/oxlint-plugin.test.ts +328 -0
  136. package/template/e2e/support/oxlint-plugin.ts +485 -0
  137. package/template/e2e/tests/helloworld.spec.ts +39 -0
  138. package/template/e2e/tsconfig.json +26 -0
  139. package/template/e2e/tsconfig.oxlint-plugin.json +12 -0
  140. package/template/package.json +9 -0
  141. package/template/pnpm-lock.yaml +10723 -0
  142. package/template/pnpm-workspace.yaml +8 -0
  143. package/template/pr-slide/README.md +95 -0
  144. package/template/pr-slide/package.json +23 -0
  145. package/template/pr-slide/src/cli.js +262 -0
  146. package/template/pr-slide/src/generate-pr-deck.js +833 -0
  147. package/template/pr-slide/src/git-context.js +91 -0
  148. package/template/pr-slide/src/presentation-paths.js +9 -0
  149. package/template/pr-slide/src/presentations.js +53 -0
  150. package/template/pr-slide/test/generate-pr-deck.test.js +118 -0
  151. package/template/pr-slide/test/presentation-paths.test.js +14 -0
  152. package/template/pr-slide/test/presentations.test.js +50 -0
  153. package/template/proto/helloworld/v1/helloworld.proto +15 -0
  154. package/template/scripts/run-e2e.sh +10 -0
  155. package/template/server/Cargo.toml +26 -0
  156. package/template/server/build.rs +9 -0
  157. package/template/server/dylint/backbone_server_lints/.cargo/config.toml +6 -0
  158. package/template/server/dylint/backbone_server_lints/Cargo.lock +1581 -0
  159. package/template/server/dylint/backbone_server_lints/Cargo.toml +21 -0
  160. package/template/server/dylint/backbone_server_lints/README.md +5 -0
  161. package/template/server/dylint/backbone_server_lints/_gitignore +1 -0
  162. package/template/server/dylint/backbone_server_lints/rust-toolchain +3 -0
  163. package/template/server/dylint/backbone_server_lints/src/lib.rs +612 -0
  164. package/template/server/dylint/backbone_server_lints/ui/lib.rs +4 -0
  165. package/template/server/dylint/backbone_server_lints/ui/lib.stderr +10 -0
  166. package/template/server/dylint/backbone_server_lints/ui/long_file.rs +303 -0
  167. package/template/server/dylint/backbone_server_lints/ui/long_file.stderr +6 -0
  168. package/template/server/dylint/backbone_server_lints/ui/main.rs +59 -0
  169. package/template/server/dylint/backbone_server_lints/ui/main.stderr +85 -0
  170. package/template/server/migrations/20260520120000_create_projects.sql +12 -0
  171. package/template/server/migrations/20260524160000_create_hello_world_inputs.sql +12 -0
  172. package/template/server/src/config.rs +27 -0
  173. package/template/server/src/db/hello_world.rs +34 -0
  174. package/template/server/src/db/hello_world_tests.rs +11 -0
  175. package/template/server/src/db/mod.rs +39 -0
  176. package/template/server/src/lib.rs +10 -0
  177. package/template/server/src/main.rs +43 -0
  178. package/template/server/src/rpc/greeter/mod.rs +31 -0
  179. package/template/server/src/rpc/greeter/say_hello.rs +27 -0
  180. package/template/server/src/rpc/mod.rs +8 -0
  181. package/template/server/src/state.rs +13 -0
  182. package/template/skills-lock.json +11 -0
@@ -0,0 +1,29 @@
1
+ [workspace]
2
+ members = ["server"]
3
+ resolver = "3"
4
+
5
+ [workspace.package]
6
+ edition = "2024"
7
+ rust-version = "1.88"
8
+ license = "MIT"
9
+
10
+ [workspace.metadata.dylint]
11
+ libraries = [{ path = "server/dylint/backbone_server_lints" }]
12
+
13
+ [workspace.lints.clippy]
14
+ correctness = { level = "deny", priority = -1 }
15
+ dbg_macro = "deny"
16
+ exit = "deny"
17
+ expect_used = "deny"
18
+ mem_forget = "deny"
19
+ missing_errors_doc = "allow"
20
+ panic = "deny"
21
+ pedantic = { level = "warn", priority = -1 }
22
+ perf = { level = "deny", priority = -1 }
23
+ print_stderr = "deny"
24
+ print_stdout = "deny"
25
+ suspicious = { level = "deny", priority = -1 }
26
+ todo = "deny"
27
+ undocumented_unsafe_blocks = "deny"
28
+ unimplemented = "deny"
29
+ unwrap_used = "deny"
@@ -0,0 +1,140 @@
1
+ set shell := ["bash", "-c"]
2
+ set tempdir := "/tmp"
3
+ varlock := "pnpm exec varlock run --no-inject-graph --"
4
+
5
+ default:
6
+ @just --list
7
+
8
+ setup:
9
+ pnpm install
10
+ pnpm --filter backbone-e2e exec playwright install chromium
11
+ cargo fetch
12
+
13
+ generate:
14
+ pnpm --filter backbone-client exec buf generate --template ../buf.gen.yaml --output .. ../proto
15
+
16
+ proto-lint:
17
+ pnpm --filter backbone-client exec buf lint ../proto
18
+
19
+ server:
20
+ {{varlock}} cargo run -p server
21
+
22
+ client:
23
+ {{varlock}} pnpm --filter backbone-client run dev
24
+
25
+ ladle:
26
+ {{varlock}} pnpm --filter backbone-client run ladle
27
+
28
+ pr-slide-generate:
29
+ pnpm --filter @backbone/pr-slide run generate
30
+
31
+ pr-slide:
32
+ pnpm --filter @backbone/pr-slide run dev
33
+
34
+ pr-slide-list:
35
+ pnpm --filter @backbone/pr-slide run list
36
+
37
+ pr-slide-open name *args:
38
+ pnpm --filter @backbone/pr-slide run open -- {{name}} {{args}}
39
+
40
+ client-test:
41
+ pnpm --filter backbone-client test
42
+
43
+ rust-test:
44
+ cargo test --workspace
45
+
46
+ rust-clippy:
47
+ cargo clippy --workspace --all-targets
48
+
49
+ rust-lint:
50
+ cargo dylint --all -- --all-targets
51
+
52
+ dev: generate
53
+ #!/usr/bin/env bash
54
+ set -euo pipefail
55
+
56
+ {{varlock}} cargo run -p server &
57
+ server_pid=$!
58
+
59
+ {{varlock}} pnpm --filter backbone-client run dev &
60
+ client_pid=$!
61
+
62
+ cleanup() {
63
+ kill "$server_pid" "$client_pid" 2>/dev/null || true
64
+ wait "$server_pid" "$client_pid" 2>/dev/null || true
65
+ }
66
+
67
+ trap cleanup EXIT INT TERM
68
+ wait -n "$server_pid" "$client_pid"
69
+
70
+ check:
71
+ just proto-lint
72
+ just generate
73
+ just rust-validation-checks
74
+ just client-validation
75
+ pnpm --filter backbone-e2e run lint
76
+
77
+ rust-validation-checks:
78
+ cargo check --workspace
79
+ just rust-clippy
80
+ just rust-lint
81
+
82
+ client-validation:
83
+ just client-test
84
+ pnpm --filter backbone-client run check
85
+ pnpm --filter backbone-client run build
86
+
87
+ full-validation:
88
+ #!/usr/bin/env bash
89
+ set -euo pipefail
90
+
91
+ just proto-lint
92
+ just generate
93
+
94
+ just rust-validation-checks &
95
+ rust_checks_pid=$!
96
+ just rust-test &
97
+ rust_test_pid=$!
98
+ pnpm --filter @backbone/design-system-lint build
99
+ pnpm --filter backbone-client run test:prepared &
100
+ client_test_pid=$!
101
+ pnpm --filter backbone-client run check:prepared &
102
+ client_check_pid=$!
103
+ pnpm --filter backbone-client run build &
104
+ client_build_pid=$!
105
+ pnpm --filter backbone-e2e run build:oxlint-plugin
106
+ pnpm --filter backbone-e2e run lint:prepared &
107
+ e2e_lint_pid=$!
108
+ pnpm --filter backbone-e2e run test:support:prepared &
109
+ e2e_support_pid=$!
110
+
111
+ status=0
112
+ for pid in "$rust_checks_pid" "$rust_test_pid" "$client_test_pid" "$client_check_pid" "$client_build_pid" "$e2e_lint_pid" "$e2e_support_pid"; do
113
+ if ! wait "$pid"; then
114
+ status=1
115
+ fi
116
+ done
117
+
118
+ if [[ "$status" -ne 0 ]]; then
119
+ exit "$status"
120
+ fi
121
+
122
+ BACKBONE_SKIP_GENERATE=1 just e2e-browser
123
+
124
+ env-check:
125
+ pnpm exec varlock load
126
+
127
+ e2e:
128
+ bash scripts/run-e2e.sh pnpm --filter backbone-e2e test
129
+
130
+ e2e-prepared:
131
+ bash scripts/run-e2e.sh pnpm --filter backbone-e2e run test:prepared
132
+
133
+ e2e-browser:
134
+ bash scripts/run-e2e.sh pnpm --filter backbone-e2e run test:browser
135
+
136
+ e2e-debug:
137
+ bash scripts/run-e2e.sh pnpm --filter backbone-e2e run test:debug
138
+
139
+ e2e-ui:
140
+ bash scripts/run-e2e.sh pnpm --filter backbone-e2e run test:ui
@@ -0,0 +1,72 @@
1
+ # Backbone
2
+
3
+ React + Rust ConnectRPC starter.
4
+
5
+ ## Layout
6
+
7
+ - `client/` - React app built with Vite, pnpm workspaces, Oxlint, and Oxfmt
8
+ - `server/` - Rust ConnectRPC server
9
+ - `proto/` - shared protobuf definitions
10
+ - `e2e/` - Playwright browser tests
11
+
12
+ ## Client Packages
13
+
14
+ The client is a pnpm workspace rooted at `client/`.
15
+
16
+ - `backbone-client` (`client/`) - Vite React application package. It owns the app entry point, generated ConnectRPC TypeScript bindings, client-side scripts, and dependencies on the design system and ConnectRPC web client.
17
+ - `@backbone/design-system` (`client/packages/design-system/`) - public design-system boundary for app code. It re-exports the active component implementation from `@backbone/design-system-basic` and the shared component types from `@backbone/design-system-contract`.
18
+ - `@backbone/design-system-contract` (`client/packages/design-system-contract/`) - type-only contract for supported UI primitives, including layout, stack, inline, text, headings, form fields, inputs, buttons, and notices.
19
+ - `@backbone/design-system-basic` (`client/packages/design-system-basic/`) - concrete React and CSS implementation of the design-system contract. It contains the DOM markup and styling used by the app.
20
+ - `@backbone/design-system-lint` (`client/packages/design-system-lint/`) - custom Oxlint plugin that protects the design-system boundary by rejecting raw DOM JSX in app code and direct imports from external UI libraries.
21
+
22
+ ## Rust Custom Lints
23
+
24
+ The server has custom Dylint rules in `server/dylint/backbone_server_lints/`.
25
+ They protect server architecture boundaries, such as keeping sqlx usage in
26
+ `server/src/db/`, environment reads in config code, and RPC method
27
+ implementations split into per-method files.
28
+
29
+ The Dylint crate intentionally contains its own empty `[workspace]` table. That
30
+ is the small Cargo hack that lets the lint crate live under `server/` while
31
+ staying out of the root Cargo workspace's normal `cargo check` and `cargo test`
32
+ paths. Dylint still builds it explicitly through the root
33
+ `[workspace.metadata.dylint]` entry, but regular server compilation does not
34
+ treat the lint implementation as application code.
35
+
36
+ ## Commands
37
+
38
+ ```sh
39
+ just setup # install JS deps, Playwright Chromium, and fetch cargo deps
40
+ just generate # generate React protobuf definitions from proto/
41
+ just check # generate, cargo check, client check, vite build
42
+ just rust-test # run Rust tests
43
+ just full-validation # run check, Rust tests, and e2e tests
44
+ just dev # run the React client and Rust server together
45
+ just ladle # run the client design-system Ladle
46
+ just e2e # start client/server and test through the UI
47
+ just e2e-debug # run e2e headed/debug, stopping after the first failure
48
+ just e2e-ui # open Playwright UI mode
49
+ ```
50
+
51
+ The dev launcher starts the client at `http://127.0.0.1:5173` and the server at
52
+ `http://127.0.0.1:8080`, then stops both child processes when the launcher exits.
53
+
54
+ ## Runtime Configuration
55
+
56
+ The starter keeps SQLite wired in so new features have a real persistence path
57
+ from day one. The hello-world RPC records each submitted name in
58
+ `hello_world_inputs`.
59
+
60
+ Required server environment variables:
61
+
62
+ ```sh
63
+ DATABASE_URL=sqlite://backbone.sqlite?mode=rwc
64
+ SERVER_HOST=127.0.0.1
65
+ SERVER_PORT=8080
66
+ ```
67
+
68
+ Required client environment variables:
69
+
70
+ ```sh
71
+ VITE_SERVER_URL=http://127.0.0.1:8080
72
+ ```
@@ -0,0 +1 @@
1
+ - Add a linter for unused design system components ?
@@ -0,0 +1,12 @@
1
+ target
2
+ node_modules
3
+ client/dist
4
+ client/node_modules
5
+ e2e/node_modules
6
+ e2e/playwright-report
7
+ e2e/test-results
8
+ e2e/support/dist
9
+ .agents/pr-presentation/**/dist
10
+ pr-slide/dist
11
+ *.local
12
+ backbone.sqlite
@@ -0,0 +1,7 @@
1
+ version: v2
2
+ clean: true
3
+ plugins:
4
+ - local: protoc-gen-es
5
+ out: client/src/gen
6
+ opt:
7
+ - target=ts
@@ -0,0 +1,10 @@
1
+ version: v2
2
+ modules:
3
+ - path: proto
4
+ lint:
5
+ use:
6
+ - STANDARD
7
+ breaking:
8
+ use:
9
+ - FILE
10
+
@@ -0,0 +1,8 @@
1
+ {
2
+ "ignorePatterns": ["dist/**", "node_modules/**", "src/gen/**"],
3
+ "printWidth": 100,
4
+ "semi": false,
5
+ "singleQuote": false,
6
+ "tabWidth": 2,
7
+ "trailingComma": "all"
8
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": [
4
+ "eslint",
5
+ "typescript",
6
+ "unicorn",
7
+ "oxc",
8
+ "import",
9
+ "react",
10
+ "react-perf",
11
+ "jsx-a11y",
12
+ "promise",
13
+ "vitest"
14
+ ],
15
+ "jsPlugins": ["./packages/design-system-lint/dist/src/oxlint-plugin.js"],
16
+ "categories": {
17
+ "correctness": "deny",
18
+ "suspicious": "deny",
19
+ "perf": "deny",
20
+ "pedantic": "off",
21
+ "style": "off",
22
+ "restriction": "off",
23
+ "nursery": "off"
24
+ },
25
+ "options": {
26
+ "denyWarnings": true,
27
+ "reportUnusedDisableDirectives": "deny",
28
+ "typeAware": true
29
+ },
30
+ "ignorePatterns": ["src/gen/**", "packages/design-system-lint/fixtures/**"],
31
+ "rules": {
32
+ "import/no-unassigned-import": "off",
33
+ "react/react-in-jsx-scope": "off",
34
+ "react-perf/jsx-no-jsx-as-prop": "off",
35
+ "react-perf/jsx-no-new-array-as-prop": "off",
36
+ "react-perf/jsx-no-new-function-as-prop": "off",
37
+ "react-perf/jsx-no-new-object-as-prop": "off",
38
+ "typescript/no-unsafe-type-assertion": "off",
39
+ "typescript/unbound-method": "off",
40
+ "unicorn/no-array-sort": "off"
41
+ },
42
+ "overrides": [
43
+ {
44
+ "files": ["**/*.test.{ts,tsx}"],
45
+ "rules": {
46
+ "typescript/no-floating-promises": "off"
47
+ }
48
+ },
49
+ {
50
+ "files": ["src/**/*.{jsx,tsx}"],
51
+ "rules": {
52
+ "backbone-design-system/no-external-ui-imports": "error",
53
+ "backbone-design-system/no-raw-dom-jsx": "error"
54
+ }
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,19 @@
1
+ # Client
2
+
3
+ React client application built with Vite and TypeScript, using pnpm workspaces,
4
+ Oxlint, and Oxfmt.
5
+
6
+ ## Design system
7
+
8
+ The client UI is composed through `@backbone/design-system`, which re-exports a concrete implementation while preserving the abstract component contract from `@backbone/design-system-contract`. App code imports layout, text, form, and control primitives from that boundary instead of rendering DOM elements directly. The actual HTML and styling live inside the design-system implementation package, so the app stays decoupled from markup and visual details. A custom Oxlint plugin enforces this by rejecting raw DOM JSX and direct imports from external UI libraries outside the design-system boundary.
9
+
10
+ ## Scripts
11
+
12
+ ```sh
13
+ pnpm install
14
+ pnpm run dev
15
+ pnpm run typecheck
16
+ pnpm run lint
17
+ pnpm run format
18
+ pnpm run check
19
+ ```
@@ -0,0 +1,5 @@
1
+ dist
2
+ build
3
+ node_modules
4
+ *.local
5
+ packages/design-system-lint/dist
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Backbone Client</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "backbone-client",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "ladle": "ladle serve --stories \"{packages/design-system-basic/src,src}/**/*.stories.{ts,tsx}\" --viteConfig ./vite.ladle.config.ts",
9
+ "ladle:build": "ladle build --stories \"{packages/design-system-basic/src,src}/**/*.stories.{ts,tsx}\" --viteConfig ./vite.ladle.config.ts",
10
+ "build": "vite build",
11
+ "preview": "vite preview",
12
+ "typecheck": "tsc --noEmit",
13
+ "lint": "pnpm --filter @backbone/design-system-lint build && pnpm --filter @backbone/design-system-lint run check:architecture && oxlint .",
14
+ "lint:prepared": "pnpm --filter @backbone/design-system-lint run check:architecture && oxlint .",
15
+ "test": "pnpm --filter @backbone/design-system-lint test && vitest run src packages/design-system-basic/src",
16
+ "test:prepared": "pnpm --filter @backbone/design-system-lint run test:prepared && vitest run src packages/design-system-basic/src",
17
+ "format": "oxfmt .",
18
+ "format:check": "oxfmt --check .",
19
+ "check": "pnpm run format:check && pnpm run lint && pnpm run typecheck",
20
+ "check:prepared": "pnpm run format:check && pnpm run lint:prepared && pnpm run typecheck"
21
+ },
22
+ "dependencies": {
23
+ "@backbone/design-system": "workspace:*",
24
+ "@bufbuild/protobuf": "latest",
25
+ "@connectrpc/connect": "latest",
26
+ "@connectrpc/connect-web": "latest",
27
+ "react": "latest",
28
+ "react-dom": "latest",
29
+ "react-router": "^7.15.0"
30
+ },
31
+ "devDependencies": {
32
+ "@backbone/design-system-lint": "workspace:*",
33
+ "@bufbuild/buf": "latest",
34
+ "@bufbuild/protoc-gen-es": "latest",
35
+ "@ladle/react": "^5.1.1",
36
+ "@types/react": "^19.2.14",
37
+ "@types/react-dom": "^19.2.3",
38
+ "@vitejs/plugin-react": "latest",
39
+ "oxfmt": "^0.51.0",
40
+ "oxlint": "^1.66.0",
41
+ "oxlint-tsgolint": "^0.22.1",
42
+ "typescript": "^6.0.3",
43
+ "vite": "latest",
44
+ "vitest": "^4.0.14"
45
+ },
46
+ "packageManager": "pnpm@10.22.0"
47
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@backbone/design-system",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./src/index.ts",
9
+ "default": "./src/index.ts"
10
+ }
11
+ },
12
+ "dependencies": {
13
+ "@backbone/design-system-basic": "workspace:*",
14
+ "@backbone/design-system-contract": "workspace:*"
15
+ },
16
+ "peerDependencies": {
17
+ "react": "^19.0.0"
18
+ }
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from "@backbone/design-system-basic"
2
+ export type * from "@backbone/design-system-contract"
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@backbone/design-system-basic",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./src/index.tsx",
9
+ "default": "./src/index.tsx"
10
+ }
11
+ },
12
+ "dependencies": {
13
+ "@backbone/design-system-contract": "workspace:*"
14
+ },
15
+ "peerDependencies": {
16
+ "react": "^19.0.0"
17
+ }
18
+ }
@@ -0,0 +1,50 @@
1
+ import type { StoryDefault } from "@ladle/react"
2
+ import { Button, Stack } from "."
3
+
4
+ export default {
5
+ title: "Design System / Button",
6
+ } satisfies StoryDefault
7
+
8
+ export const Primary = () => <Button>Say hello</Button>
9
+
10
+ export const Secondary = () => <Button variant="secondary">View history</Button>
11
+
12
+ export const Danger = () => <Button variant="danger">Clear history</Button>
13
+
14
+ export const DisabledPrimary = () => <Button disabled>Say hello</Button>
15
+
16
+ export const DisabledSecondary = () => (
17
+ <Button disabled variant="secondary">
18
+ View history
19
+ </Button>
20
+ )
21
+
22
+ export const DisabledDanger = () => (
23
+ <Button disabled variant="danger">
24
+ Clear history
25
+ </Button>
26
+ )
27
+
28
+ export const LoadingPrimary = () => <Button loading>Saving...</Button>
29
+
30
+ export const LoadingSecondary = () => (
31
+ <Button loading variant="secondary">
32
+ Opening...
33
+ </Button>
34
+ )
35
+
36
+ export const LoadingDanger = () => (
37
+ <Button loading variant="danger">
38
+ Clearing...
39
+ </Button>
40
+ )
41
+
42
+ export const AllVariants = () => (
43
+ <Stack gap="sm">
44
+ <Button>Say hello</Button>
45
+ <Button variant="secondary">View history</Button>
46
+ <Button variant="danger">Clear history</Button>
47
+ <Button disabled>Saved</Button>
48
+ <Button loading>Saving...</Button>
49
+ </Stack>
50
+ )
@@ -0,0 +1,26 @@
1
+ import type { ButtonComponent } from "@backbone/design-system-contract"
2
+
3
+ export const Button: ButtonComponent = ({
4
+ children,
5
+ className,
6
+ disabled,
7
+ loading = false,
8
+ type = "button",
9
+ variant = "primary",
10
+ ...props
11
+ }) => {
12
+ const classes = [
13
+ "ds-button",
14
+ `ds-button--${variant}`,
15
+ loading ? "ds-button--loading" : null,
16
+ className,
17
+ ]
18
+ .filter(Boolean)
19
+ .join(" ")
20
+
21
+ return (
22
+ <button className={classes} disabled={disabled || loading} type={type} {...props}>
23
+ {children}
24
+ </button>
25
+ )
26
+ }
@@ -0,0 +1,18 @@
1
+ import type { StoryDefault } from "@ladle/react"
2
+ import { Button, EmptyState } from "."
3
+
4
+ export default {
5
+ title: "Design System / EmptyState",
6
+ } satisfies StoryDefault
7
+
8
+ export const WithoutAction = () => (
9
+ <EmptyState description="Say hello to create the first saved input." title="No inputs yet" />
10
+ )
11
+
12
+ export const WithAction = () => (
13
+ <EmptyState
14
+ action={<Button>Say hello</Button>}
15
+ description="Create a saved hello-world input."
16
+ title="No inputs yet"
17
+ />
18
+ )
@@ -0,0 +1,17 @@
1
+ import type { EmptyStateComponent } from "@backbone/design-system-contract"
2
+ import { Heading } from "./heading"
3
+ import { Stack } from "./stack"
4
+ import { Text } from "./text"
5
+
6
+ export const EmptyState: EmptyStateComponent = ({ action, description, title }) => {
7
+ return (
8
+ <section className="ds-empty-state">
9
+ <Stack gap="sm">
10
+ <Heading level={2}>{title}</Heading>
11
+ <Text tone="muted">{description}</Text>
12
+ </Stack>
13
+
14
+ {action && <div className="ds-empty-state__actions">{action}</div>}
15
+ </section>
16
+ )
17
+ }
@@ -0,0 +1,15 @@
1
+ import type { Story, StoryDefault } from "@ladle/react"
2
+ import { FormField, TextInput } from "."
3
+
4
+ export default {
5
+ title: "Design System / FormField",
6
+ } satisfies StoryDefault
7
+
8
+ export const Default: Story<{ label: string }> = ({ label }) => (
9
+ <FormField inputId="ladle-field" label={label}>
10
+ <TextInput id="ladle-field" placeholder="Field input" />
11
+ </FormField>
12
+ )
13
+ Default.args = {
14
+ label: "Field label",
15
+ }
@@ -0,0 +1,10 @@
1
+ import type { FormFieldComponent } from "@backbone/design-system-contract"
2
+
3
+ export const FormField: FormFieldComponent = ({ children, inputId, label }) => {
4
+ return (
5
+ <label className="ds-field" htmlFor={inputId}>
6
+ <span className="ds-field__label">{label}</span>
7
+ {children}
8
+ </label>
9
+ )
10
+ }
@@ -0,0 +1,27 @@
1
+ import type { Story, StoryDefault } from "@ladle/react"
2
+ import type { FormEvent } from "react"
3
+ import { Button, Form, FormField, Heading, Inline, Stack, TextInput } from "."
4
+
5
+ export default {
6
+ title: "Design System / Form",
7
+ } satisfies StoryDefault
8
+
9
+ export const Default: Story = () => (
10
+ <Form ariaLabelledBy="ladle-form-title" onSubmit={preventSubmit}>
11
+ <Stack gap="md">
12
+ <Heading id="ladle-form-title" level={3}>
13
+ Form
14
+ </Heading>
15
+ <FormField inputId="ladle-form-name" label="Name">
16
+ <Inline>
17
+ <TextInput id="ladle-form-name" placeholder="World" />
18
+ <Button type="submit">Submit</Button>
19
+ </Inline>
20
+ </FormField>
21
+ </Stack>
22
+ </Form>
23
+ )
24
+
25
+ function preventSubmit(event: FormEvent<HTMLFormElement>) {
26
+ event.preventDefault()
27
+ }
@@ -0,0 +1,9 @@
1
+ import type { FormComponent } from "@backbone/design-system-contract"
2
+
3
+ export const Form: FormComponent = ({ ariaLabelledBy, children, onSubmit }) => {
4
+ return (
5
+ <form aria-labelledby={ariaLabelledBy} className="ds-form" onSubmit={onSubmit}>
6
+ {children}
7
+ </form>
8
+ )
9
+ }