feat: add desktop app (#3)

This commit is contained in:
Wes
2026-03-09 13:02:11 -07:00
committed by GitHub
parent 058c4b92a9
commit 629c99fd2f
133 changed files with 10609 additions and 35 deletions
+79 -11
View File
@@ -8,19 +8,87 @@ env:
CARGO_TERM_COLOR: always
jobs:
check:
rust-lint:
name: Rust Lint
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
with:
components: rustfmt, clippy
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
- uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- run: cargo fmt --all -- --check
- run: cargo clippy --workspace --all-targets -- -D warnings
- run: cargo test --workspace
- run: cargo install cargo-audit --locked
- run: cargo audit --ignore RUSTSEC-2023-0071 --ignore RUSTSEC-2024-0384
- run: cargo install cargo-deny --locked
- run: cargo deny check
- name: Format check
run: just fmt-check
- name: Clippy
run: just clippy
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
- uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Unit tests
run: just test-unit
desktop:
name: Desktop
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
- uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
with:
workspaces: desktop/src-tauri
- name: Install Tauri dependencies (Linux)
env:
DEBIAN_FRONTEND: noninteractive
run: |
sudo apt-get update \
-o Acquire::Retries=3 \
-o Acquire::http::Timeout=30 \
-o Acquire::https::Timeout=30
sudo apt-get install -y --no-install-recommends \
-o Acquire::Retries=3 \
-o Acquire::http::Timeout=30 \
-o Acquire::https::Timeout=30 \
-o DPkg::Lock::Timeout=120 \
build-essential \
curl \
file \
libayatana-appindicator3-dev \
libgtk-3-dev \
librsvg2-dev \
libssl-dev \
libwebkit2gtk-4.1-dev \
libxdo-dev \
patchelf \
wget
- name: Install desktop dependencies
run: just desktop-install-ci
- name: Desktop lint and format
run: just desktop-check
- name: Desktop build
run: just desktop-build
- name: Desktop Tauri check
run: just desktop-tauri-check
security:
name: Security
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
- name: Dependency policy
run: cargo-deny check
+7 -1
View File
@@ -39,8 +39,11 @@ unacceptable behavior to **conduct@sprout-relay.org**.
| Tool | Version | Notes |
|------|---------|-------|
| Rust | 1.88+ | Install via [rustup](https://rustup.rs/) |
| Node.js | 24+ | Required for desktop app commands and `just ci` |
| pnpm | 10+ | Required for desktop app commands and `just ci` |
| Docker | 24+ | For MySQL, Redis, Typesense |
| `just` | latest | Task runner — `cargo install just` |
| `lefthook` | latest | Optional; run `lefthook install` for local Git hooks |
| `sqlx-cli` | latest | Optional; `just migrate` falls back to `docker exec` |
This repo uses [Hermit](https://cashapp.github.io/hermit/) for toolchain
@@ -68,6 +71,9 @@ cp .env.example .env
# 4. Start infrastructure + run migrations
just setup
# 5. Install Git hooks (optional, recommended)
lefthook install
```
`just setup` starts Docker services (MySQL on `:3306`, Redis on `:6379`,
@@ -134,7 +140,7 @@ Before opening a PR, run the full CI gate locally:
```bash
just ci
# Runs: fmt-check + clippy + unit tests
# Runs: check + unit tests + desktop build + Tauri check
```
This is the same check that runs in CI. PRs that fail `just ci` will not be
+1
View File
@@ -14,6 +14,7 @@ members = [
"crates/sprout-admin",
"crates/sprout-workflow",
]
exclude = ["desktop/src-tauri"]
resolver = "2"
[workspace.package]
+8 -4
View File
@@ -193,7 +193,7 @@ Set `SPROUT_PRIVATE_KEY` (nsec format) to use a persistent identity.
## Development
**Prerequisites:** Rust 1.88+, Docker, [`just`](https://github.com/casey/just)
**Prerequisites:** Rust 1.88+, Docker, Node.js 24+, pnpm 10+, [`just`](https://github.com/casey/just)
This repo uses [Hermit](https://cashapp.github.io/hermit/) for toolchain pinning. Activate with:
@@ -206,11 +206,15 @@ This repo uses [Hermit](https://cashapp.github.io/hermit/) for toolchain pinning
```bash
just setup # Start Docker services + run migrations
just relay # Run the relay (dev mode)
just build # Build entire workspace
just check # fmt-check + clippy
just build # Build the Rust workspace
just desktop-install # Install desktop dependencies
just desktop-dev # Run the desktop web UI only
just desktop-app # Run the Tauri desktop app
just desktop-ci # Desktop check + build + Tauri Rust check
just check # Rust fmt/clippy + desktop check
just test-unit # Unit tests (no infra required)
just test # All tests (starts services if needed)
just ci # fmt-check + clippy + unit tests (CI gate)
just ci # check + unit tests + desktop build + Tauri check
just migrate # Run pending migrations
just down # Stop Docker services (keep data)
just reset # ⚠️ Wipe all data and recreate environment
+1
View File
@@ -0,0 +1 @@
hermit
+1
View File
@@ -0,0 +1 @@
hermit
+1
View File
@@ -0,0 +1 @@
hermit
+1
View File
@@ -0,0 +1 @@
hermit
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
+1
View File
@@ -0,0 +1 @@
.cargo-deny-0.19.0.pkg
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
+1
View File
@@ -0,0 +1 @@
.rustup-1.28.2.pkg
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
+1
View File
@@ -0,0 +1 @@
.node-24.14.0.pkg
+2 -2
View File
@@ -17,7 +17,7 @@ if [ -z "${HERMIT_STATE_DIR}" ]; then
esac
fi
export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://d1abdrezunyhdp.cloudfront.net/square}"
export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}"
HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")"
export HERMIT_CHANNEL
export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit}
@@ -26,7 +26,7 @@ if [ ! -x "${HERMIT_EXE}" ]; then
echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2
INSTALL_SCRIPT="$(mktemp)"
# This value must match that of the install script
INSTALL_SCRIPT_SHA256="4b006236f2e5e81939229b377bb355e3608f94d73ff8feccbd5792d1ed5699cd"
INSTALL_SCRIPT_SHA256="09ed936378857886fd4a7a4878c0f0c7e3d839883f39ca8b4f2f242e3126e1c6"
if [ "${INSTALL_SCRIPT_SHA256}" = "BYPASS" ]; then
curl -fsSL "${HERMIT_DIST_URL}/install.sh" -o "${INSTALL_SCRIPT}"
else
+1
View File
@@ -0,0 +1 @@
.lefthook-2.1.3.pkg
Symlink
+1
View File
@@ -0,0 +1 @@
.node-24.14.0.pkg
Symlink
+1
View File
@@ -0,0 +1 @@
.node-24.14.0.pkg
Symlink
+1
View File
@@ -0,0 +1 @@
.node-24.14.0.pkg
Symlink
+1
View File
@@ -0,0 +1 @@
.pnpm-10.31.0.pkg
Symlink
+1
View File
@@ -0,0 +1 @@
.rustup-1.28.2.pkg
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
+1 -1
View File
@@ -1 +1 @@
.rust@1.88.pkg
.rustup-1.28.2.pkg
Symlink
+1
View File
@@ -0,0 +1 @@
.rustup-1.28.2.pkg
+25
View File
@@ -0,0 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.pnpm-store
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+25
View File
@@ -0,0 +1,25 @@
# Sprout
Desktop chat shell with:
- Tauri + React + TypeScript + Vite
- Tailwind CSS
- shadcn/ui-ready shared components
- Biome (lint/format/check)
- Feature-driven frontend structure
## Scripts
- `pnpm dev` - run the web frontend
- `pnpm tauri dev` - run the desktop app
- `pnpm build` - typecheck and build frontend
- `pnpm typecheck` - TypeScript checks
- `pnpm lint` - Biome lint
- `pnpm format` - Biome format (write)
- `pnpm check` - Biome check
## Structure
- `src/shared` - reusable app-wide code (`ui`, `lib`, `styles`)
- `src/features` - feature modules (vertical slices)
- `src/app` - top-level app composition
+37
View File
@@ -0,0 +1,37 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.6/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"indentStyle": "space"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noUnknownAtRules": "off"
}
}
},
"assist": {
"enabled": false
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"css": {
"parser": {
"tailwindDirectives": true
}
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/shared/styles/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/shared/ui",
"utils": "@/shared/lib/cn",
"ui": "@/shared/ui",
"lib": "@/shared/lib"
}
}
+14
View File
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/sprout.png?v=20260309" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+45
View File
@@ -0,0 +1,45 @@
{
"name": "sprout",
"private": true,
"version": "0.1.0",
"type": "module",
"packageManager": "pnpm@10.12.1",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"typecheck": "tsc --noEmit",
"lint": "biome lint .",
"check": "biome check .",
"format": "biome format --write .",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tooltip": "^1.2.8",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-opener": "^2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.577.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"@biomejs/biome": "^2.4.6",
"@tauri-apps/cli": "^2",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
"autoprefixer": "^10.4.27",
"postcss": "^8.5.8",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"typescript": "~5.8.3",
"vite": "^7.0.4"
}
}
+2748
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

+69
View File
@@ -0,0 +1,69 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
<defs>
<linearGradient id="sprout-bg" x1="92" y1="72" x2="432" y2="448" gradientUnits="userSpaceOnUse">
<stop stop-color="#225E39" />
<stop offset="1" stop-color="#112C1C" />
</linearGradient>
<radialGradient id="sprout-glow" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(380 126) rotate(119.845) scale(152.955)">
<stop stop-color="#B6F06B" stop-opacity=".4" />
<stop offset="1" stop-color="#B6F06B" stop-opacity="0" />
</radialGradient>
<linearGradient id="sprout-leaf-left" x1="123" y1="188" x2="252" y2="282" gradientUnits="userSpaceOnUse">
<stop stop-color="#B9F173" />
<stop offset="1" stop-color="#63BD43" />
</linearGradient>
<linearGradient id="sprout-leaf-right" x1="270" y1="169" x2="421" y2="274" gradientUnits="userSpaceOnUse">
<stop stop-color="#97E45F" />
<stop offset="1" stop-color="#4FA53A" />
</linearGradient>
<linearGradient id="sprout-soil" x1="171" y1="309" x2="341" y2="403" gradientUnits="userSpaceOnUse">
<stop stop-color="#D89747" />
<stop offset="1" stop-color="#8D5228" />
</linearGradient>
</defs>
<rect x="32" y="32" width="448" height="448" rx="112" fill="url(#sprout-bg)" />
<circle cx="380" cy="126" r="124" fill="url(#sprout-glow)" />
<path
d="M149 346c0-31 25-56 56-56h102c31 0 56 25 56 56 0 49-43 70-107 70s-107-21-107-70Z"
fill="#714121"
/>
<ellipse cx="256" cy="347" rx="80" ry="32" fill="url(#sprout-soil)" />
<path
d="M256 339c-8-39-7-83 12-125"
stroke="#DAF9B8"
stroke-linecap="round"
stroke-width="22"
/>
<path
d="M259 239c-33-43-80-60-136-54 2 52 33 90 87 100 24 4 44-9 49-46Z"
fill="url(#sprout-leaf-left)"
/>
<path
d="M270 220c39-46 91-66 153-56-4 57-38 92-98 99-24 3-43-11-55-43Z"
fill="url(#sprout-leaf-right)"
/>
<path
d="M210 252c-14-18-30-33-50-45"
stroke="#E7FBD0"
stroke-linecap="round"
stroke-width="10"
opacity=".75"
/>
<path
d="M315 242c22-26 46-43 73-55"
stroke="#D8F5B8"
stroke-linecap="round"
stroke-width="10"
opacity=".65"
/>
<path
d="M231 316c2 17 15 31 32 31 18 0 33-15 35-34 2-17-8-31-24-35-18-5-39 8-43 27Z"
fill="#E09A49"
/>
<path
d="M244 308c6-7 15-11 24-10"
stroke="#F7C97C"
stroke-linecap="round"
stroke-width="8"
/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+7
View File
@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas
+5218
View File
File diff suppressed because it is too large Load Diff
+24
View File
@@ -0,0 +1,24 @@
[package]
name = "sprout"
version = "0.1.0"
description = "Sprout desktop app"
authors = ["you"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "sprout_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
@@ -0,0 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": ["core:default", "opener:default"]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#fff</color>
</resources>
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="none">
<defs>
<clipPath id="icon-clip">
<rect x="132" y="132" width="760" height="760" rx="171" ry="171" />
</clipPath>
</defs>
<g clip-path="url(#icon-clip)">
<rect x="132" y="132" width="760" height="760" rx="171" ry="171" fill="#F4F0E6" />
<image
href="./sprout-art.png"
x="156"
y="156"
width="712"
height="712"
preserveAspectRatio="xMidYMid meet"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 506 B

Some files were not shown because too many files have changed in this diff Show More