Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
f72730f
Relocate Workshop UI package and rename src/ui to parts
vinniefalco Sep 20, 2026
012b28a
Move the composer files into parts/chatbox
vinniefalco Sep 20, 2026
fcf543f
Add chat box contract, chip model, static draft renderer
vinniefalco Sep 20, 2026
03b6028
Give dictation an owner token and event-driven mic state
vinniefalco Sep 20, 2026
fa37828
Extract ChatBox: props in, events out, own mic and send
vinniefalco Sep 20, 2026
f0c2b1b
Inject mention source, extend typeahead, add boundary test
vinniefalco Sep 20, 2026
bbff014
Close plan: chatbox extraction
vinniefalco Sep 20, 2026
10c2265
Make chip remove button optional; mark two props reserved
vinniefalco Sep 20, 2026
abac3ef
Remove dead workshop lockfile globs; fix test header
vinniefalco Sep 20, 2026
3bd4ea3
Close plan: chatbox debt removal
vinniefalco Sep 20, 2026
7377cee
Add the harness family to the product boundary matrix
vinniefalco Sep 19, 2026
794d390
Add engine manifest guard with shared dependency walk
vinniefalco Sep 19, 2026
c3713b6
Add retired-symbol scan and harness clippy-ban check
vinniefalco Sep 19, 2026
2c9a99e
Add harness-api crate with gateway and session types
vinniefalco Sep 19, 2026
fa0d230
Add harness skeleton crates and the tagged spawn wrapper
vinniefalco Sep 19, 2026
723131a
Add Turso run log with append and read paths
vinniefalco Sep 19, 2026
34d9cf7
Add per-task and transcript readers to the run log
vinniefalco Sep 19, 2026
a6c7aa5
Split scheduler and protocol modules; restore bench
vinniefalco Sep 19, 2026
73b68b9
Raise every Lua-facing failure as a kind table
vinniefalco Sep 19, 2026
50dd925
Add overflow, call_id, optional tools to Lua protocol
vinniefalco Sep 19, 2026
354e67e
Add the chat dispatch arm for section VMs
vinniefalco Sep 19, 2026
131f435
Honor call_id in tool_call arm; answer local tools inline
vinniefalco Sep 19, 2026
1b576db
Move models.loop into the Lua shim and delete the Rust loop
vinniefalco Sep 19, 2026
274dbd2
Split Lua coro tests, add loop instruction-cost test
vinniefalco Sep 19, 2026
c2a3a57
Replace run-global sys.id counter with chain-path ids
vinniefalco Sep 19, 2026
ecb9396
Add task arena and tasks.spawn to the scheduler
vinniefalco Sep 19, 2026
50a66e1
End a chain's live tasks with it; author leaks fail
vinniefalco Sep 19, 2026
6fe3481
Add task waits, status, notes, and cancel to the scheduler
vinniefalco Sep 19, 2026
9849a91
Add opts.timeout to task waits via effect-backed timer
vinniefalco Sep 19, 2026
3b284e0
Move fanout to a Lua shim over the task protocol
vinniefalco Sep 19, 2026
743f4dd
Add fanout acceptance tests and share a context builder
vinniefalco Sep 19, 2026
57ad5c3
Answer the model's task built-ins over the task arena
vinniefalco Sep 19, 2026
e82d82b
Deliver model-task notices and the await_tasks built-in
vinniefalco Sep 19, 2026
3811c5b
Add end-to-end acceptance tests for model tasks
vinniefalco Sep 19, 2026
884764b
Add engine vocabulary: Event, Provenance, Timestamp, Flags
vinniefalco Sep 19, 2026
f9e6d54
Add sync CancelHandle as an AtomicBool parent-child tree
vinniefalco Sep 19, 2026
fcf773e
Split the sync dispatch body out of the tool-dispatch race
vinniefalco Sep 19, 2026
b315ffc
Report run events as values with task provenance
vinniefalco Sep 19, 2026
5263cf3
Issue leaf work as Effect values through a performer table
vinniefalco Sep 19, 2026
f45ce6e
Add Run::step and Run::resume; move tokio out of the engine
vinniefalco Sep 19, 2026
980d08c
Add serial driver, TaskEvents read, and determinism tests
vinniefalco Sep 19, 2026
f690020
Replace the engine's clock and RNG with run inputs
vinniefalco Sep 19, 2026
e0b8dc3
Bind tools by descriptor; move host seams to RunHost
vinniefalco Sep 19, 2026
40b02e0
Bridge workshop-sessions engine imports through harness-api
vinniefalco Sep 19, 2026
c6f0de7
Retire execute::run; drive Workshop sessions on tokio driver
vinniefalco Sep 19, 2026
f450cb7
Move the model transport to harness-models
vinniefalco Sep 20, 2026
60bf5ed
Move capabilities and web tools into the harness
vinniefalco Sep 20, 2026
2f53a25
Strip engine runtime deps; Emitter replaces Observer
vinniefalco Sep 20, 2026
d0ff5a4
Wire the engine guards into tidy and the xtask tests
vinniefalco Sep 20, 2026
51ae148
Pin the serial event stream to the observer sequence
vinniefalco Sep 20, 2026
d73278a
Add harness effect loop, performer traits, answer records
vinniefalco Sep 20, 2026
15d7da9
Add chat, timer, store, and task-events performers
vinniefalco Sep 20, 2026
d5dfd7a
Prepare a run from a prompt file and perform tool calls
vinniefalco Sep 20, 2026
399b2c5
Add end-to-end harness test; seed root provenance
vinniefalco Sep 20, 2026
c1e2199
Move pure session pieces into harness-sessions
vinniefalco Sep 20, 2026
52d8ead
Add session lifecycle states and interrupt race rule
vinniefalco Sep 20, 2026
b1366fe
Move input waits to harness-sessions as InputPerformer
vinniefalco Sep 20, 2026
d690d1e
Move Harness and Session runtime into harness-sessions
vinniefalco Sep 20, 2026
8f5dee8
Move Workshop agent sessions onto the harness
vinniefalco Sep 20, 2026
ad7f9db
Document the harness and write the Papergate migration note
vinniefalco Sep 20, 2026
aa03ae5
Test trust wrapping of task results reaching a model
vinniefalco Sep 20, 2026
5abb3b5
Close plan: sans-io engine harness
vinniefalco Sep 20, 2026
39c141c
Settle live tasks with one terminal when the run ends
vinniefalco Sep 20, 2026
cb74a37
Report session failures as a typed kind plus message
vinniefalco Sep 20, 2026
9530340
Close plan: harness debt removal
vinniefalco Sep 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 4 additions & 3 deletions .cursor/rules/workshop-spa.mdc
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
---
description: Workshop SPA conventions - feature directories, barrel exports, lazy loading, CSS colocation, token-only values
globs: crates/workshop-server/ui/**
globs: crates/workshop/ui/**
alwaysApply: false
---

# Workshop SPA Conventions

## Feature directories and lazy loading

- Feature-based directories under `ui/` (`ui/agent/`, `ui/editor/`, `ui/layout/`, `ui/menu/`, `ui/take/`, `ui/stt/`, `ui/chrome/`, `ui/status/`, `ui/workspace/`, `ui/gateway/`). Shared code lives in `services/` or `base/`; shared UI assets (boot-loaded icons) live in `ui/shared/`; design tokens live in `tokens/`.
- The package at `crates/workshop/ui/` is a sibling of the `crates/workshop/server/` crate that builds and serves it. `src/` has three layers: `base/` (lifecycle, events, paths, the `WorkshopPart` base class), `services/` (DOM-free registries and services), and `parts/` (feature directories; every panel extends `base/workshop-part.ts`).
- Feature-based directories under `parts/` (`parts/agent/`, `parts/editor/`, `parts/layout/`, `parts/menu/`, `parts/take/`, `parts/stt/`, `parts/chrome/`, `parts/status/`, `parts/workspace/`, `parts/gateway/`). Shared code lives in `services/` or `base/`; shared UI assets (boot-loaded icons) live in `parts/shared/`; design tokens live in `tokens/`.
- Barrel exports: every directory has an `index.ts`. Lazy directories export `register()` installing commands, menu items, panel factories, and socket subscriptions.
- The boot shell (`main.ts`, `services/`, `base/`) loads immediately. Feature directories load via dynamic `import()` on first activation. Lazy-loaded panels never import the boot shell.
- Registration, not central wiring: panel types, menu items, services, socket handlers, keyboard shortcuts, and lifecycle disposal self-register through the panel, menu/command, and service registries.

## CSS colocation and tokens

- CSS lives beside its TypeScript, imported as a side-effect. Never a separate `styles/` tree. Every feature directory is self-contained: `.ts`, `.css`, and `index.ts` together. A designer finds the styles for the agent chat at `ui/agent/agent-session.css`, not by grepping a flat directory.
- CSS lives beside its TypeScript, imported as a side-effect. Never a separate `styles/` tree. Every feature directory is self-contained: `.ts`, `.css`, and `index.ts` together. A designer finds the styles for the agent chat at `parts/agent/agent-session.css`, not by grepping a flat directory.
- No raw color, size, or spacing values in component CSS. Use `--ws-*` tokens from `tokens/`. Primitives go in `tokens/base.css`, intent aliases in `tokens/semantic.css`, per-component overrides in `tokens/component.css`. A designer themes the app by editing `semantic.css`.
- CSS classes use the `.ws-` project prefix (`.ws-agent-toolbar`). Design tokens use the `--ws-` prefix (`--ws-color-bg-surface`).
- Directories and files are kebab-case (`agent-session-view.ts`, `agent-session.css`).
4 changes: 2 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
# The UI sources are bundled by esbuild; CRLF conversion on Windows
# checkouts would change bundle bytes between platforms.
crates/gateway/config-ui/ui/** text eol=lf
crates/workshop/server/ui/** text eol=lf
crates/workshop/ui/** text eol=lf
# Images are not text; the blanket rules above must not convert them.
crates/gateway/config-ui/ui/**/*.png binary
crates/workshop/server/ui/**/*.png binary
crates/workshop/ui/**/*.png binary
# The event-log schema canary pins the version-1 file exactly as the log
# writer emits it (LF); autocrlf must not rewrite it on Windows checkouts.
crates/workshop/server/tests/it/observer/*.jsonl text eol=lf
24 changes: 9 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,10 @@ jobs:
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json

- name: Install UI dependencies
run: |
npm ci --prefix crates/workshop/server/ui
npm ci --prefix crates/workshop/ui
npm ci --prefix crates/gateway/config-ui/ui

- name: Build Gateway without Workshop UI tooling
Expand Down Expand Up @@ -90,11 +89,10 @@ jobs:
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json

- name: Install UI dependencies
run: |
npm ci --prefix crates/workshop/server/ui
npm ci --prefix crates/workshop/ui
npm ci --prefix crates/gateway/config-ui/ui

- name: Test (concurrent via nextest)
Expand Down Expand Up @@ -136,11 +134,10 @@ jobs:
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json

- name: Install UI dependencies
run: |
npm ci --prefix crates/workshop/server/ui
npm ci --prefix crates/workshop/ui
npm ci --prefix crates/gateway/config-ui/ui

- name: Docs
Expand Down Expand Up @@ -168,11 +165,10 @@ jobs:
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json

- name: Install UI dependencies
run: |
npm ci --prefix crates/workshop/server/ui
npm ci --prefix crates/workshop/ui
npm ci --prefix crates/gateway/config-ui/ui

- name: Build featureless Gateway
Expand Down Expand Up @@ -250,11 +246,10 @@ jobs:
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json

- name: Install UI dependencies
run: |
npm ci --prefix crates/workshop/server/ui
npm ci --prefix crates/workshop/ui
npm ci --prefix crates/gateway/config-ui/ui

- name: Build featureless Gateway
Expand Down Expand Up @@ -303,22 +298,21 @@ jobs:
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json

- name: Install UI dependencies
working-directory: crates/workshop/server/ui
working-directory: crates/workshop/ui
run: npm ci

- name: Typecheck
working-directory: crates/workshop/server/ui
working-directory: crates/workshop/ui
run: npm run typecheck

- name: Build
working-directory: crates/workshop/server/ui
working-directory: crates/workshop/ui
run: npm run build

- name: Test
working-directory: crates/workshop/server/ui
working-directory: crates/workshop/ui
run: npm test

- name: Install config UI dependencies
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/dist-ci/build-setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json
- name: Install UI dependencies
run: npm ci --prefix crates/workshop/server/ui
run: npm ci --prefix crates/workshop/ui
- name: Install config UI dependencies
run: npm ci --prefix crates/gateway/config-ui/ui
2 changes: 1 addition & 1 deletion .github/workflows/llama-cuda-blackwell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ jobs:
node-version: 22

- name: Install UI dependencies
working-directory: crates/workshop/server/ui
working-directory: crates/workshop/ui
run: npm ci

- name: Install config UI dependencies
Expand Down
9 changes: 3 additions & 6 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,9 @@ jobs:
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json
- name: Install UI dependencies
run: |
npm ci --prefix crates/workshop/server/ui
npm ci --prefix crates/workshop/ui
npm ci --prefix crates/gateway/config-ui/ui
- name: Build
run: cargo build --release --locked -p gateway
Expand Down Expand Up @@ -98,10 +97,9 @@ jobs:
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json
- name: Install UI dependencies
run: |
npm ci --prefix crates/workshop/server/ui
npm ci --prefix crates/workshop/ui
npm ci --prefix crates/gateway/config-ui/ui
- name: Build
run: cargo build --release --locked -p gateway
Expand Down Expand Up @@ -161,10 +159,9 @@ jobs:
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json

- name: Install UI dependencies
working-directory: crates/workshop/server/ui
working-directory: crates/workshop/ui
run: npm ci

- name: Install config UI dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/promptforge-gateway-v-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ jobs:
with:
"node-version": 22
- name: "Install UI dependencies"
run: "npm ci --prefix crates/workshop/server/ui"
run: "npm ci --prefix crates/workshop/ui"
- name: "Install config UI dependencies"
run: "npm ci --prefix crates/gateway/config-ui/ui"
- name: Install dist
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/release-workshop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,9 @@ jobs:
cache-dependency-path: |
crates/*/ui/package-lock.json
crates/gateway/*/ui/package-lock.json
crates/workshop/*/ui/package-lock.json

- name: Install UI dependencies
working-directory: crates/workshop/server/ui
working-directory: crates/workshop/ui
run: npm ci

# The gateway sidecar build needs the config UI's esbuild pipeline
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/workshop-installer-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
node-version: 22

- name: Install UI dependencies
working-directory: crates/workshop/server/ui
working-directory: crates/workshop/ui
run: npm ci

- name: Build featureless Gateway
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
# UI build pipeline: npm install target and the esbuild output. The build
# scripts write the bundle to OUT_DIR; `npm run build`/`--watch` still write
# dist/ in place for the jsdom tests, and none of it is tracked.
/crates/workshop/server/ui/node_modules/
/crates/workshop/ui/node_modules/
/crates/gateway/config-ui/ui/node_modules/
/crates/workshop/server/ui/dist/
/crates/workshop/ui/dist/
/crates/gateway/config-ui/ui/dist/
# tauri-build's generated ACL schemas, regenerated on every workshop build.
/crates/workshop/shell/gen/
Expand Down
16 changes: 9 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PromptForge

Multi-crate Rust workspace for the PromptForge pipeline runtime, inference gateway, and Workshop desktop product.
Multi-crate Rust workspace for the PromptForge pipeline engine, the harness that hosts it, the inference gateway, and the Workshop desktop product.

## Principles

Expand All @@ -17,15 +17,17 @@ Multi-crate Rust workspace for the PromptForge pipeline runtime, inference gatew
- Workshop is a user-facing agentic development environment: a Tauri desktop application with an HTML/CSS/TypeScript UI
- PromptForge is the runtime execution engine for the PromptForge Prompting Language: structured Markdown files with live Lua code fences
- Gateway is an independent service that proxies local and remote inference through one OpenAI-compatible HTTP and WebSocket endpoint
- Harness is the engine's only production host: it owns the tokio runtime, the performers that carry out the engine's effects, agent sessions, and the run log; Workshop and other clients drive runs through it

## Structure

- The three main products are PromptForge, Gateway, and Workshop
- Workshop crates are named workshop-* and must not depend on gateway crates
- The four main products are PromptForge, Gateway, Workshop, and Harness
- Workshop crates are named workshop-* and must not depend on gateway crates; workshop crates may name the gateway public pair, the promptforge door, and `harness-api`
- Gateway's public surface is two root crates, `gateway-api` and `gateway-api-discovery`; everything else lives under crates/gateway/, a manifestless container private to the family - no outside crate may depend into it, and workshop crates may name only the public pair. Gateway crates must not depend on promptforge or workshop crates
- Workshop crates live under crates/workshop/, a manifestless container private to the family - no outside crate may depend into it; the shell is crates/workshop/shell (package `workshop`), and the server and its subsystems sit beside it with short directory names
- The composed topology rule: a crate in a family container (crates/promptforge/, crates/gateway/, crates/workshop/) may depend only on crates at the crates/ root and its own siblings; the root is the public layer. Crates named build-* are meta tooling, exempt from container privacy
- PromptForge crates are named promptforge-* and must not depend on gateway or workshop crates
- Harness crates are named harness-*. Their public surface is one root crate, `harness-api`; everything else lives under crates/harness/, a fourth manifestless container private to the family, and `harness-api` is its one door - the only outside crate permitted to depend into it. harness-* crates may depend on `promptforge-api-runtime`, `promptforge-api-types`, `gateway-api`, `gateway-api-discovery`, and shared-* crates, never on workshop crates or on a private gateway crate; workshop crates may depend on harness-* only through `harness-api`; promptforge-* and gateway-* crates must not depend on harness crates
- The composed topology rule: a crate in a family container (crates/promptforge/, crates/gateway/, crates/workshop/, crates/harness/) may depend only on crates at the crates/ root and its own siblings; the root is the public layer. Crates named build-* are meta tooling, exempt from container privacy
- PromptForge crates are named promptforge-* and must not depend on gateway, workshop, or harness crates
- PromptForge is one door: crates outside the promptforge-* family may depend only on promptforge-api-runtime and promptforge-api-types, never on the internal promptforge-* substrate crates; the crates under crates/promptforge/ are private to the family, and promptforge-api-runtime is the only outside crate permitted to depend into them
- The Workshop shell (the `workshop` crate) depends on `workshop-server-api` and never on `workshop-server`; the facade is the shell's entire view of the server
- Shared crates are named shared-*, contain the public API surface across products and downstream crates, and must not depend on any product crates. PromptForge's own public surface is promptforge-api-runtime and promptforge-api-types, named promptforge-* now that the types crate has left shared-*; Gateway's is gateway-api and gateway-api-discovery, named gateway-* now that both have left shared-*
Expand Down Expand Up @@ -54,8 +56,8 @@ Multi-crate Rust workspace for the PromptForge pipeline runtime, inference gatew
## Structural Rules

- Dependencies flow one way: shell -> features -> services -> vocabulary. Never add a dependency from a lower tier to a higher one. If Cargo rejects a cycle, the design is wrong, not the graph. On the SPA side, lazy-loaded panels never import the boot shell; shared code lives in services/ or base/.
- Every workshop-* crate's lib.rs opens with a //! doc carrying a `## Invariants` marker that lists what the crate may depend on and what it may not. Read it before adding an import. Every SPA concern directory (ui/editor/, ui/agent/, etc.) has the same in its index.ts.
- No file exceeds 500 lines. If an edit would push a file past 500, split first, then edit. `cargo test -p build-xtask` enforces the tier graph, the lint inheritance, the ceiling over the Rust files in the workshop crates carrying the marker, and the product-boundary matrix above (including the one-door rule and the container privacy rules for crates/promptforge/, crates/gateway/, crates/workshop/, and the nested crates/gateway/stt/ subsystem, whose only family-visible crate is gateway-stt) across every workspace manifest; `cargo test -p gateway-stt --test it architecture` checks the same product matrix from cargo metadata; the Tauri shell (the `workshop` crate) is exempt until the headless agent mode plan.
- Every workshop-* and harness-* crate's lib.rs opens with a //! doc carrying a `## Invariants` marker that lists what the crate may depend on and what it may not. Read it before adding an import. Every SPA concern directory (ui/editor/, ui/agent/, etc.) has the same in its index.ts.
- No file exceeds 500 lines. If an edit would push a file past 500, split first, then edit. `cargo test -p build-xtask` enforces the tier graph, the lint inheritance, the ceiling over the Rust files in the workshop-* and harness-* crates carrying the marker, and the product-boundary matrix above (including the one-door rules for promptforge and harness and the container privacy rules for crates/promptforge/, crates/gateway/, crates/workshop/, crates/harness/, and the nested crates/gateway/stt/ subsystem, whose only family-visible crate is gateway-stt) across every workspace manifest; `cargo test -p gateway-stt --test it architecture` checks the same product matrix from cargo metadata; the Tauri shell (the `workshop` crate) is exempt until the headless agent mode plan.
- Source directories are flat by default. A subdirectory of source files must contain at least three files; one or two files belong beside the parent module as `foo-bar.rs` (parent stem, dash, kebab label), wired with an explicit path attribute so the module name stays clean: `#[path = "foo-bar.rs"] mod bar;`. The two forms are convertible in both directions: when a `foo-*.rs` sibling group grows to three files, rehydrate it into a `foo/` subdirectory in standard module layout (`foo/bar.rs` beside `foo.rs`) and drop the path attributes; when a subdirectory shrinks below three files, flatten it back to kebab siblings. Apply whichever conversion applies when you touch files in a group on the wrong side of the line. Top-level `tests/` and `benches/` trees are exempt; they follow Cargo target conventions.

## SPA and CSS Rules
Expand Down
Loading
Loading