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
352 changed files with 10776 additions and 14151 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

@@ -14,7 +14,7 @@
"tests/jazz-svelte/src/**",
"examples/*svelte*/**",
"starters/*svelte*/**",
"examples/server-worker-inbox/src/routeTree.gen.ts",
"examples/jazz-paper-scissors/src/routeTree.gen.ts",
"homepage/homepage/**",
"**/package.json"
]
@@ -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,80 +1,5 @@
# passkey-svelte
## 0.0.103
### Patch Changes
- Updated dependencies [a584590]
- Updated dependencies [9acccb5]
- jazz-tools@0.15.14
## 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.103",
"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

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

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"
}
}

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