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.
- package/README.md +33 -0
- package/bin/create-backbone-template.js +5 -0
- package/package.json +30 -0
- package/src/create-backbone-template.js +204 -0
- package/template/.agents/skills/agent-browser/SKILL.md +55 -0
- package/template/.agents/skills/create-plan/SKILL.md +52 -0
- package/template/.agents/skills/create-plan/agents/openai.yaml +4 -0
- package/template/.agents/skills/create-pr-presentation/SKILL.md +86 -0
- package/template/.agents/skills/create-pr-presentation/agents/openai.yaml +4 -0
- package/template/.agents/skills/implement-plan/SKILL.md +26 -0
- package/template/.agents/skills/implement-plan/agents/openai.yaml +4 -0
- package/template/.agents/skills/review-plan/SKILL.md +38 -0
- package/template/.agents/skills/review-plan/agents/openai.yaml +4 -0
- package/template/.env.schema +30 -0
- package/template/.env.test +6 -0
- package/template/.oxlintrc.json +67 -0
- package/template/.vscode/extensions.json +3 -0
- package/template/.vscode/settings.json +23 -0
- package/template/AGENTS.md +55 -0
- package/template/Cargo.lock +2648 -0
- package/template/Cargo.toml +29 -0
- package/template/Justfile +140 -0
- package/template/README.md +72 -0
- package/template/TODO.md +1 -0
- package/template/_gitignore +12 -0
- package/template/buf.gen.yaml +7 -0
- package/template/buf.yaml +10 -0
- package/template/client/.oxfmtrc.json +8 -0
- package/template/client/.oxlintrc.json +57 -0
- package/template/client/README.md +19 -0
- package/template/client/_gitignore +5 -0
- package/template/client/index.html +12 -0
- package/template/client/package.json +47 -0
- package/template/client/packages/design-system/package.json +19 -0
- package/template/client/packages/design-system/src/index.ts +2 -0
- package/template/client/packages/design-system-basic/package.json +18 -0
- package/template/client/packages/design-system-basic/src/button.stories.tsx +50 -0
- package/template/client/packages/design-system-basic/src/button.tsx +26 -0
- package/template/client/packages/design-system-basic/src/empty-state.stories.tsx +18 -0
- package/template/client/packages/design-system-basic/src/empty-state.tsx +17 -0
- package/template/client/packages/design-system-basic/src/form-field.stories.tsx +15 -0
- package/template/client/packages/design-system-basic/src/form-field.tsx +10 -0
- package/template/client/packages/design-system-basic/src/form.stories.tsx +27 -0
- package/template/client/packages/design-system-basic/src/form.tsx +9 -0
- package/template/client/packages/design-system-basic/src/heading.stories.tsx +14 -0
- package/template/client/packages/design-system-basic/src/heading.tsx +25 -0
- package/template/client/packages/design-system-basic/src/index.tsx +15 -0
- package/template/client/packages/design-system-basic/src/inline.stories.tsx +13 -0
- package/template/client/packages/design-system-basic/src/inline.tsx +5 -0
- package/template/client/packages/design-system-basic/src/layout.stories.tsx +24 -0
- package/template/client/packages/design-system-basic/src/layout.tsx +14 -0
- package/template/client/packages/design-system-basic/src/loader.stories.tsx +8 -0
- package/template/client/packages/design-system-basic/src/loader.tsx +11 -0
- package/template/client/packages/design-system-basic/src/navigation.stories.tsx +16 -0
- package/template/client/packages/design-system-basic/src/navigation.tsx +18 -0
- package/template/client/packages/design-system-basic/src/notice.stories.tsx +13 -0
- package/template/client/packages/design-system-basic/src/notice.tsx +5 -0
- package/template/client/packages/design-system-basic/src/stack.stories.tsx +17 -0
- package/template/client/packages/design-system-basic/src/stack.tsx +5 -0
- package/template/client/packages/design-system-basic/src/styles.css +254 -0
- package/template/client/packages/design-system-basic/src/text-input.stories.tsx +13 -0
- package/template/client/packages/design-system-basic/src/text-input.tsx +5 -0
- package/template/client/packages/design-system-basic/src/text.stories.tsx +21 -0
- package/template/client/packages/design-system-basic/src/text.tsx +5 -0
- package/template/client/packages/design-system-contract/package.json +15 -0
- package/template/client/packages/design-system-contract/src/button.ts +10 -0
- package/template/client/packages/design-system-contract/src/empty-state.ts +9 -0
- package/template/client/packages/design-system-contract/src/form-field.ts +9 -0
- package/template/client/packages/design-system-contract/src/form.ts +9 -0
- package/template/client/packages/design-system-contract/src/heading.ts +9 -0
- package/template/client/packages/design-system-contract/src/index.ts +13 -0
- package/template/client/packages/design-system-contract/src/inline.ts +7 -0
- package/template/client/packages/design-system-contract/src/layout.ts +8 -0
- package/template/client/packages/design-system-contract/src/loader.ts +7 -0
- package/template/client/packages/design-system-contract/src/navigation.ts +13 -0
- package/template/client/packages/design-system-contract/src/notice.ts +8 -0
- package/template/client/packages/design-system-contract/src/stack.ts +8 -0
- package/template/client/packages/design-system-contract/src/text-input.ts +5 -0
- package/template/client/packages/design-system-contract/src/text.ts +9 -0
- package/template/client/packages/design-system-lint/fixtures/invalid/external-ui-import.tsx +5 -0
- package/template/client/packages/design-system-lint/fixtures/invalid/raw-dom-jsx.tsx +3 -0
- package/template/client/packages/design-system-lint/fixtures/invalid/two-violations.tsx +7 -0
- package/template/client/packages/design-system-lint/fixtures/valid/design-system-only.tsx +13 -0
- package/template/client/packages/design-system-lint/package.json +23 -0
- package/template/client/packages/design-system-lint/src/check-design-system-architecture.ts +22 -0
- package/template/client/packages/design-system-lint/src/design-system-architecture.ts +286 -0
- package/template/client/packages/design-system-lint/src/oxlint-plugin.ts +11 -0
- package/template/client/packages/design-system-lint/src/page-architecture.ts +382 -0
- package/template/client/packages/design-system-lint/src/rules.ts +111 -0
- package/template/client/packages/design-system-lint/test/design-system-architecture.test.ts +243 -0
- package/template/client/packages/design-system-lint/test/oxlint-fixtures.test.ts +159 -0
- package/template/client/packages/design-system-lint/test/page-architecture.test.ts +175 -0
- package/template/client/packages/design-system-lint/test/rules.test.ts +65 -0
- package/template/client/packages/design-system-lint/tsconfig.json +29 -0
- package/template/client/src/App.tsx +77 -0
- package/template/client/src/design-system-components.test.tsx +75 -0
- package/template/client/src/gen/helloworld/v1/helloworld_pb.ts +63 -0
- package/template/client/src/main.tsx +18 -0
- package/template/client/src/pages/hello/hello-page.stories.tsx +20 -0
- package/template/client/src/pages/hello/hello-page.test.tsx +90 -0
- package/template/client/src/pages/hello/hello-page.tsx +126 -0
- package/template/client/src/pages/page.ts +20 -0
- package/template/client/src/testing/create-preview-events.test.ts +36 -0
- package/template/client/src/testing/create-preview-events.ts +30 -0
- package/template/client/src/vite-env.d.ts +1 -0
- package/template/client/tsconfig.json +32 -0
- package/template/client/vite.config.ts +21 -0
- package/template/client/vite.ladle.config.ts +5 -0
- package/template/e2e/.gherkin-lintrc +20 -0
- package/template/e2e/.oxfmtrc.json +15 -0
- package/template/e2e/.oxlintrc.json +37 -0
- package/template/e2e/_gitignore +4 -0
- package/template/e2e/features/helloworld.feature +10 -0
- package/template/e2e/package.json +42 -0
- package/template/e2e/playwright.config.ts +16 -0
- package/template/e2e/support/app-gherkin.ts +4 -0
- package/template/e2e/support/fixtures.ts +236 -0
- package/template/e2e/support/gherkin-fixtures/duplicate-id.feature +9 -0
- package/template/e2e/support/gherkin-fixtures/duplicate-id.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/extra-implementation.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/extra-step.spec.ts +10 -0
- package/template/e2e/support/gherkin-fixtures/happy-path.spec.ts +4 -0
- package/template/e2e/support/gherkin-fixtures/missing-id.feature +4 -0
- package/template/e2e/support/gherkin-fixtures/missing-id.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/missing-implementation.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/missing-step.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/playwright.config.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/scenario-outline.feature +9 -0
- package/template/e2e/support/gherkin-fixtures/scenario-outline.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/step-mismatch.spec.ts +9 -0
- package/template/e2e/support/gherkin-fixtures/valid-implementations.ts +23 -0
- package/template/e2e/support/gherkin-fixtures/valid-scenarios.feature +26 -0
- package/template/e2e/support/gherkin.test.ts +184 -0
- package/template/e2e/support/gherkin.ts +321 -0
- package/template/e2e/support/oxlint-plugin.test.ts +328 -0
- package/template/e2e/support/oxlint-plugin.ts +485 -0
- package/template/e2e/tests/helloworld.spec.ts +39 -0
- package/template/e2e/tsconfig.json +26 -0
- package/template/e2e/tsconfig.oxlint-plugin.json +12 -0
- package/template/package.json +9 -0
- package/template/pnpm-lock.yaml +10723 -0
- package/template/pnpm-workspace.yaml +8 -0
- package/template/pr-slide/README.md +95 -0
- package/template/pr-slide/package.json +23 -0
- package/template/pr-slide/src/cli.js +262 -0
- package/template/pr-slide/src/generate-pr-deck.js +833 -0
- package/template/pr-slide/src/git-context.js +91 -0
- package/template/pr-slide/src/presentation-paths.js +9 -0
- package/template/pr-slide/src/presentations.js +53 -0
- package/template/pr-slide/test/generate-pr-deck.test.js +118 -0
- package/template/pr-slide/test/presentation-paths.test.js +14 -0
- package/template/pr-slide/test/presentations.test.js +50 -0
- package/template/proto/helloworld/v1/helloworld.proto +15 -0
- package/template/scripts/run-e2e.sh +10 -0
- package/template/server/Cargo.toml +26 -0
- package/template/server/build.rs +9 -0
- package/template/server/dylint/backbone_server_lints/.cargo/config.toml +6 -0
- package/template/server/dylint/backbone_server_lints/Cargo.lock +1581 -0
- package/template/server/dylint/backbone_server_lints/Cargo.toml +21 -0
- package/template/server/dylint/backbone_server_lints/README.md +5 -0
- package/template/server/dylint/backbone_server_lints/_gitignore +1 -0
- package/template/server/dylint/backbone_server_lints/rust-toolchain +3 -0
- package/template/server/dylint/backbone_server_lints/src/lib.rs +612 -0
- package/template/server/dylint/backbone_server_lints/ui/lib.rs +4 -0
- package/template/server/dylint/backbone_server_lints/ui/lib.stderr +10 -0
- package/template/server/dylint/backbone_server_lints/ui/long_file.rs +303 -0
- package/template/server/dylint/backbone_server_lints/ui/long_file.stderr +6 -0
- package/template/server/dylint/backbone_server_lints/ui/main.rs +59 -0
- package/template/server/dylint/backbone_server_lints/ui/main.stderr +85 -0
- package/template/server/migrations/20260520120000_create_projects.sql +12 -0
- package/template/server/migrations/20260524160000_create_hello_world_inputs.sql +12 -0
- package/template/server/src/config.rs +27 -0
- package/template/server/src/db/hello_world.rs +34 -0
- package/template/server/src/db/hello_world_tests.rs +11 -0
- package/template/server/src/db/mod.rs +39 -0
- package/template/server/src/lib.rs +10 -0
- package/template/server/src/main.rs +43 -0
- package/template/server/src/rpc/greeter/mod.rs +31 -0
- package/template/server/src/rpc/greeter/say_hello.rs +27 -0
- package/template/server/src/rpc/mod.rs +8 -0
- package/template/server/src/state.rs +13 -0
- 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
|
+
```
|
package/template/TODO.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
- Add a linter for unused design system components ?
|
|
@@ -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,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,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
|
+
}
|