Compare commits

..

10 Commits

Author SHA1 Message Date
NicoR
998927f752 Rename form-svelte to TanStack form 2025-07-04 11:24:58 -03:00
NicoR
1da5f9c150 Use TanStack Form with JSON object for draft form state 2025-07-03 12:17:14 -03:00
NicoR
ef242d9a7b Add form example using Svelte + vanilla form 2025-07-03 11:44:46 -03:00
NicoR
ca4551b044 Use TanStack Form with JSON object for draft form state 2025-07-03 10:46:13 -03:00
NicoR
9cd8c407d2 Use React Hook Form with JSON object for draft form state 2025-07-03 10:06:39 -03:00
NicoR
88d393ac91 Keep form's draft state in a JSON object 2025-07-02 15:13:55 -03:00
NicoR
f04b11572d Fix rebase errors 2025-07-02 13:56:56 -03:00
Trisha Lim
3cbd4eae3b fix cotext field 2025-07-02 13:56:56 -03:00
Trisha Lim
63ab6abd4f fix reactivity 2025-07-02 13:56:56 -03:00
Trisha Lim
52243161a3 useForm hook 2025-07-02 13:56:55 -03:00
293 changed files with 10009 additions and 11733 deletions

View File

@@ -6,6 +6,7 @@
"fixed": [
[
"cojson",
"cojson-storage",
"cojson-storage-indexeddb",
"cojson-storage-sqlite",
"cojson-transport-ws",

View File

@@ -0,0 +1,5 @@
---
"jazz-tools": patch
---
Fix type error with `RNQuickCrypto` that prevented using it as a `CryptoProvider`

View File

@@ -0,0 +1,5 @@
---
"create-jazz-app": patch
---
Add default project name

View File

@@ -0,0 +1,7 @@
---
"jazz-react-auth-betterauth": patch
"jazz-auth-betterauth": patch
"jazz-tools": patch
---
Changes that verify the BetterAuth package for React also works with React Native and Expo apps

View File

@@ -1,23 +1,24 @@
# Description
<!-- Please include a summary of the change and which issue is fixed -->
<!-- Please also include relevant motivation and context -->
<!-- Include any links to documentation like RFCs if necessary -->
<!-- Add a link to to relevant preview environments or anything that would simplify visual review process -->
<!-- Supplemental screenshots and video are encouraged, but the primary description should be in text -->
### What this Does
Brief summary of the change, ideally framed in user or product terms.
## Manual testing instructions
### Why Are We Doing This?
Link to the shaped pitch or explain what problem it solves.
<!-- Add any actions required to manually test the changes -->
### Scope / Boundaries
Includes:
- [x] Core feature functionality
- [x] Tests or validation steps
## Tests
Do NOT include:
- [ ] Related stretch features or follow-ups
- [ ] Tests have been added and/or updated
- [ ] Tests have not been updated, because: <!-- Insert reason for not updating tests here -->
- [ ] I need help with writing tests
### Testing Instructions
How a reviewer or QA can verify behavior, offer step-by-step instructions if possible. Screenshots or recordings are welcome.
### Known Issues / Open Questions (if any)
- [ ] Note anything youd like review on or decided async
## Checklist
- [ ] I've updated the part of the docs that are affected the PR changes
- [ ] I've generated a changeset, if a version bump is required
- [ ] I've updated the jsDoc comments to the public APIs I've modified, or added them when missing
### Related Links
- GitHub issue
- Linear pitch
- Design links or references

View File

@@ -1,11 +1,5 @@
name: Code quality
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
push:
branches:

View File

@@ -1,11 +1,5 @@
name: End-to-End Tests for React Native
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
pull_request:
types: [opened, synchronize, reopened]

View File

@@ -1,11 +1,5 @@
name: Jazz Run Tests
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
push:
branches: ["main"]

View File

@@ -0,0 +1,46 @@
name: Playwright Tests
on:
push:
branches: ["main"]
pull_request:
types: [opened, synchronize, reopened]
jobs:
test:
timeout-minutes: 60
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup Source Code
uses: ./.github/actions/source-code/
- name: Install root dependencies
run: pnpm install && pnpm exec turbo build --filter="./packages/*"
- name: Install project dependencies
run: pnpm install
working-directory: ./homepage/homepage
- name: Pnpm Build
run: pnpm exec turbo build
working-directory: ./homepage/homepage
- name: Install Playwright Browsers
run: pnpm exec playwright install
working-directory: ./homepage/homepage
- name: Run Playwright tests
run: pnpm exec playwright test
working-directory: ./homepage/homepage
- uses: actions/upload-artifact@v4
if: failure()
with:
name: homepage-playwright-report
path: ./homepage/homepage/playwright-report/
retention-days: 30

View File

@@ -1,11 +1,5 @@
name: Playwright Tests
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
push:
branches: ["main"]
@@ -19,7 +13,21 @@ jobs:
continue-on-error: true
strategy:
matrix:
shard: ["1/2", "2/2"]
project: [
"tests/e2e",
"examples/chat",
"examples/chat-svelte",
"examples/clerk",
"examples/betterauth",
"examples/file-share-svelte",
"examples/form",
"examples/inspector",
"examples/music-player",
"examples/organization",
"starters/react-passkey-auth",
"starters/svelte-passkey-auth",
"tests/jazz-svelte"
]
steps:
- uses: actions/checkout@v4
@@ -29,129 +37,25 @@ jobs:
- name: Setup Source Code
uses: ./.github/actions/source-code/
- name: Pnpm Build
run: |
if [ -f .env.test ]; then
cp .env.test .env
fi
pnpm turbo build
working-directory: ./${{ matrix.project }}
- name: Install Playwright Browsers
run: pnpm exec playwright install
working-directory: ./${{ matrix.project }}
- name: Run Playwright tests for shard ${{ matrix.shard }}
run: |
# Parse shard information (e.g., "1/2" -> shard_num=1, total_shards=2)
IFS='/' read -r shard_num total_shards <<< "${{ matrix.shard }}"
shard_index=$((shard_num - 1)) # Convert to 0-based index
# Debug: Print parsed values
echo "Parsed shard_num: $shard_num"
echo "Parsed total_shards: $total_shards"
echo "Calculated shard_index: $shard_index"
# Define all projects to test
all_projects=(
"tests/e2e"
"examples/chat"
"examples/chat-svelte"
"examples/clerk"
"examples/betterauth"
"examples/file-share-svelte"
"examples/form"
"examples/inspector"
"examples/music-player"
"examples/organization"
"starters/react-passkey-auth"
"starters/svelte-passkey-auth"
"tests/jazz-svelte"
)
# Calculate which projects this shard should run
shard_projects=()
for i in "${!all_projects[@]}"; do
if [ $((i % total_shards)) -eq $shard_index ]; then
shard_projects+=("${all_projects[i]}")
fi
done
# Track project results
overall_exit_code=0
failed_projects=()
passed_projects=()
echo "=== Running tests for shard ${{ matrix.shard }} ==="
echo "Projects in this shard:"
printf '%s\n' "${shard_projects[@]}"
echo
# Run tests for each project
for project in "${shard_projects[@]}"; do
echo "=== Testing project: $project ==="
# Check if project directory exists
if [ ! -d "$project" ]; then
echo "❌ FAILED: Project directory $project does not exist"
failed_projects+=("$project (directory not found)")
overall_exit_code=1
continue
fi
# Check if project has package.json
if [ ! -f "$project/package.json" ]; then
echo "❌ FAILED: No package.json found in $project"
failed_projects+=("$project (no package.json)")
overall_exit_code=1
continue
fi
# Build the project
echo "🔨 Building $project..."
cd "$project"
if [ -f .env.test ]; then
cp .env.test .env
fi
if ! pnpm turbo build; then
echo "❌ BUILD FAILED: $project"
failed_projects+=("$project (build failed)")
overall_exit_code=1
cd - > /dev/null
continue
fi
# Run Playwright tests
echo "🧪 Running Playwright tests for $project..."
if ! pnpm exec playwright test; then
echo "❌ TESTS FAILED: $project"
failed_projects+=("$project (tests failed)")
overall_exit_code=1
else
echo "✅ TESTS PASSED: $project"
passed_projects+=("$project")
fi
cd - > /dev/null
echo "=== Finished testing $project ==="
echo
done
# Print summary report
echo "=========================================="
echo "📊 TEST SUMMARY FOR SHARD ${{ matrix.shard }}"
echo "=========================================="
if [ ${#passed_projects[@]} -gt 0 ]; then
echo "✅ PASSED (${#passed_projects[@]}):"
printf ' - %s\n' "${passed_projects[@]}"
echo
fi
if [ ${#failed_projects[@]} -gt 0 ]; then
echo "❌ FAILED (${#failed_projects[@]}):"
printf ' - %s\n' "${failed_projects[@]}"
echo
fi
echo "Total projects in shard: ${#shard_projects[@]}"
echo "Passed: ${#passed_projects[@]}"
echo "Failed: ${#failed_projects[@]}"
echo "=========================================="
# Exit with overall status
exit $overall_exit_code
- name: Run Playwright tests
run: pnpm exec playwright test
working-directory: ./${{ matrix.project }}
- uses: actions/upload-artifact@v4
if: failure()
with:
name: ${{ hashFiles(format('{0}/package.json', matrix.project)) }}-playwright-report
path: ./${{ matrix.project }}/playwright-report/
retention-days: 30

View File

@@ -1,11 +1,4 @@
name: Pre-Publish tagged Pull Requests
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
pull_request:
types: [opened, synchronize, reopened, labeled]

View File

@@ -1,11 +1,5 @@
name: Unit Tests
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
pull_request:
types: [opened, synchronize, reopened]

View File

@@ -56,7 +56,7 @@
}
},
{
"include": ["packages/cojson/src/storage/*/**", "cojson-transport-ws/**"],
"include": ["packages/cojson-storage*/**", "cojson-transport-ws/**"],
"linter": {
"enabled": true,
"rules": {

View File

@@ -13,13 +13,13 @@
"@bacons/text-decoder": "^0.0.0",
"@bam.tech/react-native-image-resizer": "^3.0.11",
"@react-native-community/netinfo": "11.4.1",
"expo": "54.0.0-canary-20250701-6a945c5",
"expo": "~53.0.9",
"expo-clipboard": "^7.1.4",
"expo-secure-store": "~14.2.3",
"expo-sqlite": "~15.2.10",
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-native": "0.80.0",
"react": "19.0.0",
"react-native": "0.79.2",
"react-native-get-random-values": "^1.11.0",
"readable-stream": "^4.7.0"
},
@@ -29,4 +29,4 @@
"typescript": "~5.8.3"
},
"private": true
}
}

View File

@@ -11,11 +11,11 @@ react {
// The root of your project, i.e. where "package.json" lives. Default is '../..'
// root = file("../../")
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
reactNativeDir = file("../../../../node_modules/react-native")
// reactNativeDir = file("../../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
codegenDir = file("../../../../node_modules/@react-native/codegen")
// codegenDir = file("../../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js
cliFile = file("../../../../node_modules/react-native/cli.js")
// cliFile = file("../../node_modules/react-native/cli.js")
/* Variants */
// The list of variants to that are debuggable. For those we're going to

View File

@@ -1,6 +1,6 @@
pluginManagement { includeBuild("../../../node_modules/@react-native/gradle-plugin") }
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
plugins { id("com.facebook.react.settings") }
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
rootProject.name = 'ChatRN'
include ':app'
includeBuild('../../../node_modules/@react-native/gradle-plugin')
includeBuild('../node_modules/@react-native/gradle-plugin')

View File

@@ -380,7 +380,7 @@
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
USE_HERMES = true;
@@ -452,7 +452,7 @@
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
VALIDATE_PRODUCT = YES;

View File

@@ -2370,87 +2370,87 @@ PODS:
- Yoga (0.0.0)
DEPENDENCIES:
- boost (from `../../../node_modules/react-native/third-party-podspecs/boost.podspec`)
- DoubleConversion (from `../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- fast_float (from `../../../node_modules/react-native/third-party-podspecs/fast_float.podspec`)
- FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`)
- fmt (from `../../../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- glog (from `../../../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- "op-sqlite (from `../../../node_modules/@op-engineering/op-sqlite`)"
- RCT-Folly (from `../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTDeprecation (from `../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
- RCTRequired (from `../../../node_modules/react-native/Libraries/Required`)
- RCTTypeSafety (from `../../../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../../../node_modules/react-native/`)
- React-callinvoker (from `../../../node_modules/react-native/ReactCommon/callinvoker`)
- React-Core (from `../../../node_modules/react-native/`)
- React-Core/RCTWebSocket (from `../../../node_modules/react-native/`)
- React-CoreModules (from `../../../node_modules/react-native/React/CoreModules`)
- React-cxxreact (from `../../../node_modules/react-native/ReactCommon/cxxreact`)
- React-debug (from `../../../node_modules/react-native/ReactCommon/react/debug`)
- React-defaultsnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/defaults`)
- React-domnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/dom`)
- React-Fabric (from `../../../node_modules/react-native/ReactCommon`)
- React-FabricComponents (from `../../../node_modules/react-native/ReactCommon`)
- React-FabricImage (from `../../../node_modules/react-native/ReactCommon`)
- React-featureflags (from `../../../node_modules/react-native/ReactCommon/react/featureflags`)
- React-featureflagsnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`)
- React-graphics (from `../../../node_modules/react-native/ReactCommon/react/renderer/graphics`)
- React-hermes (from `../../../node_modules/react-native/ReactCommon/hermes`)
- React-idlecallbacksnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`)
- React-ImageManager (from `../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`)
- React-jserrorhandler (from `../../../node_modules/react-native/ReactCommon/jserrorhandler`)
- React-jsi (from `../../../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../../../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../../../node_modules/react-native/ReactCommon/jsinspector-modern`)
- React-jsinspectorcdp (from `../../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`)
- React-jsinspectornetwork (from `../../../node_modules/react-native/ReactCommon/jsinspector-modern/network`)
- React-jsinspectortracing (from `../../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`)
- React-jsitooling (from `../../../node_modules/react-native/ReactCommon/jsitooling`)
- React-jsitracing (from `../../../node_modules/react-native/ReactCommon/hermes/executor/`)
- React-logger (from `../../../node_modules/react-native/ReactCommon/logger`)
- React-Mapbuffer (from `../../../node_modules/react-native/ReactCommon`)
- React-microtasksnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
- RCTRequired (from `../node_modules/react-native/Libraries/Required`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`)
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
- React-Core (from `../node_modules/react-native/`)
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
- React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
- React-debug (from `../node_modules/react-native/ReactCommon/react/debug`)
- React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`)
- React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`)
- React-Fabric (from `../node_modules/react-native/ReactCommon`)
- React-FabricComponents (from `../node_modules/react-native/ReactCommon`)
- React-FabricImage (from `../node_modules/react-native/ReactCommon`)
- React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`)
- React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`)
- React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`)
- React-hermes (from `../node_modules/react-native/ReactCommon/hermes`)
- React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`)
- React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`)
- React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`)
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`)
- React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`)
- React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`)
- React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`)
- React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`)
- React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
- react-native-get-random-values (from `../../../node_modules/react-native-get-random-values`)
- react-native-mmkv (from `../../../node_modules/react-native-mmkv`)
- "react-native-netinfo (from `../../../node_modules/@react-native-community/netinfo`)"
- react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`)
- React-NativeModulesApple (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-oscompat (from `../../../node_modules/react-native/ReactCommon/oscompat`)
- React-perflogger (from `../../../node_modules/react-native/ReactCommon/reactperflogger`)
- React-performancetimeline (from `../../../node_modules/react-native/ReactCommon/react/performance/timeline`)
- React-RCTActionSheet (from `../../../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../../../node_modules/react-native/Libraries/NativeAnimation`)
- React-RCTAppDelegate (from `../../../node_modules/react-native/Libraries/AppDelegate`)
- React-RCTBlob (from `../../../node_modules/react-native/Libraries/Blob`)
- React-RCTFabric (from `../../../node_modules/react-native/React`)
- React-RCTFBReactNativeSpec (from `../../../node_modules/react-native/React`)
- React-RCTImage (from `../../../node_modules/react-native/Libraries/Image`)
- React-RCTLinking (from `../../../node_modules/react-native/Libraries/LinkingIOS`)
- React-RCTNetwork (from `../../../node_modules/react-native/Libraries/Network`)
- React-RCTRuntime (from `../../../node_modules/react-native/React/Runtime`)
- React-RCTSettings (from `../../../node_modules/react-native/Libraries/Settings`)
- React-RCTText (from `../../../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../../../node_modules/react-native/Libraries/Vibration`)
- React-rendererconsistency (from `../../../node_modules/react-native/ReactCommon/react/renderer/consistency`)
- React-renderercss (from `../../../node_modules/react-native/ReactCommon/react/renderer/css`)
- React-rendererdebug (from `../../../node_modules/react-native/ReactCommon/react/renderer/debug`)
- React-rncore (from `../../../node_modules/react-native/ReactCommon`)
- React-RuntimeApple (from `../../../node_modules/react-native/ReactCommon/react/runtime/platform/ios`)
- React-RuntimeCore (from `../../../node_modules/react-native/ReactCommon/react/runtime`)
- React-runtimeexecutor (from `../../../node_modules/react-native/ReactCommon/runtimeexecutor`)
- React-RuntimeHermes (from `../../../node_modules/react-native/ReactCommon/react/runtime`)
- React-runtimescheduler (from `../../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
- React-timing (from `../../../node_modules/react-native/ReactCommon/react/timing`)
- React-utils (from `../../../node_modules/react-native/ReactCommon/react/utils`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
- React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`)
- React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`)
- React-RCTFabric (from `../node_modules/react-native/React`)
- React-RCTFBReactNativeSpec (from `../node_modules/react-native/React`)
- React-RCTImage (from `../node_modules/react-native/Libraries/Image`)
- React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`)
- React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`)
- React-RCTRuntime (from `../node_modules/react-native/React/Runtime`)
- React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`)
- React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`)
- React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`)
- React-rncore (from `../node_modules/react-native/ReactCommon`)
- React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`)
- React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`)
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`)
- React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
- React-timing (from `../node_modules/react-native/ReactCommon/react/timing`)
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactAppDependencyProvider (from `build/generated/ios`)
- ReactCodegen (from `build/generated/ios`)
- ReactCommon/turbomodule/core (from `../../../node_modules/react-native/ReactCommon`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "RNCClipboard (from `../../../node_modules/@react-native-clipboard/clipboard`)"
- RNScreens (from `../../../node_modules/react-native-screens`)
- RNScreens (from `../node_modules/react-native-screens`)
- SocketRocket (~> 0.7.1)
- Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
trunk:
@@ -2458,88 +2458,88 @@ SPEC REPOS:
EXTERNAL SOURCES:
boost:
:podspec: "../../../node_modules/react-native/third-party-podspecs/boost.podspec"
:podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec"
DoubleConversion:
:podspec: "../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
fast_float:
:podspec: "../../../node_modules/react-native/third-party-podspecs/fast_float.podspec"
:podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec"
FBLazyVector:
:path: "../../../node_modules/react-native/Libraries/FBLazyVector"
:path: "../node_modules/react-native/Libraries/FBLazyVector"
fmt:
:podspec: "../../../node_modules/react-native/third-party-podspecs/fmt.podspec"
:podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec"
glog:
:podspec: "../../../node_modules/react-native/third-party-podspecs/glog.podspec"
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
hermes-engine:
:podspec: "../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:tag: hermes-2025-05-06-RNv0.80.0-4eb6132a5bf0450bf4c6c91987675381d7ac8bca
op-sqlite:
:path: "../../../node_modules/@op-engineering/op-sqlite"
RCT-Folly:
:podspec: "../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
RCTDeprecation:
:path: "../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation"
:path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation"
RCTRequired:
:path: "../../../node_modules/react-native/Libraries/Required"
:path: "../node_modules/react-native/Libraries/Required"
RCTTypeSafety:
:path: "../../../node_modules/react-native/Libraries/TypeSafety"
:path: "../node_modules/react-native/Libraries/TypeSafety"
React:
:path: "../../../node_modules/react-native/"
:path: "../node_modules/react-native/"
React-callinvoker:
:path: "../../../node_modules/react-native/ReactCommon/callinvoker"
:path: "../node_modules/react-native/ReactCommon/callinvoker"
React-Core:
:path: "../../../node_modules/react-native/"
:path: "../node_modules/react-native/"
React-CoreModules:
:path: "../../../node_modules/react-native/React/CoreModules"
:path: "../node_modules/react-native/React/CoreModules"
React-cxxreact:
:path: "../../../node_modules/react-native/ReactCommon/cxxreact"
:path: "../node_modules/react-native/ReactCommon/cxxreact"
React-debug:
:path: "../../../node_modules/react-native/ReactCommon/react/debug"
:path: "../node_modules/react-native/ReactCommon/react/debug"
React-defaultsnativemodule:
:path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/defaults"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults"
React-domnativemodule:
:path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/dom"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom"
React-Fabric:
:path: "../../../node_modules/react-native/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
React-FabricComponents:
:path: "../../../node_modules/react-native/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
React-FabricImage:
:path: "../../../node_modules/react-native/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
React-featureflags:
:path: "../../../node_modules/react-native/ReactCommon/react/featureflags"
:path: "../node_modules/react-native/ReactCommon/react/featureflags"
React-featureflagsnativemodule:
:path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags"
React-graphics:
:path: "../../../node_modules/react-native/ReactCommon/react/renderer/graphics"
:path: "../node_modules/react-native/ReactCommon/react/renderer/graphics"
React-hermes:
:path: "../../../node_modules/react-native/ReactCommon/hermes"
:path: "../node_modules/react-native/ReactCommon/hermes"
React-idlecallbacksnativemodule:
:path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks"
React-ImageManager:
:path: "../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios"
:path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios"
React-jserrorhandler:
:path: "../../../node_modules/react-native/ReactCommon/jserrorhandler"
:path: "../node_modules/react-native/ReactCommon/jserrorhandler"
React-jsi:
:path: "../../../node_modules/react-native/ReactCommon/jsi"
:path: "../node_modules/react-native/ReactCommon/jsi"
React-jsiexecutor:
:path: "../../../node_modules/react-native/ReactCommon/jsiexecutor"
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector:
:path: "../../../node_modules/react-native/ReactCommon/jsinspector-modern"
:path: "../node_modules/react-native/ReactCommon/jsinspector-modern"
React-jsinspectorcdp:
:path: "../../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp"
:path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp"
React-jsinspectornetwork:
:path: "../../../node_modules/react-native/ReactCommon/jsinspector-modern/network"
:path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network"
React-jsinspectortracing:
:path: "../../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing"
:path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing"
React-jsitooling:
:path: "../../../node_modules/react-native/ReactCommon/jsitooling"
:path: "../node_modules/react-native/ReactCommon/jsitooling"
React-jsitracing:
:path: "../../../node_modules/react-native/ReactCommon/hermes/executor/"
:path: "../node_modules/react-native/ReactCommon/hermes/executor/"
React-logger:
:path: "../../../node_modules/react-native/ReactCommon/logger"
:path: "../node_modules/react-native/ReactCommon/logger"
React-Mapbuffer:
:path: "../../../node_modules/react-native/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
React-microtasksnativemodule:
:path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
react-native-get-random-values:
:path: "../../../node_modules/react-native-get-random-values"
react-native-mmkv:
@@ -2547,75 +2547,75 @@ EXTERNAL SOURCES:
react-native-netinfo:
:path: "../../../node_modules/@react-native-community/netinfo"
react-native-safe-area-context:
:path: "../../../node_modules/react-native-safe-area-context"
:path: "../node_modules/react-native-safe-area-context"
React-NativeModulesApple:
:path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
React-oscompat:
:path: "../../../node_modules/react-native/ReactCommon/oscompat"
:path: "../node_modules/react-native/ReactCommon/oscompat"
React-perflogger:
:path: "../../../node_modules/react-native/ReactCommon/reactperflogger"
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
React-performancetimeline:
:path: "../../../node_modules/react-native/ReactCommon/react/performance/timeline"
:path: "../node_modules/react-native/ReactCommon/react/performance/timeline"
React-RCTActionSheet:
:path: "../../../node_modules/react-native/Libraries/ActionSheetIOS"
:path: "../node_modules/react-native/Libraries/ActionSheetIOS"
React-RCTAnimation:
:path: "../../../node_modules/react-native/Libraries/NativeAnimation"
:path: "../node_modules/react-native/Libraries/NativeAnimation"
React-RCTAppDelegate:
:path: "../../../node_modules/react-native/Libraries/AppDelegate"
:path: "../node_modules/react-native/Libraries/AppDelegate"
React-RCTBlob:
:path: "../../../node_modules/react-native/Libraries/Blob"
:path: "../node_modules/react-native/Libraries/Blob"
React-RCTFabric:
:path: "../../../node_modules/react-native/React"
:path: "../node_modules/react-native/React"
React-RCTFBReactNativeSpec:
:path: "../../../node_modules/react-native/React"
:path: "../node_modules/react-native/React"
React-RCTImage:
:path: "../../../node_modules/react-native/Libraries/Image"
:path: "../node_modules/react-native/Libraries/Image"
React-RCTLinking:
:path: "../../../node_modules/react-native/Libraries/LinkingIOS"
:path: "../node_modules/react-native/Libraries/LinkingIOS"
React-RCTNetwork:
:path: "../../../node_modules/react-native/Libraries/Network"
:path: "../node_modules/react-native/Libraries/Network"
React-RCTRuntime:
:path: "../../../node_modules/react-native/React/Runtime"
:path: "../node_modules/react-native/React/Runtime"
React-RCTSettings:
:path: "../../../node_modules/react-native/Libraries/Settings"
:path: "../node_modules/react-native/Libraries/Settings"
React-RCTText:
:path: "../../../node_modules/react-native/Libraries/Text"
:path: "../node_modules/react-native/Libraries/Text"
React-RCTVibration:
:path: "../../../node_modules/react-native/Libraries/Vibration"
:path: "../node_modules/react-native/Libraries/Vibration"
React-rendererconsistency:
:path: "../../../node_modules/react-native/ReactCommon/react/renderer/consistency"
:path: "../node_modules/react-native/ReactCommon/react/renderer/consistency"
React-renderercss:
:path: "../../../node_modules/react-native/ReactCommon/react/renderer/css"
:path: "../node_modules/react-native/ReactCommon/react/renderer/css"
React-rendererdebug:
:path: "../../../node_modules/react-native/ReactCommon/react/renderer/debug"
:path: "../node_modules/react-native/ReactCommon/react/renderer/debug"
React-rncore:
:path: "../../../node_modules/react-native/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
React-RuntimeApple:
:path: "../../../node_modules/react-native/ReactCommon/react/runtime/platform/ios"
:path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios"
React-RuntimeCore:
:path: "../../../node_modules/react-native/ReactCommon/react/runtime"
:path: "../node_modules/react-native/ReactCommon/react/runtime"
React-runtimeexecutor:
:path: "../../../node_modules/react-native/ReactCommon/runtimeexecutor"
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
React-RuntimeHermes:
:path: "../../../node_modules/react-native/ReactCommon/react/runtime"
:path: "../node_modules/react-native/ReactCommon/react/runtime"
React-runtimescheduler:
:path: "../../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler"
:path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler"
React-timing:
:path: "../../../node_modules/react-native/ReactCommon/react/timing"
:path: "../node_modules/react-native/ReactCommon/react/timing"
React-utils:
:path: "../../../node_modules/react-native/ReactCommon/react/utils"
:path: "../node_modules/react-native/ReactCommon/react/utils"
ReactAppDependencyProvider:
:path: build/generated/ios
ReactCodegen:
:path: build/generated/ios
ReactCommon:
:path: "../../../node_modules/react-native/ReactCommon"
:path: "../node_modules/react-native/ReactCommon"
RNCClipboard:
:path: "../../../node_modules/@react-native-clipboard/clipboard"
RNScreens:
:path: "../../../node_modules/react-native-screens"
:path: "../node_modules/react-native-screens"
Yoga:
:path: "../../../node_modules/react-native/ReactCommon/yoga"
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
@@ -2692,7 +2692,7 @@ SPEC CHECKSUMS:
React-timing: a275a1c2e6112dba17f8f7dd496d439213bbea0d
React-utils: 449a6e1fd53886510e284e80bdbb1b1c6db29452
ReactAppDependencyProvider: 3267432b637c9b38e86961b287f784ee1b08dde0
ReactCodegen: d82f538f70f00484d418803f74b5a0ea09cc8689
ReactCodegen: 5d41e1df061200130dd326e55cdfdf94b0289c6e
ReactCommon: b028d09a66e60ebd83ca59d8cc9a1216360db147
RNCClipboard: 54ff19965d7c816febbafe5f520c2c3e7b677a49
RNScreens: ee2abe7e0c548eed14e92742e81ed991165c56aa

View File

@@ -13,7 +13,7 @@
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@bacons/text-decoder": "0.0.0",
"@op-engineering/op-sqlite": "14.1.0",
"@react-native-clipboard/clipboard": "1.16.3",
"@react-native-clipboard/clipboard": "1.16.2",
"@react-native-community/netinfo": "11.4.1",
"@react-navigation/native": "7.1.14",
"@react-navigation/native-stack": "7.3.19",
@@ -40,7 +40,7 @@
"@react-native/typescript-config": "0.80.0",
"@rnx-kit/metro-config": "^2.0.1",
"@rnx-kit/metro-resolver-symlinks": "^0.2.5",
"@types/react": "^19.1.0",
"@types/react": "19.1.0",
"eslint": "^8.19.0",
"pod-install": "^0.3.5",
"prettier": "2.8.8",

View File

@@ -1,72 +1,5 @@
# passkey-svelte
## 0.0.102
### Patch Changes
- Updated dependencies [6c76ff8]
- jazz-tools@0.15.13
## 0.0.101
### Patch Changes
- Updated dependencies [d1c1b0c]
- Updated dependencies [cf4ad72]
- jazz-tools@0.15.12
## 0.0.100
### Patch Changes
- Updated dependencies [bdc9aee]
- jazz-tools@0.15.11
## 0.0.99
### Patch Changes
- Updated dependencies [9815ec6]
- Updated dependencies [b4fdab4]
- jazz-tools@0.15.10
## 0.0.98
### Patch Changes
- Updated dependencies [27b4837]
- jazz-tools@0.15.9
## 0.0.97
### Patch Changes
- Updated dependencies [3844666]
- jazz-tools@0.15.8
## 0.0.96
### Patch Changes
- Updated dependencies [c09b636]
- jazz-tools@0.15.7
## 0.0.95
### Patch Changes
- Updated dependencies [a5ceaff]
- jazz-tools@0.15.6
## 0.0.94
### Patch Changes
- Updated dependencies [23bfea5]
- Updated dependencies [e4ba23c]
- Updated dependencies [4b89838]
- jazz-tools@0.15.5
## 0.0.93
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-svelte",
"version": "0.0.102",
"version": "0.0.93",
"type": "module",
"private": true,
"scripts": {

View File

@@ -16,15 +16,15 @@
"hash-slash": "workspace:*",
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"zod": "3.25.28"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react-swc": "^3.10.1",
"is-ci": "^3.0.1",
"postcss": "^8.4.40",
@@ -32,4 +32,4 @@
"typescript": "5.6.2",
"vite": "^6.3.5"
}
}
}

View File

@@ -1,8 +1,8 @@
import { co } from "jazz-tools";
import { co, z } from "jazz-tools";
export const Message = co.map({
text: co.plainText(),
image: co.optional(co.image()),
image: z.optional(co.image()),
});
export type Message = co.loaded<typeof Message>;

View File

@@ -14,15 +14,15 @@
"@bam.tech/react-native-image-resizer": "^3.0.11",
"@clerk/clerk-expo": "^2.13.1",
"@react-native-community/netinfo": "11.4.1",
"expo": "54.0.0-canary-20250701-6a945c5",
"expo": "~53.0.9",
"expo-crypto": "~14.1.5",
"expo-linking": "~7.1.5",
"expo-secure-store": "~14.2.3",
"expo-sqlite": "~15.2.10",
"expo-web-browser": "~14.2.0",
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-native": "0.80.0",
"react": "19.0.0",
"react-native": "0.79.2",
"react-native-get-random-values": "^1.11.0",
"readable-stream": "^4.7.0"
},
@@ -32,4 +32,4 @@
"typescript": "~5.8.3"
},
"private": true
}
}

View File

@@ -14,17 +14,17 @@
"dependencies": {
"@clerk/clerk-react": "^5.4.1",
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0"
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@biomejs/biome": "1.9.4",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"typescript": "5.6.2",
"vite": "^6.3.5"
}
}
}

View File

@@ -11,14 +11,14 @@
},
"dependencies": {
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0"
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"is-ci": "^3.0.1",

View File

@@ -1,7 +1,7 @@
import { co } from "jazz-tools";
import { co, z } from "jazz-tools";
export const JazzProfile = co.profile({
file: co.optional(co.fileStream()),
file: z.optional(co.fileStream()),
});
export const JazzAccount = co.account({

View File

@@ -10,18 +10,19 @@
"format-and-lint:fix": "biome check . --write"
},
"dependencies": {
"@tanstack/react-form": "^0.33.0",
"hash-slash": "workspace:*",
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0"
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@playwright/test": "^1.50.1",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"is-ci": "^3.0.1",
@@ -30,4 +31,4 @@
"typescript": "5.6.2",
"vite": "^6.3.5"
}
}
}

View File

@@ -1,11 +1,13 @@
import { useCoState } from "jazz-tools/react";
import { LinkToHome } from "./LinkToHome.tsx";
import { OrderForm } from "./OrderForm.tsx";
import { OrderFormWithSaveButton } from "./OrderFormWithSaveButton.tsx";
import { OrderThumbnail } from "./OrderThumbnail.tsx";
import { BubbleTeaOrder } from "./schema.ts";
export function EditOrder(props: { id: string }) {
const order = useCoState(BubbleTeaOrder, props.id);
const order = useCoState(BubbleTeaOrder, props.id, {
resolve: { addOns: true, instructions: true },
});
if (!order) return;
@@ -13,13 +15,17 @@ export function EditOrder(props: { id: string }) {
<>
<LinkToHome />
<OrderThumbnail order={order} />
<div>
<p>Saved order:</p>
<OrderThumbnail order={order} />
</div>
<h1 className="text-lg">
<strong>Edit your bubble tea order 🧋</strong>
</h1>
<OrderForm order={order} />
<OrderFormWithSaveButton order={order} />
</>
);
}

View File

@@ -0,0 +1,214 @@
import { CoPlainText, Loaded } from "jazz-tools";
import { useForm } from "@tanstack/react-form";
import {
BubbleTeaAddOnTypes,
BubbleTeaBaseTeaTypes,
BubbleTeaOrder,
} from "./schema.ts";
import { OrderThumbnail } from "./OrderThumbnail.tsx";
type LoadedBubbleTeaOrder = Loaded<
typeof BubbleTeaOrder,
{ addOns: { $each: true }; instructions: true }
>;
// Would be great to derive this type from the CoValue schema
export type OrderFormData = {
id: string;
baseTea: (typeof BubbleTeaBaseTeaTypes)[number];
addOns: (typeof BubbleTeaAddOnTypes)[number][];
deliveryDate: Date;
withMilk: boolean;
instructions?: string;
};
export function OrderFormWithSaveButton({
order: originalOrder,
}: {
order: LoadedBubbleTeaOrder;
}) {
const defaultValues = originalOrder.toJSON();
// Convert timestamp to Date
defaultValues.deliveryDate = new Date(defaultValues.deliveryDate);
const form = useForm<OrderFormData>({
defaultValues,
onSubmit: async ({ value }: { value: OrderFormData }) => {
console.log("submit form", value);
// Apply changes to the original Jazz order
originalOrder.baseTea = value.baseTea;
originalOrder.addOns.applyDiff(value.addOns);
originalOrder.deliveryDate = new Date(value.deliveryDate);
originalOrder.withMilk = value.withMilk;
// `applyDiff` requires nested objects to be CoValues as well
const instructions = originalOrder.instructions ?? CoPlainText.create("");
if (value.instructions) {
instructions.applyDiff(value.instructions);
}
},
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
className="grid gap-5"
>
{/* TODO refactor OrderThumbnail to support receiving a plain JSON object */}
<div>
<p>Unsaved order preview:</p>
<form.Subscribe
selector={(state) => [state.values]}
children={([values]) => <OrderThumbnail order={values} />}
/>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="baseTea">Base tea</label>
<form.Field
name="baseTea"
validators={{
onChange: ({ value }) =>
!value ? "Please select your preferred base tea" : undefined,
}}
children={(field) => (
<>
<select
id="baseTea"
className="dark:bg-transparent"
value={field.state.value}
onChange={(e) =>
field.handleChange(e.target.value as typeof field.state.value)
}
onBlur={field.handleBlur}
>
<option value="" disabled>
Please select your preferred base tea
</option>
{BubbleTeaBaseTeaTypes.map((teaType) => (
<option key={teaType} value={teaType}>
{teaType}
</option>
))}
</select>
{field.state.meta.errors.length > 0 && (
<span className="text-red-500 text-sm">
{field.state.meta.errors[0]}
</span>
)}
</>
)}
/>
</div>
<fieldset>
<legend className="mb-2">Add-ons</legend>
<form.Field
name="addOns"
mode="array"
children={(field) => (
<>
{BubbleTeaAddOnTypes.map((addOn) => (
<div key={addOn} className="flex items-center gap-2">
<input
type="checkbox"
id={addOn}
checked={field.state.value.includes(addOn)}
onChange={(e) => {
const currentValue = field.state.value;
if (e.target.checked) {
field.handleChange([...currentValue, addOn]);
} else {
field.handleChange(
currentValue.filter((item) => item !== addOn),
);
}
}}
/>
<label htmlFor={addOn}>{addOn}</label>
</div>
))}
</>
)}
/>
</fieldset>
<div className="flex flex-col gap-2">
<label htmlFor="deliveryDate">Delivery date</label>
<form.Field
name="deliveryDate"
validators={{
onChange: ({ value }) =>
!value ? "Delivery date is required" : undefined,
}}
children={(field) => (
<>
<input
type="date"
id="deliveryDate"
className="dark:bg-transparent"
value={field.state.value.toISOString().split("T")[0]}
onChange={(e) => field.handleChange(new Date(e.target.value))}
onBlur={field.handleBlur}
/>
{field.state.meta.errors.length > 0 && (
<span className="text-red-500 text-sm">
{field.state.meta.errors[0]}
</span>
)}
</>
)}
/>
</div>
<div className="flex items-center gap-2">
<form.Field
name="withMilk"
children={(field) => (
<input
type="checkbox"
id="withMilk"
checked={field.state.value}
onChange={(e) => field.handleChange(e.target.checked)}
/>
)}
/>
<label htmlFor="withMilk">With milk?</label>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="instructions">Special instructions</label>
<form.Field
name="instructions"
children={(field) => (
<textarea
id="instructions"
className="dark:bg-transparent"
value={field.state.value || ""}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
/>
)}
/>
</div>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button
type="submit"
disabled={!canSubmit}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:bg-gray-400"
>
{isSubmitting ? "Submitting..." : "Submit"}
</button>
)}
/>
</form>
);
}

View File

@@ -1,10 +1,11 @@
import { Loaded } from "jazz-tools";
import { BubbleTeaOrder } from "./schema.ts";
import { OrderFormData } from "./OrderFormWithSaveButton.tsx";
export function OrderThumbnail({
order,
}: {
order: Loaded<typeof BubbleTeaOrder>;
order: Loaded<typeof BubbleTeaOrder> | OrderFormData;
}) {
const { id, baseTea, addOns, instructions, deliveryDate, withMilk } = order;
const date = deliveryDate.toLocaleDateString();

View File

@@ -28,16 +28,16 @@ export const BubbleTeaOrder = co.map({
addOns: ListOfBubbleTeaAddOns,
deliveryDate: z.date(),
withMilk: z.boolean(),
instructions: co.optional(co.plainText()),
instructions: z.optional(co.plainText()),
});
export const DraftBubbleTeaOrder = co
.map({
baseTea: z.optional(z.literal([...BubbleTeaBaseTeaTypes])),
addOns: co.optional(ListOfBubbleTeaAddOns),
addOns: z.optional(ListOfBubbleTeaAddOns),
deliveryDate: z.optional(z.date()),
withMilk: z.optional(z.boolean()),
instructions: co.optional(co.plainText()),
instructions: z.optional(co.plainText()),
})
.withHelpers((Self) => ({
hasChanges(order: Loaded<typeof Self> | undefined) {

View File

@@ -11,14 +11,14 @@
},
"dependencies": {
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0"
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"typescript": "5.6.2",

View File

@@ -1,7 +1,7 @@
import { co } from "jazz-tools";
import { co, z } from "jazz-tools";
export const JazzProfile = co.profile({
image: co.optional(co.image()),
image: z.optional(co.image()),
});
export const JazzAccount = co.account({

View File

@@ -17,15 +17,15 @@
"cojson-transport-ws": "workspace:*",
"hash-slash": "workspace:*",
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-use": "^17.4.0"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react-swc": "^3.10.1",
"postcss": "^8.4.40",
"tailwindcss": "^4.1.10",

View File

@@ -10,8 +10,8 @@
"dependencies": {
"jazz-tools": "workspace:*",
"next": "15.3.2",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
@@ -21,4 +21,4 @@
"tailwindcss": "^4.1.10",
"typescript": "^5"
}
}
}

View File

@@ -24,15 +24,15 @@
"clsx": "^2.1.1",
"jazz-tools": "workspace:*",
"lucide-react": "^0.485.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.17",
"tw-animate-css": "^1.2.5"
},
"devDependencies": {
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.3.4",
"jazz-run": "workspace:*",
"npm-run-all": "^4.1.5",

View File

@@ -8,7 +8,7 @@ export type Player = co.loaded<typeof Player>;
export const Game = co.map({
player1: Player,
player2: co.optional(Player),
player2: z.optional(Player),
outcome: z.optional(z.literal(["player1", "player2", "draw"])),
player1Score: z.number(),
player2Score: z.number(),
@@ -17,8 +17,8 @@ export type Game = co.loaded<typeof Game>;
export const WaitingRoom = co.map({
account1: co.account(),
account2: co.optional(co.account()),
game: co.optional(Game),
account2: z.optional(co.account()),
game: z.optional(Game),
});
export type WaitingRoom = co.loaded<typeof WaitingRoom>;
@@ -47,7 +47,7 @@ export const JoinGameRequest = co.map({
});
export type JoinGameRequest = co.loaded<typeof JoinGameRequest>;
export const InboxMessage = co.discriminatedUnion("type", [
export const InboxMessage = z.discriminatedUnion("type", [
PlayIntent,
NewGameIntent,
CreateGameRequest,

View File

@@ -13,14 +13,14 @@
"dependencies": {
"@react-spring/web": "^9.7.5",
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"zod": "3.25.28"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"is-ci": "^3.0.1",

View File

@@ -12,14 +12,14 @@
"dependencies": {
"@clerk/clerk-react": "^5.4.1",
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"tailwindcss": "^4.1.10"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"typescript": "5.6.2",

View File

@@ -23,8 +23,8 @@
"clsx": "^2.1.1",
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"tailwind-merge": "^1.14.0"
@@ -32,8 +32,8 @@
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react-swc": "^3.10.1",
"postcss": "^8.4.27",
"tailwindcss": "^4.1.10",

View File

@@ -66,7 +66,7 @@ export const MusicaAccountRoot = co.map({
// track and playlist
// You can also add the position in time if you want make it possible
// to resume the song
activeTrack: co.optional(MusicTrack),
activeTrack: z.optional(MusicTrack),
activePlaylist: Playlist,
exampleDataLoaded: z.optional(z.boolean()),

View File

@@ -14,8 +14,8 @@
"dependencies": {
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0"
},
@@ -24,8 +24,8 @@
"@playwright/test": "^1.50.1",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"postcss": "^8.4.40",

View File

@@ -11,14 +11,14 @@
},
"dependencies": {
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"tailwindcss": "^4.1.10"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"typescript": "5.6.2",

View File

@@ -12,14 +12,14 @@
"dependencies": {
"hash-slash": "workspace:*",
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"tailwindcss": "^4.1.10"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"typescript": "5.6.2",

View File

@@ -19,15 +19,15 @@
"prosemirror-schema-list": "^1.5.1",
"prosemirror-state": "^1.4.3",
"prosemirror-view": "^1.39.1",
"react": "19.1.0",
"react-dom": "19.1.0"
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"is-ci": "^3.0.1",

View File

@@ -22,15 +22,15 @@
"clsx": "^2.1.1",
"jazz-tools": "workspace:*",
"lucide-react": "^0.509.0",
"react": "19.1.0",
"react-dom": "19.1.0"
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"is-ci": "^3.0.1",

View File

@@ -1,3 +0,0 @@
{
"ignoreCommand": "echo true"
}

View File

@@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@@ -0,0 +1,35 @@
# Changelog
## v0.1.0 - Initial Svelte Migration
### Added
- Complete Svelte implementation of the form example
- Native Svelte reactive state management (replacing TanStack Form)
- SvelteKit routing with hash router
- Tailwind CSS styling
- Jazz tools integration for Svelte
### Changes from React Version
- **Form Management**: Replaced TanStack Form with native Svelte `$state` and `$derived` reactives
- **Components**: Converted all React components to Svelte components
- **Event Handling**: Used Svelte's native event handling instead of React event handlers
- **State Management**: Leveraged Svelte's built-in reactivity instead of external form libraries
- **Validation**: Implemented custom validation using Svelte reactive patterns
### Features
- ✅ Create new bubble tea orders
- ✅ Edit existing orders
- ✅ Real-time form preview
- ✅ Form validation with error messages
- ✅ Date input handling with proper format conversion
- ✅ Multiple selection for add-ons
- ✅ Jazz CoValue synchronization
- ✅ Responsive design with Tailwind CSS
### Technical Implementation
- **Framework**: SvelteKit 2.x with Svelte 5.x
- **State**: Native Svelte `$state` runes for reactive state
- **Derived State**: `$derived` for computed values like form validity
- **Effects**: `$effect` for side effects and lifecycle management
- **TypeScript**: Full type safety maintained
- **Styling**: Tailwind CSS for consistent design

View File

@@ -0,0 +1,126 @@
# Jazz Form Example - Svelte
A comprehensive form example built with Svelte and Jazz, demonstrating reactive form handling, validation, and real-time data synchronization.
## Overview
This example showcases a bubble tea ordering system with the following features:
- **Create Orders**: Build new bubble tea orders with base tea, add-ons, delivery date, and special instructions
- **Edit Orders**: Modify existing orders with real-time preview
- **Form Validation**: Client-side validation with error messages
- **Real-time Sync**: Jazz CoValues for real-time data synchronization
- **Responsive Design**: Modern UI with Tailwind CSS
## Key Technologies
- **Svelte 5.x**: Modern reactive UI framework with runes
- **SvelteKit**: Full-stack Svelte framework
- **Jazz Tools**: Real-time collaborative data layer
- **TypeScript**: Full type safety
- **Tailwind CSS**: Utility-first CSS framework
## Architecture
### State Management
Instead of external form libraries, this example uses Svelte's native reactivity:
- `$state` - For mutable reactive state
- `$derived` - For computed values (like form validity)
- `$effect` - For side effects and lifecycle management
### Form Pattern
```svelte
<script>
// Reactive form state
let formData = $state({
baseTea: '',
addOns: [],
deliveryDate: new Date(),
withMilk: false,
instructions: ''
});
// Derived validation
const isValid = $derived(
formData.baseTea && formData.deliveryDate
);
// Event handlers
function handleSubmit(event) {
// Handle form submission
}
</script>
```
### Jazz Integration
The example demonstrates Jazz CoValues for:
- **Account Management**: User authentication and profile
- **Data Persistence**: Bubble tea orders stored as CoMaps
- **Real-time Sync**: Changes sync across sessions
- **Collaborative Features**: Shared data structures
## Getting Started
```bash
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Build for production
pnpm build
```
## Project Structure
```
src/
├── lib/
│ ├── components/
│ │ ├── CreateOrder.svelte # New order creation
│ │ ├── EditOrder.svelte # Edit existing orders
│ │ ├── Orders.svelte # Order list view
│ │ ├── OrderFormWithSaveButton.svelte # Main form component
│ │ ├── OrderThumbnail.svelte # Order preview
│ │ └── LinkToHome.svelte # Navigation
│ ├── schema.ts # Jazz CoValue definitions
│ └── apiKey.ts # Jazz API configuration
├── routes/
│ ├── +layout.svelte # App layout with Jazz provider
│ └── +page.svelte # Main app with routing
└── app.html # HTML template
```
## Migration from React
This Svelte version was migrated from a React + TanStack Form implementation. Key differences:
| React | Svelte |
|-------|--------|
| `useForm` hook | `$state` runes |
| `form.Field` components | Native form elements |
| `form.Subscribe` | `$derived` reactives |
| Event handlers via props | Native Svelte event handling |
| TanStack Form validation | Custom validation functions |
The Svelte implementation is more concise while maintaining the same functionality and type safety.
## Features Demonstrated
-**Form State Management**: Reactive state with validation
-**Real-time Preview**: Live updates as user types
-**Date Handling**: Proper Date ↔ string conversion for inputs
-**Array Fields**: Dynamic add-on selection
-**Error Handling**: Field-level validation messages
-**Submit State**: Loading states and form submission
-**Jazz Integration**: CoValue creation and updates
-**TypeScript**: Full type safety throughout
## Learn More
- [Svelte Documentation](https://svelte.dev/docs)
- [SvelteKit Documentation](https://kit.svelte.dev/docs)
- [Jazz Documentation](https://jazz.tools/docs)
- [Tailwind CSS](https://tailwindcss.com/docs)

View File

@@ -0,0 +1,32 @@
import eslint from "@eslint/js";
import prettier from "eslint-config-prettier";
import svelte from "eslint-plugin-svelte";
import globals from "globals";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
...svelte.configs["flat/recommended"],
prettier,
...svelte.configs["flat/prettier"],
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
},
{
files: ["**/*.svelte"],
languageOptions: {
parserOptions: {
parser: tseslint.parser,
},
},
},
{
ignores: ["build/", ".svelte-kit/", "dist/"],
},
);

View File

@@ -0,0 +1,43 @@
{
"name": "tanstack-form",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "pnpm run check && vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"format-and-lint": "pnpm run format && pnpm run lint",
"format-and-lint:fix": "pnpm run format --write && pnpm run lint --fix",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.3.1",
"@sveltejs/kit": "^2.21.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tailwindcss/postcss": "^4.1.10",
"@types/eslint": "^9.6.1",
"eslint": "^9.27.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.15.0",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.31.1",
"svelte-check": "^4.2.1",
"tailwindcss": "^4.1.7",
"typescript": "5.6.2",
"typescript-eslint": "^8.32.1",
"vite": "6.0.11"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.7",
"@tanstack/svelte-form": "^1.12.4",
"jazz-tools": "workspace:*",
"tailwindcss": "^4.1.7"
}
}

View File

@@ -0,0 +1 @@
@import 'tailwindcss';

13
examples/tanstack-form/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1 @@
export const apiKey = 'tanstack-form-example_bYRMYEZmBNwE1qKQ';

View File

@@ -0,0 +1,50 @@
<script lang="ts">
import { AccountCoState } from 'jazz-tools/svelte';
import { JazzAccount, BubbleTeaOrder, BubbleTeaBaseTeaTypes, ListOfBubbleTeaAddOns } from '$lib/schema';
import OrderFormWithSaveButton from './OrderFormWithSaveButton.svelte';
import LinkToHome from './LinkToHome.svelte';
import { CoPlainText } from 'jazz-tools';
const me = new AccountCoState(JazzAccount, {
resolve: {
profile: true,
root: {
orders: true
}
}
});
const orders = $derived(me.current?.root.orders);
// Create a new order when component loads
let newOrder = $state<any>(null);
$effect(() => {
if (orders && !newOrder) {
const order = BubbleTeaOrder.create({
baseTea: BubbleTeaBaseTeaTypes[0],
addOns: ListOfBubbleTeaAddOns.create([], orders._owner),
deliveryDate: new Date(),
withMilk: false,
instructions: CoPlainText.create(''),
}, orders._owner);
orders.push(order);
newOrder = order;
}
});
</script>
<div>
<LinkToHome />
<h1 class="text-lg mb-4">
<strong>Create your bubble tea order 🧋</strong>
</h1>
{#if newOrder}
<OrderFormWithSaveButton order={newOrder} />
{:else}
<p>Creating order...</p>
{/if}
</div>

View File

@@ -0,0 +1,39 @@
<script lang="ts">
import { CoState } from 'jazz-tools/svelte';
import LinkToHome from './LinkToHome.svelte';
import OrderFormWithSaveButton from './OrderFormWithSaveButton.svelte';
import OrderThumbnail from './OrderThumbnail.svelte';
import { BubbleTeaOrder } from '$lib/schema';
type Props = {
id: string;
};
let { id }: Props = $props();
const order = new CoState(BubbleTeaOrder, id, {
resolve: { addOns: true, instructions: true },
});
</script>
{#if order.current}
<div>
<LinkToHome />
<div class="mb-6">
<p>Saved order:</p>
<OrderThumbnail order={order.current} />
</div>
<h1 class="text-lg mb-4">
<strong>Edit your bubble tea order 🧋</strong>
</h1>
<OrderFormWithSaveButton order={order.current} />
</div>
{:else}
<div>
<LinkToHome />
<p>Loading order...</p>
</div>
{/if}

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import { goto } from '$app/navigation';
function navigateHome() {
goto('#/');
}
</script>
<button
onclick={navigateHome}
class="mb-4 text-blue-500 hover:text-blue-700 underline"
>
← Back to orders
</button>

View File

@@ -0,0 +1,194 @@
<script lang="ts">
import { CoPlainText, type Loaded } from 'jazz-tools';
import {
BubbleTeaAddOnTypes,
BubbleTeaBaseTeaTypes,
type BubbleTeaOrder
} from '$lib/schema';
import { createForm } from '@tanstack/svelte-form';
import OrderThumbnail from './OrderThumbnail.svelte';
type LoadedBubbleTeaOrder = Loaded<
typeof BubbleTeaOrder,
{ addOns: { $each: true }; instructions: true }
>;
type OrderFormData = {
id: string;
baseTea: (typeof BubbleTeaBaseTeaTypes)[number];
addOns: (typeof BubbleTeaAddOnTypes)[number][];
deliveryDate: Date;
withMilk: boolean;
instructions?: string;
};
type Props = {
order: LoadedBubbleTeaOrder;
};
let { order: originalOrder }: Props = $props();
const defaultValues: OrderFormData = originalOrder.toJSON();
defaultValues.deliveryDate = new Date(defaultValues.deliveryDate);
const form = createForm(() => ({
defaultValues,
onSubmit: async ({ value }) => {
console.log('submit form', value);
// Apply changes to the original Jazz order
originalOrder.baseTea = value.baseTea;
originalOrder.addOns.applyDiff(value.addOns);
originalOrder.deliveryDate = value.deliveryDate;
originalOrder.withMilk = value.withMilk;
// Handle instructions (CoPlainText)
const instructions = originalOrder.instructions ?? CoPlainText.create('');
if (value.instructions) {
instructions.applyDiff(value.instructions);
}
},
}));
function handleSubmit(event: Event) {
event.preventDefault();
event.stopPropagation();
form.handleSubmit();
}
</script>
<form onsubmit={handleSubmit} class="grid gap-5">
<form.Subscribe selector={(state) => state.values}>
{#snippet children(values)}
<p>Unsaved order preview:</p>
<OrderThumbnail order={values} />
{/snippet}
</form.Subscribe>
<div class="flex flex-col gap-2">
<label for="baseTea">Base tea</label>
<form.Field
name="baseTea"
validators={{
onChange: ({ value }) => {
if (!value) {
return 'Please select your preferred base tea';
}
return undefined;
},
}}
>
{#snippet children(field)}
<select
id="baseTea"
class="dark:bg-transparent"
value={field.state.value}
onchange={(e) => field.handleChange(e.currentTarget.value as typeof BubbleTeaBaseTeaTypes[number])}
onblur={field.handleBlur}
>
<option value="" disabled>
Please select your preferred base tea
</option>
{#each BubbleTeaBaseTeaTypes as teaType}
<option value={teaType}>
{teaType}
</option>
{/each}
</select>
{#if field.state.meta.errors.length > 0}
<span class="text-red-500 text-sm">{field.state.meta.errors[0]}</span>
{/if}
{/snippet}
</form.Field>
</div>
<fieldset>
<legend class="mb-2">Add-ons</legend>
<form.Field name="addOns">
{#snippet children(field)}
{#each BubbleTeaAddOnTypes as addOn}
<div class="flex items-center gap-2">
<input
type="checkbox"
id={addOn}
checked={field.state.value.includes(addOn)}
onchange={(e) => {
const updatedAddons = (e.currentTarget.checked) ? [...field.state.value, addOn] : field.state.value.filter((item: string) => item !== addOn)
field.handleChange(updatedAddons)}}
/>
<label for={addOn}>{addOn}</label>
</div>
{/each}
{/snippet}
</form.Field>
</fieldset>
<div class="flex flex-col gap-2">
<label for="deliveryDate">Delivery date</label>
<form.Field
name="deliveryDate"
validators={{
onChange: ({ value }) => {
if (!value) {
return 'Delivery date is required';
}
return undefined;
},
}}
>
{#snippet children(field)}
<input
type="date"
id="deliveryDate"
class="dark:bg-transparent"
value={field.state.value.toISOString().split('T')[0]}
onchange={(e) => field.handleChange(new Date(e.currentTarget.value))}
onblur={field.handleBlur}
/>
{#if field.state.meta.errors.length > 0}
<span class="text-red-500 text-sm">{field.state.meta.errors[0]}</span>
{/if}
{/snippet}
</form.Field>
</div>
<div class="flex items-center gap-2">
<form.Field name="withMilk">
{#snippet children(field)}
<input
type="checkbox"
id="withMilk"
checked={field.state.value}
onchange={(e) => field.handleChange(e.currentTarget.checked)}
/>
<label for="withMilk">With milk?</label>
{/snippet}
</form.Field>
</div>
<div class="flex flex-col gap-2">
<label for="instructions">Special instructions</label>
<form.Field name="instructions">
{#snippet children(field)}
<textarea
id="instructions"
class="dark:bg-transparent"
value={field.state.value || ''}
onchange={(e) => field.handleChange(e.currentTarget.value)}
></textarea>
{/snippet}
</form.Field>
</div>
<form.Subscribe selector={(state) => [state.canSubmit, state.isSubmitting]}>
{#snippet children([canSubmit, isSubmitting])}
<button
type="submit"
disabled={!canSubmit}
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:bg-gray-400"
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
{/snippet}
</form.Subscribe>
</form>

View File

@@ -0,0 +1,39 @@
<script lang="ts">
import { type Loaded } from 'jazz-tools';
import { type BubbleTeaOrder } from '$lib/schema';
type Props = {
order: Loaded<typeof BubbleTeaOrder> | {
baseTea: string;
addOns: string[];
deliveryDate: Date;
withMilk: boolean;
instructions?: string;
};
};
let { order }: Props = $props();
</script>
<div class="border p-3 bg-gray-50 dark:bg-gray-800 rounded">
<strong>
{order.baseTea || "(No tea selected)"}
{order.withMilk ? " milk " : " "}
tea
</strong>
{#if order.addOns && order.addOns.length > 0}
<p class="text-sm text-stone-600">
with {order.addOns.join(", ").toLowerCase()}
</p>
{/if}
{#if order.instructions}
<p class="text-sm text-stone-600 italic">
{order.instructions}
</p>
{/if}
{#if order.deliveryDate}
<p class="text-sm text-stone-600">
Delivery: {order.deliveryDate.toLocaleDateString()}
</p>
{/if}
</div>

View File

@@ -0,0 +1,59 @@
<script lang="ts">
import { AccountCoState } from 'jazz-tools/svelte';
import { JazzAccount, type BubbleTeaOrder } from '$lib/schema';
import { type Loaded } from 'jazz-tools';
import OrderThumbnail from './OrderThumbnail.svelte';
import { goto } from '$app/navigation';
const me = new AccountCoState(JazzAccount, {
resolve: {
profile: true,
root: {
orders: {
$each: true
}
}
}
});
const orders = $derived(me.current?.root.orders);
function navigateToCreateOrder() {
goto('#/order');
}
function navigateToEditOrder(id: string) {
goto(`#/order/${id}`);
}
</script>
<div>
<h1 class="text-lg mb-4">
<strong>Your bubble tea orders 🧋</strong>
</h1>
{#if orders && orders.length > 0}
<div class="space-y-4">
{#each orders as order}
{#if order}
<button
type="button"
class="border rounded-lg p-4 cursor-pointer hover:bg-gray-50 text-left w-full"
onclick={() => navigateToEditOrder(order.id)}
>
<OrderThumbnail {order} />
</button>
{/if}
{/each}
</div>
{:else}
<p class="text-gray-500">No orders yet. Create your first order!</p>
{/if}
<button
onclick={navigateToCreateOrder}
class="mt-6 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Create New Order
</button>
</div>

View File

@@ -0,0 +1,86 @@
import { type Loaded, co, z } from 'jazz-tools';
export const BubbleTeaAddOnTypes = [
'Pearl',
'Lychee jelly',
'Red bean',
'Brown sugar',
'Taro'
] as const;
export const BubbleTeaBaseTeaTypes = ['Black', 'Oolong', 'Jasmine', 'Thai'] as const;
export const ListOfBubbleTeaAddOns = co
.list(z.literal([...BubbleTeaAddOnTypes]))
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.withHelpers((Self) => ({
hasChanges(list?: Loaded<typeof Self> | null) {
return list && Object.entries(list._raw.insertions).length > 0;
}
}));
export const BubbleTeaOrder = co.map({
baseTea: z.literal([...BubbleTeaBaseTeaTypes]),
addOns: ListOfBubbleTeaAddOns,
deliveryDate: z.date(),
withMilk: z.boolean(),
instructions: z.optional(co.plainText())
});
export const DraftBubbleTeaOrder = co
.map({
baseTea: z.optional(z.literal([...BubbleTeaBaseTeaTypes])),
addOns: z.optional(ListOfBubbleTeaAddOns),
deliveryDate: z.optional(z.date()),
withMilk: z.optional(z.boolean()),
instructions: z.optional(co.plainText())
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.withHelpers((Self) => ({
hasChanges(order: Loaded<typeof Self> | undefined) {
return (
!!order &&
(Object.keys(order._edits).length > 1 || ListOfBubbleTeaAddOns.hasChanges(order.addOns))
);
},
validate(order: Loaded<typeof Self>) {
const errors: string[] = [];
if (!order.baseTea) {
errors.push('Please select your preferred base tea.');
}
if (!order.deliveryDate) {
errors.push('Plese select a delivery date.');
}
return { errors };
}
}));
/** The root is an app-specific per-user private `CoMap`
* where you can store top-level objects for that user */
export const AccountRoot = co.map({
draft: DraftBubbleTeaOrder,
orders: co.list(BubbleTeaOrder)
});
export const JazzAccount = co
.account({
root: AccountRoot,
profile: co.profile()
})
.withMigration((account) => {
if (!account.root) {
const orders = co.list(BubbleTeaOrder).create([], account);
const draft = DraftBubbleTeaOrder.create(
{
addOns: ListOfBubbleTeaAddOns.create([], account),
instructions: co.plainText().create('', account)
},
account
);
account.root = AccountRoot.create({ draft, orders }, account);
}
});

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { JazzSvelteProvider } from 'jazz-tools/svelte';
import 'jazz-tools/inspector/register-custom-element';
import { PasskeyAuthBasicUI } from 'jazz-tools/svelte';
import { apiKey } from '$lib/apiKey';
import { JazzAccount } from '$lib/schema';
import '../app.css';
let { children } = $props();
</script>
<svelte:head>
<title>Form Example</title>
</svelte:head>
<JazzSvelteProvider
AccountSchema={JazzAccount}
sync={{
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
}}
>
<jazz-inspector></jazz-inspector>
<PasskeyAuthBasicUI appName="Form Example">
<div class="min-h-screen bg-gray-100">
{@render children()}
</div>
</PasskeyAuthBasicUI>
</JazzSvelteProvider>

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import { page } from '$app/stores';
import CreateOrder from '$lib/components/CreateOrder.svelte';
import EditOrder from '$lib/components/EditOrder.svelte';
import Orders from '$lib/components/Orders.svelte';
// Get current route from URL hash or default to '/'
const currentRoute = $derived($page.url.hash || '#/');
const route = $derived(currentRoute.replace('#', '') || '/');
</script>
<main class="max-w-xl mx-auto px-3 py-8 space-y-8">
{#if route === '/'}
<Orders />
{:else if route === '/order'}
<CreateOrder />
{:else if route.startsWith('/order/')}
{#if route.split('/').length === 3}
<EditOrder id={route.split('/')[2]} />
{/if}
{/if}
</main>

View File

@@ -0,0 +1,13 @@
import adapter from "@sveltejs/adapter-auto";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
},
};
export default config;

View File

@@ -0,0 +1,14 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
}

View File

@@ -0,0 +1,10 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [sveltekit(), tailwindcss()],
optimizeDeps: {
exclude: ['jazz-tools']
}
});

View File

@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@faker-js/faker": "^9.7.0",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-toast": "^1.2.14",
@@ -18,8 +19,8 @@
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"tailwind-merge": "^1.14.0",
@@ -29,8 +30,8 @@
"devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"@types/qrcode": "^1.5.1",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react-swc": "^3.10.1",
"postcss": "^8.4.27",
"tailwindcss": "^4.1.10",

View File

@@ -17,12 +17,12 @@ import React from "react";
import { TodoAccount, TodoProject } from "./1_schema.ts";
import { NewProjectForm } from "./3_NewProjectForm.tsx";
import { ProjectTodoTable } from "./4_ProjectTodoTable.tsx";
import { apiKey } from "./apiKey.ts";
import {
Button,
ThemeProvider,
TitleAndLogo,
} from "./basicComponents/index.ts";
import { TaskGenerator } from "./components/TaskGenerator.tsx";
import { wordlist } from "./wordlist.ts";
/**
@@ -41,7 +41,7 @@ function JazzAndAuth({ children }: { children: React.ReactNode }) {
return (
<JazzReactProvider
sync={{
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
peer: `ws://localhost:4200`,
}}
AccountSchema={TodoAccount}
>
@@ -92,6 +92,10 @@ export default function App() {
path: "/invite/*",
element: <p>Accepting invite...</p>,
},
{
path: "/generate",
element: <TaskGenerator />,
},
]);
// `useAcceptInvite()` is a hook that accepts an invite link from the URL hash,

View File

@@ -0,0 +1,63 @@
import { TodoAccount } from "@/1_schema";
import { FormEvent, useState } from "react";
import { useNavigate } from "react-router-dom";
import { generateRandomProject } from "../generate";
export function TaskGenerator() {
const [isGenerating, setIsGenerating] = useState(false);
const navigate = useNavigate();
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const numTasks = Math.max(
1,
parseInt(formData.get("numTasks") as string) || 1,
);
setIsGenerating(true);
const project = generateRandomProject(numTasks);
const { root } = await TodoAccount.getMe().ensureLoaded({
resolve: {
root: {
projects: true,
},
},
});
root.projects.push(project.value);
await project.done;
navigate(`/project/${project.value.id}`);
};
return (
<div className="p-4 border rounded-lg shadow-xs bg-white">
<h2 className="text-lg font-semibold mb-4">Generate Random Tasks</h2>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<label htmlFor="numTasks" className="text-sm font-medium">
Number of tasks:
</label>
<input
id="numTasks"
name="numTasks"
type="number"
min="1"
defaultValue={5}
className="w-20 px-2 py-1 border rounded"
/>
</div>
<button
type="submit"
disabled={isGenerating}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:bg-blue-300"
>
{isGenerating ? "Generating..." : "Generate Tasks"}
</button>
</form>
</div>
);
}

View File

@@ -3,6 +3,9 @@ import { CoPlainText } from "jazz-tools";
import { Task, TodoProject } from "./1_schema";
export function generateRandomProject(numTasks: number) {
// Generate a random project title
const projectTitle = faker.company.catchPhrase();
// Create a list of tasks
const tasks = TodoProject.def.shape.tasks.create([]);
@@ -15,6 +18,7 @@ export function generateRandomProject(numTasks: number) {
faker.lorem.sentence({ min: 3, max: 8 }),
tasks._owner,
),
version: 1,
});
tasks.push(task);
}
@@ -23,7 +27,7 @@ export function generateRandomProject(numTasks: number) {
// Create and return the project
return {
value: TodoProject.create({
title: `${numTasks} tasks`,
title: projectTitle,
tasks: tasks,
}),
done: new Promise((resolve) => {

View File

@@ -12,14 +12,14 @@
"dependencies": {
"@tailwindcss/forms": "^0.5.9",
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0"
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"tailwindcss": "^4.1.10",

View File

@@ -27,26 +27,28 @@ export default function ButtonsPage() {
return (
<>
<h3 className="text-lg mt-5 mb-2 font-bold">Variants</h3>
<p className="my-3">Buttons are styled with the variant prop.</p>
<p className="mb-3">
For compatibility the shadcn/ui variants are mapped to the design
system.
</p>
<div className="grid grid-cols-2 gap-2">
<Button variant="default">default</Button>
<Button variant="link">link</Button>
<Button variant="ghost">ghost</Button>
<Button variant="outline">outline</Button>
<Button variant="secondary">secondary</Button>
<Button variant="destructive">destructive</Button>
</div>
<h3 className="text-lg mt-5 font-bold">Intents</h3>
<p className="my-3">
We have extended the variants to include more styles via the intent
prop.
<h3 className="text-lg mt-5 mb-2 font-bold">Intents</h3>
<p>
We have extended the shadcn/ui variants to include more styles via the
intent prop.
</p>
<div className="grid grid-cols-2 gap-2">
<Button intent="default">default</Button>
<Button intent="muted">muted</Button>
<Button intent="strong">strong</Button>
{/* <Button intent="default">default</Button> */}
<Button intent="primary">primary</Button>
<Button intent="tip">tip</Button>
<Button intent="info">info</Button>
@@ -54,6 +56,8 @@ export default function ButtonsPage() {
<Button intent="warning">warning</Button>
<Button intent="alert">alert</Button>
<Button intent="danger">danger</Button>
<Button intent="muted">muted</Button>
<Button intent="strong">strong</Button>
</div>
<div className="flex justify-between items-center w-48 mt-10">
@@ -85,7 +89,7 @@ export default function ButtonsPage() {
<p className="text-sm mt-2 mb-5">
<strong>NB:</strong> Variants and styles are interchangeable. See the
intent on each variant with the dropdown.
intent on each variant with the dropdown
</p>
<div className="grid grid-cols-2 gap-2">
@@ -103,19 +107,9 @@ export default function ButtonsPage() {
</Button>
</div>
<p className="my-3">
For compatibility the shadcn/ui variants are mapped to the design
system.
</p>
<div className="grid grid-cols-2 gap-2">
<Button variant="secondary">secondary</Button>
<Button variant="destructive">destructive</Button>
</div>
<h3 className="text-lg font-bold mt-5">Icons</h3>
<p className="my-3">Buttons can also contain an icon and text.</p>
<p>Buttons can also contain an icon and text.</p>
<div className="grid grid-cols-2 gap-2">
<Button
@@ -136,7 +130,7 @@ export default function ButtonsPage() {
>
outline info with icon
</Button>
<p className="col-span-2 my-2">
<p className="col-span-2">
Or just use the icon prop with any of the button variants, style
variants and colors.
</p>
@@ -157,7 +151,6 @@ const buttonPropsTableData = {
{
prop: "intent?",
types: [
"default",
"primary",
"tip",
"info",
@@ -181,7 +174,7 @@ const buttonPropsTableData = {
"secondary",
"destructive",
],
default: "default",
default: "undefined",
},
{
prop: "icon?",

View File

@@ -159,8 +159,8 @@ const styleClasses = (intent: Style, variant: Variant | undefined) => {
inverted: `${styleToTextMap[intent]} ${colorToBgHoverMap30[styleToColorMap[intent] as VariantColor]} ${colorToBgMap[styleToColorMap[intent] as VariantColor]} ${colorToBgActiveMap50[styleToColorMap[intent] as VariantColor]} ${shadowClassesBase}`,
ghost: `bg-transparent ${styleToTextMap[intent]} ${colorToBgHoverMap10[styleToColorMap[intent] as VariantColor]} ${colorToBgActiveMap25[styleToColorMap[intent] as VariantColor]}`,
link: `bg-transparent ${styleToTextMap[intent]} underline underline-offset-2 p-0 hover:bg-transparent ${styleToTextHoverMap[intent]} ${styleToTextActiveMap[intent]} active:underline-stone-500`,
secondary: variantClass("muted"),
destructive: variantClass("danger"),
secondary: `bg-stone-300 ${styleToTextMap[intent]} hover:bg-stone-400/80 active:bg-stone-500/80`,
destructive: `bg-danger text-white hover:bg-red/80 active:bg-red/70`,
default: `${styleToBgGradientColorMap["default"]} ${styleToBgGradientHoverMap["default"]} ${textColorVariant("default")} ${styleToButtonStateMap["default"]} ${shadowClassesBase} shadow-stone-400/20`,
};
};

View File

@@ -12,7 +12,6 @@ import {
ChevronLeftIcon,
ChevronRight,
ChevronRightIcon,
ClipboardCheckIcon,
ClipboardIcon,
CodeIcon,
Eye,
@@ -67,7 +66,6 @@ export const icons = {
close: XIcon,
code: CodeIcon,
copy: ClipboardIcon,
copySuccess: ClipboardCheckIcon,
cursor: MousePointer2Icon,
darkTheme: MoonIcon,
delete: TrashIcon,

View File

@@ -11,7 +11,7 @@ export function CopyButton({
onCopy,
}: {
code: string;
size: "sm" | "md" | "lg";
size: "md" | "lg";
className?: string;
onCopy?: () => void;
}) {
@@ -32,13 +32,13 @@ export function CopyButton({
type="button"
className={clsx(
className,
"group/button absolute overflow-hidden rounded text-2xs font-medium md:opacity-0 backdrop-blur transition md:focus:opacity-100 group-hover:opacity-100 items-center align-middle p-0",
"group/button absolute overflow-hidden rounded text-2xs font-medium md:opacity-0 backdrop-blur transition md:focus:opacity-100 group-hover:opacity-100",
copied
? "bg-blue-400/10 ring-1 ring-inset ring-blue-400/20"
? "bg-emerald-400/10 ring-1 ring-inset ring-emerald-400/20"
: "bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5",
size === "md"
size == "md"
? "right-[8.5px] top-[8.5px] py-[2px] pl-1 pr-2"
: "right-2 top-2 py-1 pl-2 pr-2",
: "right-2 top-2 py-1 pl-2 pr-3",
)}
onClick={() => {
window.navigator.clipboard.writeText(code).then(() => {
@@ -60,22 +60,18 @@ export function CopyButton({
className={clsx(
size === "md" ? "size-3" : "size-4",
"stroke-stone-500 transition-colors group-hover/button:stroke-stone-600 dark:group-hover/button:stroke-stone-400",
copied && "stroke-primary",
)}
/>
{size !== "sm" && "Copy"}
Copy
</span>
<span
aria-hidden={!copied}
className={clsx(
"pointer-events-none absolute inset-0 flex items-center justify-center text-primary transition duration-300",
"pointer-events-none absolute inset-0 flex items-center justify-center text-emerald-600 transition duration-300 dark:text-emerald-400",
!copied && "translate-y-1.5 opacity-0",
)}
>
{size === "sm" && (
<Icon name="copySuccess" size="xs" className="stroke-primary" />
)}
{size !== "sm" && "Copied!"}
Copied!
</span>
</button>
);

View File

@@ -44,7 +44,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
: icon && iconPosition === "right";
const inputClassName = clsx(
"w-full rounded-md border px-2.5 py-1 shadow-sm h-[36px]",
"w-full rounded-md border px-3.5 py-2 shadow-sm",
"font-medium text-stone-900",
"dark:text-white dark:bg-stone-925",
);

View File

@@ -60,7 +60,7 @@ export function DropdownItem({
let classes = clsx(
className,
// Base styles
"group rounded-md space-x-2 focus:outline-none px-2.5 py-1.5",
"group rounded-md space-x-2 focus:outline-none px-2.5 py-1.5",
// Text styles
"text-left text-sm/6 dark:text-white forced-colors:text-[CanvasText]",
// Focus

View File

@@ -21,8 +21,8 @@ export type Style =
export const sizeClasses = {
sm: "text-sm py-1 px-2",
md: "py-1.5 px-3 h-[36px]",
lg: "py-2 px-5 md:px-6 md:py-2.5",
md: "py-1.5 px-3",
lg: "md:text-lg py-2 px-3 md:px-8 md:py-3",
};
export const styleToBorderMap = {

View File

@@ -42,6 +42,15 @@ export const team: Array<TeamMember> = [
linkedin: "giordanoricci",
image: "gio.jpg",
},
{
name: "Trisha Lim",
slug: "trisha",
titles: ["Frontend Dev", "Marketing"],
image: "trisha.png",
location: "Lisbon, Portugal ",
github: "trishalim",
website: "https://trishalim.com",
},
{
name: "Meg Culotta",
slug: "meg",
@@ -64,7 +73,7 @@ export const team: Array<TeamMember> = [
name: "Sammii Kellow",
slug: "sammii",
location: "London, UK",
titles: ["Frontend & Design Engineer", "Marketing"],
titles: ["Design Engineer", "Marketing"],
x: "SammiiHaylock",
github: "sammii-hk",
website: "https://sammii.dev",
@@ -82,25 +91,4 @@ export const team: Array<TeamMember> = [
linkedin: "boorad",
image: "brad.png",
},
{
name: "Divya S",
slug: "div",
location: "New York, US",
titles: ["Platform Engineer"],
x: "shortdiv",
github: "shortdiv",
website: "https://shortdiv.com",
bluesky: "shortdiv.bsky.social",
linkedin: "shortdiv",
image: "div.jpg",
},
{
name: "Nico Rainhart",
slug: "nico",
location: "Buenos Aires, Argentina",
titles: ["Full-Stack Dev", "Framework Engineer"],
image: "nico.jpeg",
github: "nrainhart",
linkedin: "nicolás-rainhart",
},
];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

View File

@@ -4,7 +4,6 @@ import { ComingSoonSection } from "@/components/home/ComingSoonSection";
import { EarlyAdopterSection } from "@/components/home/EarlyAdopterSection";
import { EncryptionSection } from "@/components/home/EncryptionSection";
import { FeaturesSection } from "@/components/home/FeaturesSection";
import { GetStartedSnippetSelect } from "@/components/home/GetStartedSnippetSelect";
import { HeroSection } from "@/components/home/HeroSection";
import { HowJazzWorksSection } from "@/components/home/HowJazzWorksSection";
import { LocalFirstFeaturesSection } from "@/components/home/LocalFirstFeaturesSection";
@@ -17,8 +16,7 @@ export default function Home() {
<>
<HeroSection />
<div className="container flex flex-col gap-12 lg:gap-20">
<GetStartedSnippetSelect />
<div className="container flex flex-col gap-12 mt-12 lg:gap-20 lg:mt-20">
<SupportedEnvironmentsSection />
<HowJazzWorksSection />

View File

@@ -17,7 +17,7 @@ export function SideNavItem({
}) {
const classes = clsx(
className,
"py-1 px-2 group rounded-md flex items-center transition-colors",
"py-1 px-2 -mx-2 group rounded-md flex items-center transition-colors",
);
const path = usePathname();
@@ -28,7 +28,7 @@ export function SideNavItem({
className={clsx(
classes,
path === href
? "text-stone-900 font-medium bg-stone-200/50 dark:text-white dark:bg-stone-800/50"
? "text-stone-900 font-medium bg-stone-100 dark:text-white dark:bg-stone-900"
: "hover:text-stone-900 dark:hover:text-stone-200",
)}
>

View File

@@ -10,21 +10,10 @@ import {
DropdownItem,
DropdownMenu,
} from "@garden-co/design-system/src/components/organisms/Dropdown";
import clsx from "clsx";
import { usePathname, useRouter } from "next/navigation";
import { useState } from "react";
export function FrameworkSelect({
onSelect,
size = "md",
routerPush = true,
className,
}: {
onSelect?: (framework: Framework) => void;
size?: "sm" | "md";
routerPush?: boolean;
className?: string;
}) {
export function FrameworkSelect() {
const router = useRouter();
const defaultFramework = useFramework();
const [selectedFramework, setSelectedFramework] =
@@ -34,26 +23,26 @@ export function FrameworkSelect({
const selectFramework = (newFramework: Framework) => {
setSelectedFramework(newFramework);
onSelect && onSelect(newFramework);
routerPush && router.push(path.replace(defaultFramework, newFramework));
router.push(path.replace(defaultFramework, newFramework));
};
return (
<Dropdown>
<DropdownButton
className={clsx("w-full justify-between overflow-hidden text-nowrap", size === "sm" && "text-sm", className)}
className="w-full justify-between"
as={Button}
variant="outline"
intent="default"
>
<span className="text-nowrap max-w-full overflow-hidden text-ellipsis">{frameworkNames[selectedFramework].label}</span>
{frameworkNames[selectedFramework].label}
<Icon name="chevronDown" size="sm" />
</DropdownButton>
<DropdownMenu className="w-[--button-width] z-50" anchor="bottom start">
{Object.entries(frameworkNames)
.filter(([_, framework]) => !framework.hidden)
.map(([key, framework]) => (
<DropdownItem
className={clsx("items-baseline", size === "sm" && "text-xs text-nowrap", selectedFramework === key && "text-primary dark:text-primary")}
className="items-baseline"
key={key}
onClick={() => selectFramework(key as Framework)}
>

View File

@@ -1,43 +0,0 @@
'use client'
import { Framework } from "@/content/framework";
import { useFramework } from "@/lib/use-framework";
import NpxCreateJazzApp from "@/components/home/NpxCreateJazzApp.mdx";
import { CopyButton } from "@garden-co/design-system/src/components/molecules/CodeGroup";
import { useState } from "react";
import { Button } from "@garden-co/design-system/src/components/atoms/Button";
import Link from "next/link";
import { FrameworkSelect } from "../docs/FrameworkSelect";
import clsx from "clsx";
import { track } from "@vercel/analytics";
import { GappedGrid } from "@garden-co/design-system/src/components/molecules/GappedGrid";
export function GetStartedSnippetSelect() {
const defaultFramework = useFramework();
const [selectedFramework, setSelectedFramework] =
useState<Framework>(defaultFramework);
return (
<GappedGrid>
<div className="relative w-full col-span-2 lg:col-span-3 border-2 border-primary rounded-lg overflow-hidden">
<CopyButton
code="npx create-jazz-app@latest"
size="sm"
className={clsx("mt-0.5 mr-0.5 z-100 md:opacity-100 hidden md:block")}
onCopy={() => track("create-jazz-app command copied from hero")}
/>
<NpxCreateJazzApp />
</div>
<div className="col-span-2 lg:col-span-3 flex flex-row gap-2">
<div className="h-full items-center w-[175px]">
<FrameworkSelect onSelect={setSelectedFramework} size="md" routerPush={false} className="h-full md:px-4" />
</div>
<div className="flex h-full items-center">
<Button intent="primary" size="lg" className="w-full">
<Link className="my-[0.11rem]" href={`/docs/${selectedFramework}`}>Get started</Link>
</Button>
</div>
</div>
</GappedGrid>
);
}

View File

@@ -1,5 +1,6 @@
"use client";
import CreateJazzApp from "@/components/home/CreateJazzApp.mdx";
import { marketingCopy } from "@/content/marketingCopy";
import { H1 } from "@garden-co/design-system/src/components/atoms/Headings";
import {
@@ -7,10 +8,11 @@ import {
type IconName,
} from "@garden-co/design-system/src/components/atoms/Icon";
import { Kicker } from "@garden-co/design-system/src/components/atoms/Kicker";
import { CopyButton } from "@garden-co/design-system/src/components/molecules/CodeGroup";
import { Prose } from "@garden-co/design-system/src/components/molecules/Prose";
import { SectionHeader } from "@garden-co/design-system/src/components/molecules/SectionHeader";
import { track } from "@vercel/analytics";
import Link from "next/link";
import { GetStartedSnippetSelect } from "./GetStartedSnippetSelect";
const features: Array<{
title: string;
@@ -52,8 +54,8 @@ const features: Array<{
export function HeroSection() {
return (
<div className="container grid items-center gap-x-8 gap-y-12 my-12 md:my-16 lg:my-24 lg:gap-x-10 lg:grid-cols-12">
<div className="flex flex-col justify-center gap-5 lg:col-span-11 lg:gap-8">
<div className="container grid items-center gap-x-8 gap-y-12 my-12 md:my-16 lg:my-24 lg:gap-x-10 lg:grid-cols-3">
<div className="flex flex-col justify-center gap-5 lg:col-span-2 lg:gap-8">
<Kicker>Toolkit for backendless apps</Kicker>
<H1>
<span className="inline-block text-highlight">
@@ -92,6 +94,31 @@ export function HeroSection() {
))}
</div>
</div>
<div className="h-full group grid md:grid-cols-2 items-center lg:grid-cols-1 lg:pt-36">
<SectionHeader
className="md:col-span-2 lg:sr-only"
title="Get a Jazz app running in minutes."
/>
<div className="overflow-hidden sm:rounded-xl sm:border h-full sm:px-8 sm:pt-6 bg-stone-50 dark:bg-stone-950">
<div className="rounded-lg bg-white dark:bg-stone-925 sm:ring-4 ring-stone-400/20 sm:shadow-xl sm:shadow-blue/20 border relative sm:top-2 h-full w-full">
<div className="py-4 flex items-center gap-2.5 px-6 border-b">
<span className="rounded-full size-3 bg-stone-200 dark:bg-stone-900" />
<span className="rounded-full size-3 bg-stone-200 dark:bg-stone-900" />
<span className="rounded-full size-3 bg-stone-200 dark:bg-stone-900" />
<CopyButton
code="npx create-jazz-app@latest"
size="md"
className="mt-0.5 mr-0.5"
onCopy={() => track("create-jazz-app command copied from hero")}
/>
</div>
<div className="p-3">
<CreateJazzApp />
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,3 +0,0 @@
```sh
npx create-jazz-app@latest
```

View File

@@ -118,7 +118,7 @@ To use it, install the following Packages:
<CodeGroup>
```bash
pnpm add react-native-quick-crypto@1.0.0-beta.18 react-native-nitro-modules react-native-fast-encoder
pnpm add react-native-quick-crypto@1.0.0-beta.18 react-native-nitro-modules
```
</CodeGroup>

View File

@@ -116,7 +116,7 @@ To use it, install the following Packages:
<CodeGroup>
```bash
pnpm add react-native-quick-crypto@1.0.0-beta.18 react-native-nitro-modules react-native-fast-encoder
pnpm add react-native-quick-crypto@1.0.0-beta.18 react-native-nitro-modules
```
</CodeGroup>

View File

@@ -40,7 +40,7 @@ npx expo prebuild
<CodeGroup>
```bash
# Expo dependencies
npx expo install expo-linking expo-secure-store expo-sqlite expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer
npx expo install expo-linking expo-secure-store expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer
# React Native polyfills
npm i -S @azure/core-asynciterator-polyfill react-native-url-polyfill readable-stream react-native-get-random-values

View File

@@ -202,7 +202,7 @@ See the corresponding sections for [creating](/docs/using-covalues/filestreams#c
### Unions of CoMaps (declaration)
You can declare unions of CoMaps that have discriminating fields, using `co.discriminatedUnion()`.
You can declare unions of CoMaps that have discriminating fields, using `z.discriminatedUnion()`.
<CodeGroup>
```ts twoslash
@@ -220,7 +220,7 @@ const SliderWidget = co.map({
max: z.number(),
});
const WidgetUnion = co.discriminatedUnion("type", [ButtonWidget, SliderWidget]);
const WidgetUnion = z.discriminatedUnion("type", [ButtonWidget, SliderWidget]);
```
</CodeGroup>
@@ -321,7 +321,7 @@ const Company = co.map({
#### Optional References
You can make references optional with `co.optional()`:
You can make references optional with `z.optional()`:
<CodeGroup>
```ts twoslash
@@ -331,7 +331,7 @@ const Pet = co.map({
});
// ---cut---
const Person = co.map({
pet: co.optional(Pet),
pet: z.optional(Pet),
});
```
</CodeGroup>
@@ -372,7 +372,7 @@ const ListOfPeople = co.list(Person);
</CodeGroup>
Note: similarly, if you use modifiers like `co.optional()` you'll need to help TypeScript along:
Note: similarly, if you use modifiers like `z.optional()` you'll need to help TypeScript along:
<CodeGroup>
```ts twoslash
@@ -381,7 +381,7 @@ import { co, z } from "jazz-tools";
const Person = co.map({
name: z.string(),
get bestFriend(): z.ZodOptional<typeof Person> {
return co.optional(Person);
return z.optional(Person);
}
});
```

View File

@@ -24,7 +24,7 @@ const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
coordinator: z.optional(Member),
});
export type Project = co.loaded<typeof Project>;
```
@@ -54,7 +54,7 @@ const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
coordinator: z.optional(Member),
});
const Inventory = co.record(z.string(), z.number());
// ---cut---
@@ -90,7 +90,7 @@ const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
coordinator: z.optional(Member),
});
// ---cut---
@@ -134,7 +134,7 @@ const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
coordinator: z.optional(Member),
});
const project = Project.create(
{
@@ -165,7 +165,7 @@ const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
coordinator: z.optional(Member),
});
const project = Project.create(
{
@@ -197,7 +197,7 @@ const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
coordinator: z.optional(Member),
get subProject() {
return Project.optional();
}
@@ -219,9 +219,9 @@ const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
coordinator: z.optional(Member),
get subProjects(): z.ZodOptional<CoListSchema<typeof Project>> {
return co.optional(co.list(Project));
return z.optional(co.list(Project));
}
});
export type Project = co.loaded<typeof Project>;
@@ -265,7 +265,7 @@ const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
coordinator: z.optional(Member),
});
const Inventory = co.record(z.string(), z.number());
const project = Project.create(
@@ -297,7 +297,7 @@ const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
coordinator: z.optional(Member),
});
const Inventory = co.record(z.string(), z.number());
const project = Project.create(
@@ -347,7 +347,7 @@ const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
coordinator: z.optional(Member),
});
const Inventory = co.record(z.string(), z.number());
const project = Project.create(
@@ -445,7 +445,7 @@ const TaskV2 = co.map({
// Export the discriminated union; because some users might
// not be able to run the migration
export const Task = co.discriminatedUnion("version", [
export const Task = z.discriminatedUnion("version", [
TaskV1,
TaskV2,
]);

View File

@@ -6,7 +6,7 @@ export const metadata = {
# Connecting CoValues with direct linking
CoValues can form relationships with each other by **linking directly to other CoValues**. This creates a powerful connection where one CoValue can point to the unique identity of another.
Instead of embedding all the details of one CoValue directly within another, you use its Jazz-Tools schema as the field type. This allows multiple CoValues to point to the same piece of data effortlessly.
Instead of embedding all of the details of one coValue directly within another, you use its Jazz-Tools schema as the field type. This allows multiple CoValues to point to the same piece of data effortlessly.
<CodeGroup>
```ts twoslash
@@ -50,51 +50,3 @@ export type User = co.loaded<typeof User>;
This direct linking approach offers a single source of truth. When you update a referenced CoValue, all other CoValues that point to it are automatically updated, ensuring data consistency across your application.
By connecting CoValues through these direct references, you can build robust and collaborative applications where data is consistent, efficient to manage, and relationships are clearly defined. The ability to link different CoValue types to the same underlying data is fundamental to building complex applications with Jazz.
## Recursive references with DiscriminatedUnion
In advanced schemas, you may want a CoValue that recursively references itself. For example, a `ReferenceItem` that contains a list of other items like `NoteItem` or `AttachmentItem`. This is common in tree-like structures such as threaded comments or nested project outlines.
You can model this with a Zod `z.discriminatedUnion`, but TypeScripts type inference doesn't handle recursive unions well without a workaround.
Heres how to structure your schema to avoid circular reference errors.
### Use this pattern for recursive discriminated unions
<CodeGroup>
```ts twoslash
import { CoListSchema, co, z } from "jazz-tools";
// Recursive item modeling pattern using discriminated unions
// First, define the non-recursive types
export const NoteItem = co.map({
type: z.literal("note"),
internal: z.boolean(),
content: co.plainText(),
});
export const AttachmentItem = co.map({
type: z.literal("attachment"),
internal: z.boolean(),
content: co.fileStream(),
});
export const ReferenceItem = co.map({
type: z.literal("reference"),
internal: z.boolean(),
content: z.string(),
// Workaround: declare the field type using CoListSchema and ZodDiscriminatedUnion so TS can safely recurse
get children(): CoListSchema<z.ZodDiscriminatedUnion<[typeof NoteItem, typeof AttachmentItem, typeof ReferenceItem]>> {
return ProjectContextItemList;
},
});
// Create the recursive union
export const ProjectContextItem = z.discriminatedUnion("type", [NoteItem, AttachmentItem, ReferenceItem]);
// Final list of recursive types
export const ProjectContextItemList = co.list(ProjectContextItem);
```
</CodeGroup>
Even though this seems like a shortcut, TypeScript and Zod can't resolve the circular reference this way. Always define the discriminated union before introducing recursive links.

1543
homepage/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@ packages:
- "gcmp"
catalog:
"react": "19.1.0"
"react-dom": "19.1.0"
"@types/react": "19.1.0"
"@types/react-dom": "19.1.0"
"react": "19.0.0"
"react-dom": "19.0.0"
"@types/react": "19.0.0"
"@types/react-dom": "19.0.0"

View File

@@ -39,7 +39,6 @@
"changeset-version": "changeset version && pnpm i --no-frozen-lockfile",
"release": "turbo run build --filter='./packages/*' && pnpm changeset publish && git push --follow-tags",
"clean": "rm -rf ./packages/*/dist && rm -rf ./packages/*/node_modules && rm -rf ./examples/*/node_modules && rm -rf ./examples/*/dist",
"postinstall": "lefthook install",
"check-catalog-deps": "node scripts/check-catalog-deps.js"
},
"version": "0.0.0",
@@ -51,10 +50,10 @@
"ignoreMissing": ["@babel/*", "expo-modules-*", "typescript"]
},
"overrides": {
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"vite": "6.3.5",
"esbuild": "0.24.0"
}

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