Compare commits

...

649 Commits

Author SHA1 Message Date
Guido D'Orsi
c1c6e31711 Merge pull request #2719 from garden-co/changeset-release/main
Version Packages
2025-08-11 14:15:38 +02:00
github-actions[bot]
0b16085f3c Version Packages 2025-08-11 12:07:42 +00:00
Guido D'Orsi
e53db2e96a chore: format 2025-08-11 14:04:22 +02:00
Guido D'Orsi
384f0e23c0 Merge pull request #2701 from garden-co/feat/better-async-storage
feat: support multiple instances of storage
2025-08-11 14:03:39 +02:00
Guido D'Orsi
daaf1789d9 Merge pull request #2721 from garden-co/fix/char-chunking-coplaintext
Fix local transactions streaming and implement chunking for CoPlainText
2025-08-11 14:02:49 +02:00
Guido D'Orsi
1f9e20e753 Merge pull request #2705 from garden-co/chore/biome-2
chore: bump biome version to 2.1.3
2025-08-11 14:01:16 +02:00
Guido D'Orsi
ce9ca54f5c feat: content chunking on CoPlainText 2025-08-11 14:00:20 +02:00
Guido D'Orsi
67e0968809 fix: fix content streaming chunking, now chunks should be splitted always respecting the MAX_RECOMMENDED_TX_SIZE 2025-08-11 14:00:17 +02:00
Giordano Ricci
96a922cceb Merge pull request #2711 from garden-co/gio/usage-metering 2025-08-11 12:22:00 +01:00
Sammii
0a98b826f1 Merge pull request #2675 from garden-co/feat/quint-add-full-button-suite
Feat/quint add full button suite
2025-08-11 10:59:10 +01:00
Sammii
62a3854c41 Update packages/quint-ui/src/components/button.tsx
Co-authored-by: Giordano Ricci <me@giordanoricci.com>
2025-08-11 10:45:57 +01:00
Guido D'Orsi
7bdb6f4279 chore: simplify drawWaveform 2025-08-11 11:33:49 +02:00
Guido D'Orsi
4b99ff1fe3 feat: support multiple storage instances 2025-08-11 11:21:22 +02:00
Guido D'Orsi
3ebf8258a0 Merge pull request #2692 from garden-co/feat/garbage-collector
feat: added a TTL-based garbage collection
2025-08-11 10:54:58 +02:00
Guido D'Orsi
4809d14f6d chore: restore CI quality check 2025-08-11 10:45:04 +02:00
Guido D'Orsi
5ae1f33127 chore: disable importOrder and format the codebase 2025-08-11 10:44:03 +02:00
Guido D'Orsi
ca5d84f6a9 Merge pull request #2720 from garden-co/fix/vitest-nested-projects
chore: removed nested projects in vitest.config
2025-08-11 10:33:59 +02:00
Guido D'Orsi
6e6acc3404 chore: revert the homepage formatting 2025-08-11 10:26:24 +02:00
Guido D'Orsi
b17b7b6481 Merge remote-tracking branch 'origin/main' into chore/biome-2 2025-08-11 10:22:41 +02:00
Guido D'Orsi
5341646301 chore: revert formatting, remove the code-quality CI check 2025-08-11 10:21:33 +02:00
Guido D'Orsi
5416165d28 Merge remote-tracking branch 'origin/main' into feat/garbage-collector 2025-08-11 10:18:37 +02:00
Guido D'Orsi
b5a9f681c5 Merge pull request #2696 from garden-co/feat/chat-pagination
feat(chat): implement lazy-loading
2025-08-11 10:17:32 +02:00
Matteo Manchi
7dffc006eb chore: removed nested projects in vitest.config 2025-08-11 00:09:19 +02:00
Guido D'Orsi
cd3cc5b0ab Merge pull request #2716 from garden-co/fix/co-record-key-deep-loading
Fix return type on deep loaded co.record() when using single keys
2025-08-10 22:54:54 +02:00
Guido D'Orsi
ceab75eb4d Merge pull request #2718 from garden-co/feat/nice-music-player
fix: fix UnknownError: Unknown transaction on IndexedDB
2025-08-10 22:40:50 +02:00
Guido D'Orsi
103d1b41f7 fix: fix unknown transaction error on IndexedDB 2025-08-10 22:32:23 +02:00
Guido D'Orsi
b87cc6973e Merge pull request #2717 from garden-co/feat/nice-music-player
feat: improve music player UI
2025-08-10 22:23:41 +02:00
Guido D'Orsi
3d541ca241 feat: improve music player controls bar 2025-08-10 22:21:03 +02:00
Matteo Manchi
e72bfec884 fixup! fix(jazz-tools/coValues): fix return type on deep loaded co.record() when using string keys 2025-08-10 20:37:37 +02:00
Matteo Manchi
19c7ad27d9 fix(jazz-tools/coValues): fix return type on deep loaded co.record() when using string keys 2025-08-10 15:22:24 +02:00
Guido D'Orsi
0bc7bfc5cc test: cover string loading and unavailable props 2025-08-09 17:31:49 +02:00
Matteo Manchi
2c8120d46f fix(jazz-tools/coValues): fix return type on deep loaded co.record() when using single keys 2025-08-09 16:58:49 +02:00
Guido D'Orsi
c936c8c611 Merge pull request #2708 from garden-co/changeset-release/main
Version Packages
2025-08-08 19:29:12 +02:00
github-actions[bot]
58c6013770 Version Packages 2025-08-08 16:28:02 +00:00
Guido D'Orsi
3eb3291a97 Merge pull request #2714 from garden-co/revert-2712-fix/invalid-signature-allowlist
Revert "feat: add markAsStorageSignatureToFix to make it possible to fix bad signatures caused by the storage bug fixed in 0.15.9"
2025-08-08 18:25:32 +02:00
Guido D'Orsi
6b659f2df3 Revert "feat: add markAsStorageSignatureToFix to make it possible to fix bad signatures caused by the storage bug fixed in 0.15.9" 2025-08-08 18:25:22 +02:00
Guido D'Orsi
dcc9c9a5ec Merge pull request #2712 from garden-co/fix/invalid-signature-allowlist
feat: add markAsStorageSignatureToFix to make it possible to fix bad signatures caused by the storage bug fixed in 0.15.9
2025-08-08 18:14:58 +02:00
Guido D'Orsi
fe9a244363 Merge pull request #2710 from garden-co/fix/missing-child-rotation
fix: handle missing child groups when rotating key
2025-08-08 18:12:11 +02:00
Guido D'Orsi
9440bbc058 Merge pull request #2713 from garden-co/fix/nested-discriminated-union
fix: fix nested discriminated unions
2025-08-08 18:08:34 +02:00
Guido D'Orsi
1c92cc2997 chore: improve the key fallback 2025-08-08 18:07:15 +02:00
Guido D'Orsi
33ebbf0bdd fix: fix nested discriminated unions 2025-08-08 17:58:50 +02:00
Guido D'Orsi
d630b5bde5 Merge pull request #2704 from garden-co/fix/everyone-readkey-rotation
fix: skip rotateKey when everyone has read access
2025-08-08 17:31:14 +02:00
Guido D'Orsi
1c6ae12cd9 feat: add markAsStorageSignatureToFix to make it possible to fix bad signatures caused by the storage bug fixed in 0.15.9 2025-08-08 14:53:58 +02:00
Giordano Ricci
ac5d20d159 Revert "Merge pull request #2709 from garden-co/revert-2682-gio/usage-metering"
This reverts commit b3d1ad7201, reversing
changes made to fbc29f2f17.
2025-08-08 12:35:16 +01:00
Guido D'Orsi
21bcaabd5a chore: update failing snapshot 2025-08-08 13:13:45 +02:00
Guido D'Orsi
17b4d5b668 chore: update failing snapshot 2025-08-08 13:12:51 +02:00
Guido D'Orsi
3cd15862d5 fix: handle missing child groups when rotating key 2025-08-08 13:11:40 +02:00
Guido D'Orsi
b3d1ad7201 Merge pull request #2709 from garden-co/revert-2682-gio/usage-metering
Revert "feat: add ingress/egress metering on cojosn-transport-ws"
2025-08-08 12:32:58 +02:00
Guido D'Orsi
d87df11795 fix: fallback to the latest available readkey when key_for_everyone was not being revealed when everyone has access 2025-08-08 12:16:55 +02:00
Giordano Ricci
82c2a62b2a Revert "feat: add ingress/egress metering on cojosn-transport-ws" 2025-08-08 11:03:48 +01:00
Guido D'Orsi
0a9112506e fix: fixes cilrcular import issue on group.test.ts 2025-08-08 11:40:24 +02:00
Giordano Ricci
fbc29f2f17 Merge pull request #2682 from garden-co/gio/usage-metering 2025-08-08 11:16:59 +02:00
Brad Anderson
3915bbbf3c fix: update tests due to sync protocol improvements 2025-08-06 17:08:00 -04:00
Brad Anderson
0b471c4e89 fix: undo organizeImports that broke tests - jazz-tools 2025-08-06 12:34:29 -04:00
Brad Anderson
09077d37ef chore: code-quality version bump, biome to catalog for examples 2025-08-06 10:40:42 -04:00
Brad Anderson
afe06b4fa6 chore: format-and-lint:fix 2025-08-06 10:29:38 -04:00
Brad Anderson
d89b6e488a chore: bump biome version to 2.1.3 2025-08-06 10:26:33 -04:00
Guido D'Orsi
f6361ee43b Merge pull request #2703 from didier/patch-2
Update Svelte setup doc to be more accurate for Svelte 5
2025-08-06 15:41:16 +02:00
Guido D'Orsi
726dbfb6df fix: heal groups with missing key for everyone 2025-08-06 13:44:10 +02:00
Guido D'Orsi
267f689f10 fix: skip rotateKey when everyone has read access 2025-08-06 12:26:36 +02:00
Giordano Ricci
893ad3ae23 comment out flaky assertion 2025-08-06 12:25:06 +02:00
Giordano Ricci
f5590b1be8 remove duplicated import 2025-08-06 12:14:10 +02:00
Giordano Ricci
17a01f57e8 move utils 2025-08-06 12:12:08 +02:00
Giordano Ricci
7318d86f52 Merge branch 'main' into gio/usage-metering 2025-08-06 12:05:05 +02:00
Didier Catz
1c8403e87a Update to new schema syntax instead of classes 2025-08-06 10:23:35 +02:00
Didier Catz
dd747c068a Use consistent quotes / semis 2025-08-06 00:30:00 +02:00
Didier Catz
1f0f230fe2 Newline 2025-08-06 00:28:37 +02:00
Didier Catz
da655cbff5 Typo 2025-08-06 00:24:45 +02:00
Didier Catz
02f6c6220e Update svelte.mdx 2025-08-06 00:10:31 +02:00
Didier Catz
0755cd198e Update svelte.mdx 2025-08-06 00:02:30 +02:00
Didier Catz
c4a8227b66 Update Svelte setup doc to be more accurate for Svelte 5 2025-08-06 00:00:55 +02:00
Giordano Ricci
86f0302233 add meta 2025-08-05 13:13:06 +01:00
Sammii
a5ece15797 adding defaults to button 2025-08-05 12:04:41 +01:00
Sammii
9f8877202e creating color-highlight var in quint 2025-08-05 10:56:08 +01:00
Sammii
d190097ed9 creating tempory nav 2025-08-05 10:55:16 +01:00
Sammii
9841617c66 adding colours to homepage 2025-08-05 10:54:56 +01:00
Guido D'Orsi
165a6170cd Merge pull request #2700 from garden-co/changeset-release/main
Version Packages
2025-08-04 21:13:54 +02:00
github-actions[bot]
5148419df9 Version Packages 2025-08-04 19:11:13 +00:00
Guido D'Orsi
fc0ecb0968 chore: changeset 2025-08-04 21:07:48 +02:00
Guido D'Orsi
802b5a3060 chore: changeset 2025-08-04 21:06:23 +02:00
Guido D'Orsi
e47af262b3 Merge pull request #2673 from garden-co/feat/storage-wal
fix: ensure that transactions are synced in the correct order
2025-08-04 20:54:53 +02:00
Sammii
688a4850a4 add svg sizes to button and amend icons docs page 2025-08-04 16:09:39 +01:00
Sammii
e87fef751e remove old icon pages 2025-08-04 16:08:01 +01:00
Sammii
8f714440f8 create icons page 2025-08-04 16:07:28 +01:00
Sammii
70cd09170e updating button docs page 2025-08-04 16:02:28 +01:00
Guido D'Orsi
e98b610fd0 Merge pull request #2698 from garden-co/feat/comap-pick-and-partial
feat: Add `.pick()` and `.partial()` methods to CoMapSchema
2025-08-04 14:53:38 +02:00
Guido D'Orsi
b554983558 Merge pull request #2699 from garden-co/fix/extend-circular-check
fix: fixes error when extending a group without having child groups loaded
2025-08-04 14:53:15 +02:00
Guido D'Orsi
4c63334299 chore: add comments 2025-08-04 14:48:17 +02:00
Guido D'Orsi
4aef7cdac5 Update .changeset/ten-cobras-fetch.md
Co-authored-by: Joe Innes <joe@joeinn.es>
2025-08-04 14:39:26 +02:00
Guido D'Orsi
76adeb0d53 chore: clean up implementation 2025-08-04 14:03:51 +02:00
Guido D'Orsi
d95dcbe7db fix: align pick to the Zod API 2025-08-04 13:24:44 +02:00
Guido D'Orsi
f9d538f049 fix: fixes error when extending a group without having child groups loaded 2025-08-04 12:37:53 +02:00
Guido D'Orsi
40c7336c09 chore: update lucide-react 2025-08-04 11:21:18 +02:00
Guido D'Orsi
e0d2723615 fix: router update when calling navitate 2025-08-04 11:17:49 +02:00
Guido D'Orsi
93e68c62f5 docs: fix a missing type alias 2025-08-04 10:52:06 +02:00
Guido D'Orsi
dadee9dcc5 test: fix flaky test 2025-08-04 10:40:03 +02:00
Guido D'Orsi
6724c4bd83 feat: add docs, remove lodash-es dependency and add tests for recursive types with pick and partial 2025-08-04 10:34:43 +02:00
NicoR
1942bd5de4 Replace lodash with lodash-es 2025-08-04 01:44:27 -03:00
NicoR
16764f6365 Add changeset 2025-08-04 01:23:00 -03:00
NicoR
b56cfc2e1f Add TS docs 2025-08-04 01:21:32 -03:00
NicoR
7091bcf9c0 Add CoMapSchema.partial 2025-08-04 01:17:25 -03:00
NicoR
436cbfa095 Add CoMapSchema.pick 2025-08-04 01:00:57 -03:00
Guido D'Orsi
c19a25f928 feat(chat): implement lazy-loading 2025-08-03 17:20:46 +02:00
Guido D'Orsi
104e664bbb fix: fix build errors on music player 2025-08-03 17:20:15 +02:00
Guido D'Orsi
f199b451eb chore: use inline JSON when creating covalues 2025-08-03 17:09:02 +02:00
Guido D'Orsi
70bc48458e Merge pull request #2695 from garden-co/feat/music-player-refresh
docs: exclude upgrade guides from llm.txt
2025-08-02 14:37:56 +02:00
Guido D'Orsi
f28b2a6135 docs: exclude upgrade guides 2025-08-02 14:36:55 +02:00
Guido D'Orsi
55b770b7c9 Merge pull request #2694 from garden-co/feat/music-player-refresh
feat: improve the music player UI
2025-08-02 14:29:35 +02:00
Guido D'Orsi
e6838dfb98 feat: make the music-player a PWA 2025-08-02 14:22:36 +02:00
Guido D'Orsi
5e34061fdc feat: improve the music player UI 2025-08-02 14:19:24 +02:00
Guido D'Orsi
6d9b77195a chore: clean up code 2025-08-02 12:36:56 +02:00
Guido D'Orsi
9bf7946ee6 feat: added a TTL-based garbage collection 2025-08-01 19:58:11 +02:00
Guido D'Orsi
acecffaeb2 test: fix flaky tests on the created and update time 2025-08-01 19:57:33 +02:00
Nico Rainhart
0a98d6aaf2 Merge pull request #2691 from garden-co/changeset-release/main
Version Packages
2025-08-01 12:57:33 -03:00
github-actions[bot]
4ea1a63a0a Version Packages 2025-08-01 15:47:51 +00:00
Nico Rainhart
41a4c3bc95 Merge pull request #2683 from garden-co/feat/json-create-and-set
feat: create CoValues using plain JSON objects
2025-08-01 12:45:30 -03:00
Guido D'Orsi
60d0027f9d Merge pull request #2690 from garden-co/docs/useCoState-jsDoc
docs: adds jsDocs to useCoState and useAccount react hooks
2025-08-01 16:12:39 +02:00
Guido D'Orsi
748c2ff751 Merge pull request #2688 from garden-co/changeset-release/main
Version Packages
2025-08-01 15:46:41 +02:00
github-actions[bot]
70938b0ab3 Version Packages 2025-08-01 13:42:25 +00:00
Guido D'Orsi
f2f5b55dbf Update packages/jazz-tools/src/react-core/hooks.ts
Co-authored-by: Nico Rainhart <nmrainhart@gmail.com>
2025-08-01 15:42:24 +02:00
Guido D'Orsi
3c3acae803 Merge pull request #2689 from joeinnes/2687-fix-broken-link
Fix HTTP API link in inbox.mdx. Fixes #2687
2025-08-01 15:40:07 +02:00
Guido D'Orsi
896ee3460f docs: adds jsDocs to useCoState and useAccount react hooks 2025-08-01 15:37:02 +02:00
Joe Innes
9b9bf44e2b Fix HTTP API link in inbox.mdx. Fixes #2687 2025-08-01 15:13:33 +02:00
Guido D'Orsi
392aa88d95 Merge pull request #2655 from joeinnes/docs/optional-references
Docs/optional references
2025-08-01 13:15:20 +02:00
Joe Innes
7ce82cd934 Merge branch 'main' into docs/optional-references 2025-08-01 13:13:26 +02:00
Guido D'Orsi
0c8158b91c Merge pull request #2676 from Gabrola/fix/jazz-run-exports
fix: jazz-run package.json exports
2025-08-01 13:07:30 +02:00
Guido D'Orsi
5a48c9c44c chore: improve tests titles and add comments 2025-08-01 10:14:24 +02:00
Guido D'Orsi
25c56146f5 Merge pull request #2686 from garden-co/test/logout-state
test: logout integration tests on browser
2025-08-01 09:57:18 +02:00
NicoR
c564fbb02e test: add permission tests for creating nested CoValues from JSON 2025-07-31 15:03:25 -03:00
Guido D'Orsi
12481e14c2 test: logout integration tests on browser 2025-07-31 18:56:37 +02:00
NicoR
fd2d247ff5 docs: improve examples 2025-07-31 13:08:50 -03:00
NicoR
9e9ea029b2 fix: move alert out of CodeGroup 2025-07-30 17:32:56 -03:00
NicoR
a0da272dcd fix: add missing import in docs 2025-07-30 16:51:23 -03:00
NicoR
72fbcc3262 chore: remove unnecessary import from form example 2025-07-30 15:52:07 -03:00
NicoR
f4c8cc858b docs: add sections for creating CoValues from JSON and permissions 2025-07-30 15:48:55 -03:00
Anselm
0ab4d7a20d Update meta description 2025-07-30 11:34:54 -07:00
Giordano Ricci
5c98ff4e4f use object.values 2025-07-30 19:24:45 +01:00
NicoR
4cbda689c4 refactor: update form example to use new API 2025-07-30 14:54:08 -03:00
NicoR
771b0ed914 fix: cannot create empty plain text when nested 2025-07-30 14:53:38 -03:00
NicoR
79913c3136 fix: simplify CoMapInit schema 2025-07-30 13:37:10 -03:00
NicoR
43d3511d15 Add changeset 2025-07-30 12:40:25 -03:00
NicoR
928ef14086 feat: support deeply nested optional primitive fields 2025-07-30 12:33:28 -03:00
NicoR
048dd7def0 feat: support deeply nested optional CoValue fields 2025-07-30 12:33:09 -03:00
Guido D'Orsi
51fcb8a44b test: improve the client subscription test 2025-07-30 17:29:01 +02:00
Guido D'Orsi
c5888c39f5 perf: update parent before updating children to favor batching 2025-07-30 17:14:45 +02:00
Guido D'Orsi
2defcfae67 test: mark retry unavailable states as flaky 2025-07-30 17:10:50 +02:00
NicoR
873b146d15 feat: create a child group for each created CoValue 2025-07-30 11:03:38 -03:00
Guido D'Orsi
213de11c3b feat: preserve transaction order on sync 2025-07-30 15:37:58 +02:00
Sammii
2f24d35471 amending comments for button tv 2025-07-30 12:49:49 +01:00
Sammii
42667c81bb imrprove icon docs 2025-07-30 12:42:36 +01:00
Giordano Ricci
1b881cc89f cleanup tests 2025-07-30 12:00:54 +01:00
Guido D'Orsi
af295d816a chore: add comments and rename CoValueSyncQueue in LocalTransactionsSyncQueue 2025-07-30 12:54:46 +02:00
Guido D'Orsi
fe8d3497c0 chore: fix the peer attribution on storage corrections tests 2025-07-30 12:37:19 +02:00
Giordano Ricci
c2899e94ca add changeset 2025-07-30 11:36:17 +01:00
Giordano Ricci
f4be67e9b6 Merge branch 'main' into gio/usage-metering 2025-07-30 11:34:37 +01:00
Guido D'Orsi
ba9ad295b6 fix: don't consider -1 as a valid signature checkpoint 2025-07-30 12:32:07 +02:00
Giordano Ricci
9ed5a96ef8 lockfile update 2025-07-30 11:28:44 +01:00
Giordano Ricci
4272ea9019 refactor: use getTransactionSize util 2025-07-30 11:28:17 +01:00
Giordano Ricci
9509307ed1 cleanup and add egress tests 2025-07-30 11:27:24 +01:00
Giordano Ricci
be08921bc5 cleanup and add ingress tests 2025-07-30 11:01:10 +01:00
Sammii
77e3c21cbd format globals css 2025-07-30 10:58:18 +01:00
NicoR
ab1798c7bd feat: make CoValue creation from JSON type safe 2025-07-29 16:41:48 -03:00
NicoR
26ae69a242 refactor: reuse TypeOfZodSchema 2025-07-29 12:44:42 -03:00
Giordano Ricci
25be055a51 wip: basica ingress/egress metering 2025-07-29 16:39:34 +01:00
NicoR
21ad3767b9 refactor: avoid InstanceOrPrimitiveOfSchemaCoValuesNullable duplication 2025-07-29 11:48:46 -03:00
NicoR
a9383516c1 refactor: avoid InstanceOrPrimitiveOfSchema duplication 2025-07-29 11:38:09 -03:00
NicoR
bffc516c68 refactor: extract TypeOfZodSchema util 2025-07-29 11:16:14 -03:00
NicoR
9e7c0d9887 chore: clean up instantiateRefEncodedWithInit's implementation 2025-07-29 10:56:32 -03:00
NicoR
99b44d5780 feat: create CoMap with JSON discriminated union fields 2025-07-29 10:36:33 -03:00
NicoR
02db5f3b1d feat: create CoMap with JSON CoFeed fields 2025-07-29 09:13:01 -03:00
NicoR
1949a5fcd9 feat: create CoMap with JSON CoList fields 2025-07-28 17:15:43 -03:00
NicoR
dcd3b022cc feat: create CoMap with JSON plain and rich text fields 2025-07-28 16:56:43 -03:00
NicoR
a7b837c7e1 feat: create CoMap with JSON CoMap fields 2025-07-28 16:47:51 -03:00
Anselm Eickhoff
88ebcf58ab Merge pull request #2680 from garden-co/jazz-as-a-db
Jazz as a DB narrative MVP
2025-07-28 11:34:01 -07:00
Guido D'Orsi
b173e0884a feat: improve local transactions streaming calculation 2025-07-28 19:45:31 +02:00
Anselm
f379a168be Update garden co slogan 2025-07-28 10:30:18 -07:00
Anselm
bde6ac7d45 Jazz as a DB narrative MVP 2025-07-28 10:08:56 -07:00
Guido D'Orsi
231947c97a fix(sync): start a new content message when the size exceeds the recommended value 2025-07-28 18:44:13 +02:00
Guido D'Orsi
d1609cdd55 Merge pull request #2679 from garden-co/changeset-release/main
Version Packages
2025-07-28 18:12:46 +02:00
Guido D'Orsi
d5b57ad1fc fix: fix priority for content 2025-07-28 17:53:33 +02:00
github-actions[bot]
b71ab3168a Version Packages 2025-07-28 15:15:40 +00:00
Nico Rainhart
0c8f6e5039 Merge pull request #2677 from garden-co/feat/add-nullable-support
feat: Add support for nullable non-collaborative fields
2025-07-28 12:12:12 -03:00
Guido D'Orsi
0bf5c53bec fix: disable code coverage check on CI 2025-07-28 16:59:02 +02:00
Guido D'Orsi
e7b1550003 feat: perserve insert order when storing transactions on multiple covalues 2025-07-28 16:59:02 +02:00
NicoR
6a93a1b8a3 chore: add comment on why nullable date cofields are not supported 2025-07-28 11:52:44 -03:00
NicoR
9f654a2603 test: loading a map with a nullable field 2025-07-28 11:46:50 -03:00
NicoR
dbf735d9e1 Fix rebase error 2025-07-28 10:27:44 -03:00
NicoR
c62abefb66 Add changeset 2025-07-28 10:19:55 -03:00
NicoR
1453869a46 Add support for nullable non-collaborative fields 2025-07-28 10:18:39 -03:00
Sammii
f5039cefc1 addig icon button to docs page and icons pagr tidy 2025-07-28 14:09:46 +01:00
Youssef Gaber
239da90c9f chore: changeset 2025-07-28 17:06:01 +04:00
Youssef Gaber
972791e7a8 fix: correct jazz-run package.json exports 2025-07-28 17:05:45 +04:00
Sammii
6540893caf adding default, strong and muted to css 2025-07-28 13:51:23 +01:00
Sammii
bfc85c4573 refactoring icon and icon page 2025-07-28 13:50:57 +01:00
Sammii
e9076313ab amending Button page, adding title 2025-07-28 13:50:44 +01:00
Sammii
c6afd8ae36 adding placeholder favicon 2025-07-28 13:40:52 +01:00
Sammii
370f20d13d refactoring css and button component 2025-07-28 13:40:31 +01:00
NicoR
0c0178764e chore: fix rebase errors 2025-07-28 09:34:24 -03:00
NicoR
928350b821 refactor: rename OptionalizeUndefinedKeys to PartialOnUndefined 2025-07-28 09:34:23 -03:00
NicoR
be3fd9c696 test: create CoMap with shallowly resolved CoValue 2025-07-28 09:34:23 -03:00
NicoR
269c028df0 test: add test for CoMapSchema + catchall 2025-07-28 09:34:23 -03:00
NicoR
e4df837138 refactor: rename CoMapInitZod to CoMapSchemaInit 2025-07-28 09:34:23 -03:00
NicoR
54fe6d93ba refactor: extract CoMapSchema.create's return type 2025-07-28 09:34:23 -03:00
NicoR
979689c6d8 refactor: improve CatchAll type handling in CoMapSchemas 2025-07-28 09:34:23 -03:00
NicoR
859a37868f refactor: simplify CoMapSchema.create's return type 2025-07-28 09:34:23 -03:00
NicoR
57bd32d77e refactor: simplify the type of CoMapSchema.create's init parameter 2025-07-28 09:34:23 -03:00
Sammii
f9b3116deb adding custom color steps to all tailwind css colours in design system 2025-07-28 13:10:42 +01:00
Sammii
352d34979f create icon component 2025-07-25 17:34:29 +01:00
Sammii
7ff736ace4 improving gradient on muted, default and strong intent buttons 2025-07-25 16:23:23 +01:00
Sammii
5bab466fd0 adding default/hover/active states for all intents 2025-07-25 16:20:55 +01:00
Sammii
329b8c3d6a switching muted and default styles 2025-07-25 15:29:01 +01:00
Nico Rainhart
e21cbccd4b Merge pull request #2674 from garden-co/changeset-release/main
Version Packages
2025-07-25 11:07:46 -03:00
github-actions[bot]
a66ab7d174 Version Packages 2025-07-25 13:34:52 +00:00
Nico Rainhart
78e91f4030 Merge pull request #2667 from garden-co/0-16
Jazz 0.16 upgrade
2025-07-25 10:32:40 -03:00
Guido D'Orsi
7a915c198e Merge pull request #2657 from garden-co/fix/root-trusting
feat: store the root id unencrypted in account
2025-07-25 14:39:59 +02:00
Guido D'Orsi
c9b0420746 Merge pull request #2672 from garden-co/chore/fix-conflicts-between-http-api-and-schema-refactoring
chore: Fix conflicts between HTTP requests and CoValue schema refactor
2025-07-25 14:39:16 +02:00
NicoR
2303f3e70a fix: schema definition error in server-http-worker example 2025-07-25 09:31:47 -03:00
NicoR
a7bc9569a3 chore: Fix conflicts between HTTP requests and CoValue schema refactor 2025-07-25 09:19:15 -03:00
NicoR
f351ba0fcd Merge remote-tracking branch 'origin/main' into chore/fix-conflicts-between-http-api-and-schema-refactoring 2025-07-25 09:12:05 -03:00
Guido D'Orsi
d3e554f491 Merge pull request #2671 from garden-co/chore/queues
chore: move cojson queues in a dedicated directory
2025-07-25 13:03:33 +02:00
Guido D'Orsi
b5e31456ad chore: trigger deploy 2025-07-25 12:25:05 +02:00
Sammii
c0aeb7baf9 porting over variant/intent styles 2025-07-25 11:21:17 +01:00
Sammii
8a14de10d7 fix(quint-ui): correct hover and active states for button variants 2025-07-25 11:13:39 +01:00
Sammii
b585b39a86 porting over glass styles with specular borders 2025-07-25 10:15:57 +01:00
Guido D'Orsi
42d07ba7b4 docs: upgrade docs for account root id 2025-07-25 11:13:40 +02:00
Guido D'Orsi
b81b6ba69b Merge remote-tracking branch 'origin/0-16' into fix/root-trusting 2025-07-25 10:54:21 +02:00
Guido D'Orsi
1bc1759bb4 Merge remote-tracking branch 'origin/main' into chore/queues 2025-07-25 10:51:43 +02:00
Guido D'Orsi
225bc1f63f Merge pull request #2670 from garden-co/changeset-release/main
Version Packages
2025-07-25 10:37:04 +02:00
github-actions[bot]
5d94564f99 Version Packages 2025-07-25 08:30:50 +00:00
Guido D'Orsi
9633d0187f chore: changeset 2025-07-25 10:28:31 +02:00
Guido D'Orsi
b82ecaa3ca docs: add server worker http example card 2025-07-25 10:23:16 +02:00
Guido D'Orsi
111ec8d351 Merge pull request #2626 from garden-co/feat/http-requests
RPC style HTTP requests with CoValues
2025-07-25 10:18:10 +02:00
Nico Rainhart
512aacdbc2 Merge pull request #2669 from garden-co/fix/simplify-circular-constraint
fix: circular constraint type check error with `Simplify`
2025-07-24 17:07:53 -03:00
NicoR
7ad843aa3e fix: circular constraint type check error with Simplify 2025-07-24 16:18:21 -03:00
Guido D'Orsi
071128339b docs: fix typo 2025-07-24 21:03:55 +02:00
Guido D'Orsi
688ced499d feat: ux improvments on http example 2025-07-24 18:59:33 +02:00
Guido D'Orsi
ac91c8e7c2 Merge remote-tracking branch 'origin/main' into feat/http-requests 2025-07-24 18:23:00 +02:00
Guido D'Orsi
c3b303c310 feat: track handled messages in the request envelope 2025-07-24 18:21:23 +02:00
Guido D'Orsi
fc027a56db Merge pull request #2650 from garden-co/refactor/covalue-zod-schema-boundary
refactor: CoValue schemas are no longer Zod schemas
2025-07-24 17:41:09 +02:00
Guido D'Orsi
959a7a3927 Merge branch '0-16' into refactor/covalue-zod-schema-boundary 2025-07-24 17:40:51 +02:00
NicoR
2548085b59 Update upgrade guide and docs with improved support for recursive refs 2025-07-24 12:24:33 -03:00
Guido D'Orsi
b27bb3e65b test: remove .only 2025-07-24 17:21:04 +02:00
Guido D'Orsi
937284f7e9 Merge pull request #2666 from garden-co/changeset-release/main
Version Packages
2025-07-24 17:16:01 +02:00
github-actions[bot]
e999727c70 Version Packages 2025-07-24 14:58:34 +00:00
Guido D'Orsi
2197766624 Merge pull request #2665 from garden-co/fix/optional-ref-assign
fix: property update when assigning an optional reference on CoMap
2025-07-24 16:56:05 +02:00
Guido D'Orsi
d1efde468f Merge remote-tracking branch 'origin/main' into fix/root-trusting 2025-07-24 16:55:31 +02:00
Guido D'Orsi
4d4fd0beaa chore: use import content instead of copy on acceptInvite 2025-07-24 16:55:19 +02:00
Guido D'Orsi
2b61e853a7 test: cover recursive references without explicit return type 2025-07-24 16:43:35 +02:00
Guido D'Orsi
6f79b45544 chore: move cojson queues in a dedicated directory 2025-07-24 16:23:58 +02:00
Guido D'Orsi
2e1ff99579 chore: use import content instead of copy on acceptInvite 2025-07-24 16:04:43 +02:00
Guido D'Orsi
7361854ee4 chore: add isCoValueId check 2025-07-24 16:03:41 +02:00
Guido D'Orsi
4a775fada3 chore: remove unused import 2025-07-24 15:57:20 +02:00
Guido D'Orsi
3fe53a3a4a fix: property update when assigning an optional reference on CoMap 2025-07-24 12:19:48 +02:00
Guido D'Orsi
fe37516786 chore: remove TODO from play route 2025-07-24 12:18:43 +02:00
Guido D'Orsi
4beafb7cf3 fix: property update when assigning an optional reference on CoMap 2025-07-24 12:16:35 +02:00
Guido D'Orsi
82a592e08a feat(ci): add server-worker-http to ci tests 2025-07-24 11:45:15 +02:00
Guido D'Orsi
4c6926153a chore: simplify groups in createPlayer 2025-07-24 11:45:15 +02:00
Guido D'Orsi
c51b088243 Update examples/server-worker-http/src/apiKey.ts
Co-authored-by: Nico Rainhart <nmrainhart@gmail.com>
2025-07-24 11:36:55 +02:00
Guido D'Orsi
867cb6b7a5 Merge pull request #2659 from garden-co/changeset-release/main
Version Packages
2025-07-23 18:30:51 +02:00
Guido D'Orsi
3a1fdd7600 docs: remove per-user limits exception 2025-07-23 18:28:30 +02:00
Guido D'Orsi
3fdbb43b54 docs: added the security safeguards provided by Jazz 2025-07-23 18:20:04 +02:00
Guido D'Orsi
02969ee89b docs: pr feedback 2025-07-23 17:49:39 +02:00
Sammii
e9b2860e74 button tv refactor 2025-07-23 16:32:07 +01:00
Guido D'Orsi
9b4988a514 docs: improve deploy requirements docs 2025-07-23 17:27:15 +02:00
Sammii
6327d74f68 mapping over button suite from old design system to quint 2025-07-23 16:24:19 +01:00
Guido D'Orsi
8aa4b59d49 chore: fix typo
Co-authored-by: Nico Rainhart <nmrainhart@gmail.com>
2025-07-23 16:39:27 +02:00
NicoR
ac782674de One more Upgrade guide tweak 2025-07-23 11:24:14 -03:00
Sammii
bedbabdcb4 styling the layout of quint docs 2025-07-23 15:18:48 +01:00
NicoR
5eb406d54d Upgrade guide adjustments 2025-07-23 11:03:47 -03:00
NicoR
a3be832414 Remove WithHelpers export 2025-07-23 10:55:35 -03:00
NicoR
7ca8dd960e Merge branch 'main' into refactor/covalue-zod-schema-boundary 2025-07-23 10:50:08 -03:00
NicoR
62c8aff73f Fix Upgrade guide navigation 2025-07-23 10:24:53 -03:00
NicoR
7731109a28 Address Upgrade guide comments 2025-07-23 10:08:14 -03:00
github-actions[bot]
0401fcf2a8 Version Packages 2025-07-23 12:43:56 +00:00
Nico Rainhart
139a649279 Merge pull request #2663 from garden-co/fix/export-WithHelpers-type
fix: Export `WithHelpers` type
2025-07-23 09:41:20 -03:00
NicoR
9acccb5df2 Add changeset 2025-07-23 09:23:12 -03:00
NicoR
fd90cdb49a fix: Export WithHelpers type 2025-07-23 09:15:09 -03:00
Guido D'Orsi
4a29999c6a feat: support worker groups 2025-07-23 14:07:09 +02:00
Giordano Ricci
df487d5335 Merge pull request #2658 from garden-co/gio/quint 2025-07-23 12:35:06 +01:00
Giordano Ricci
1efe84c691 Merge branch 'main' into gio/quint 2025-07-23 12:17:02 +01:00
Guido D'Orsi
73b99c6c1a docs: http request jsDoc 2025-07-23 13:07:51 +02:00
Guido D'Orsi
039b92c839 feat: export API and jsdocs 2025-07-23 13:03:58 +02:00
Guido D'Orsi
618af5f1e3 feat: e2e tests and improve UX on server-worker-http 2025-07-23 12:50:48 +02:00
NicoR
dfc4286694 Return resolved resized images on createImage 2025-07-22 15:15:02 -03:00
NicoR
970ff0d813 Fix docs 2025-07-22 14:57:57 -03:00
Guido D'Orsi
65eee0ef01 docs: update the server-worker-http readme 2025-07-22 18:39:11 +02:00
NicoR
eee221f563 Merge branch 'main' into refactor/covalue-zod-schema-boundary 2025-07-22 13:38:47 -03:00
Guido D'Orsi
063553090e docs: fix type errors on co.profile 2025-07-22 18:29:18 +02:00
Guido D'Orsi
97f6bcedbd test: remove failing test 2025-07-22 18:24:07 +02:00
Guido D'Orsi
7c63e6bb0f docs: mention jazz-run account create defaults 2025-07-22 18:07:06 +02:00
Guido D'Orsi
08aedcf517 docs: fix typos 2025-07-22 17:53:08 +02:00
Guido D'Orsi
3e12ee127f docs: cover the inbox API 2025-07-22 17:52:06 +02:00
NicoR
2283d375ef Update docs 2025-07-22 12:17:49 -03:00
NicoR
202e763380 Merge branch 'main' into refactor/covalue-zod-schema-boundary 2025-07-22 12:07:57 -03:00
NicoR
52bbdb37a9 Add .optional() to all CoValue schemas 2025-07-22 12:06:21 -03:00
Guido D'Orsi
96f743b2f4 Merge remote-tracking branch 'origin/main' into feat/http-requests 2025-07-22 16:38:26 +02:00
NicoR
f5c47feeb6 Upgrade Zod to 3.25.76 instead 2025-07-22 11:33:01 -03:00
Guido D'Orsi
6dffe73bd2 chore: rename jazz-paper-scissors in server-worker-inbox 2025-07-22 16:32:08 +02:00
Guido D'Orsi
6f9ee31179 feat: rename serve-worker examples 2025-07-22 16:23:05 +02:00
Guido D'Orsi
52f324ffc4 chore: update lockfile 2025-07-22 16:21:34 +02:00
Guido D'Orsi
2d86f53575 Merge remote-tracking branch 'origin/main' into feat/http-requests 2025-07-22 16:20:49 +02:00
Guido D'Orsi
68cb357a94 Merge pull request #2660 from garden-co/feat/persistent-peers
feat: introduce persistent peers
2025-07-22 16:18:56 +02:00
Guido D'Orsi
56ccf9ab9d feat: make it possible to define API with empty responses 2025-07-22 15:57:57 +02:00
NicoR
b8b0851433 Add upgrade guide 2025-07-22 10:42:46 -03:00
NicoR
2bbb07b0bf Add changeset 2025-07-22 10:01:00 -03:00
Guido D'Orsi
4f7bc91502 chore: apply suggestions from code review
Co-authored-by: Nico Rainhart <nmrainhart@gmail.com>
2025-07-22 14:40:45 +02:00
NicoR
d3053955d8 Update docs 2025-07-22 09:34:40 -03:00
Giordano Ricci
f61a120560 changeset 2025-07-22 12:22:22 +01:00
Giordano Ricci
2f1307a0ba rename package 2025-07-22 12:21:19 +01:00
Giordano Ricci
fa15ea56d1 build & dev workflows 2025-07-22 12:03:21 +01:00
Guido D'Orsi
1e58ecb3ac test: integration tests for browser and workers on offline loading & sync 2025-07-22 12:42:49 +02:00
Giordano Ricci
ceeabfaf89 strip down PR 2025-07-22 11:07:52 +01:00
Guido D'Orsi
70ce7c5736 feat: introduce persistent peers 2025-07-22 11:12:48 +02:00
NicoR
f40484eca9 Migrate plain text, rich text, file stream and optional schemas to classes 2025-07-21 17:00:57 -03:00
NicoR
d581a59aa1 Convert CoDiscriminatedUnionSchema into a class 2025-07-21 16:39:43 -03:00
NicoR
0ca09f75c1 Convert CoFeedSchema into a class 2025-07-21 16:30:04 -03:00
NicoR
e8fcd101f2 Convert CoListSchema into a class 2025-07-21 16:24:35 -03:00
NicoR
cf43fa7529 Remove usage of withHelpers 2025-07-21 15:23:36 -03:00
NicoR
df1cdda4e8 Update zod version in all examples 2025-07-21 15:15:51 -03:00
Guido D'Orsi
be46042cdc chore: fix typos and errors 2025-07-21 20:12:39 +02:00
Nico Rainhart
6afdb16739 Merge pull request #2656 from garden-co/feat/prevent-resolving-discriminated-union-fields
feat: Prevent resolving discriminated union fields
2025-07-21 14:45:51 -03:00
NicoR
7a60d7bb76 Avoid NotNull duplication 2025-07-21 14:33:22 -03:00
NicoR
f8263a8358 Stop extending Zod schemas when creating core CoValue schemas 2025-07-21 14:25:23 -03:00
NicoR
f6da966922 Explain "core" CoValue schema / actual CoValue schema distinction 2025-07-21 14:21:44 -03:00
Giordano Ricci
b0b2b85a6f fix lint issues 2025-07-21 18:13:18 +01:00
NicoR
8a2ab51543 Expose internal schemas for co.map, co.list, co.optional and co.account 2025-07-21 14:09:48 -03:00
Giordano Ricci
28c19c134f Merge branch 'main' into gio/quint 2025-07-21 18:09:42 +01:00
Giordano Ricci
0924c9baaa revert changes to example app 2025-07-21 18:04:17 +01:00
Guido D'Orsi
8bfaa0a18b docs: document deployments 2025-07-21 19:00:19 +02:00
Giordano Ricci
b2712e18a2 fix imports 2025-07-21 17:46:15 +01:00
Giordano Ricci
66894b63d7 more cleanup 2025-07-21 17:43:11 +01:00
Guido D'Orsi
147be76399 test: cover deep response sharing 2025-07-21 16:51:24 +02:00
Guido D'Orsi
36770bed52 fix: limit the content pieces sent to server to the envelope 2025-07-21 16:45:40 +02:00
NicoR
466e6c44ee Remove unused imports 2025-07-21 11:43:11 -03:00
NicoR
5bd8277161 Remove withHelpers schema method 2025-07-21 11:41:28 -03:00
Giordano Ricci
b1a05143e3 cleanup 2025-07-21 15:26:49 +01:00
Giordano Ricci
fb761ce66d cleanup 2025-07-21 15:25:09 +01:00
Giordano Ricci
07a6c340dc some cleanup 2025-07-21 15:23:02 +01:00
Guido D'Orsi
8b4261f7d8 docs: pr feedback 2025-07-21 16:13:59 +02:00
NicoR
0ec917e453 Remove non-namespaced CoValue schema exports 2025-07-21 10:42:23 -03:00
NicoR
6326d0fc45 Export co.Image type 2025-07-21 10:30:55 -03:00
NicoR
d746b1279a Remove deprecated createCoValueObservable function 2025-07-21 10:18:54 -03:00
Giordano Ricci
0fea904dd0 tailwind class source 2025-07-21 14:04:39 +01:00
Giordano Ricci
373aef313f wip: quint 2025-07-21 13:55:21 +01:00
Guido D'Orsi
c09dcdfc76 feat: make the root trusting 2025-07-21 12:06:28 +02:00
NicoR
a584590ed8 Add changeset 2025-07-18 18:00:50 -03:00
NicoR
0a830e29a9 Prevent resolving discriminated union fields 2025-07-18 17:57:07 -03:00
NicoR
4402c553b6 Fix z.object bug with cyclic references 2025-07-18 15:44:54 -03:00
NicoR
e76fe343da Fix co.discriminatedUnion with cyclic references 2025-07-18 15:10:53 -03:00
Guido D'Orsi
dc183a19b2 feat: show that the opponent has made their move 2025-07-18 19:51:10 +02:00
Guido D'Orsi
fef55a4cd6 fix: remove bad export on route 2025-07-18 19:34:47 +02:00
Guido D'Orsi
ddef54048f docs: fix type error 2025-07-18 19:26:40 +02:00
NicoR
a2626a0f38 Avoid rehydrating CoValue schemas 2025-07-18 14:21:27 -03:00
NicoR
ec579bcaf7 Go back to using tuple for discriminatedUnion's options type 2025-07-18 14:19:53 -03:00
Guido D'Orsi
8aa2d2a789 docs: fix type errors on organization pattern 2025-07-18 19:12:24 +02:00
Guido D'Orsi
a39d009b87 docs: http requests & server workers 2025-07-18 19:00:36 +02:00
Joe Innes
6b662b0efe Type fixes for twoslash 2025-07-18 15:06:03 +02:00
Guido D'Orsi
efff4d0f4f Merge pull request #2654 from garden-co/fix/vitest-type-tests
fix: restore type tests on Vitest and upgrade Vitest to v3.2.4
2025-07-18 14:13:01 +02:00
Guido D'Orsi
ea2b01d8a2 fix: restore type tests on Vitest and upgrade Vitest to v3.2.4 2025-07-18 12:51:02 +02:00
NicoR
e9af90c841 Fix Zod type messing up Zod's type inference 2025-07-17 17:06:37 -03:00
NicoR
2b7c6f5aa7 Rename anySchemaToCoSchema to coValueClassFromCoValueClassOrSchema 2025-07-17 16:55:43 -03:00
NicoR
d73a3d9d46 Rename files 2025-07-17 16:52:56 -03:00
NicoR
8af39077a3 Remove CoValueSchema.getZodSchema 2025-07-17 16:44:48 -03:00
NicoR
54bd487818 PlainText, RichText and FileStream schemas are no longer Zod schemas 2025-07-17 16:30:09 -03:00
Guido D'Orsi
f01dab5c8f test: cover requests error management 2025-07-17 20:29:02 +02:00
Joe Innes
a8b3ec7bb0 Add more detail regarding optional references
As the boundary becomes more defined between CoValue schemas and Zod schemas, we need to ensure folks pick the right `.optional()` between `co.optional()` for CoValues and `z.optional()` for primitives.
2025-07-17 20:24:03 +02:00
Guido D'Orsi
a7f6870048 chore: update package json 2025-07-17 19:43:37 +02:00
Guido D'Orsi
3b294f6994 Merge remote-tracking branch 'origin/main' into feat/http-requests 2025-07-17 19:42:56 +02:00
NicoR
a420b43029 Add CoDiscriminatedUnionSchema.optional() 2025-07-17 14:29:52 -03:00
NicoR
a57268de32 Add runtime check to prevent using z.object with coValues as values 2025-07-17 14:12:41 -03:00
NicoR
6b2c4ed280 Remove no longer necessary Zod re-export wrappers 2025-07-17 14:12:41 -03:00
NicoR
8d4e0027be Upgrade Zod to 4.0.5 2025-07-17 14:12:41 -03:00
NicoR
a4141da1b7 Drop support for z.optional CoValue schemas 2025-07-17 14:12:41 -03:00
NicoR
c9ca5202f9 Fix browser integration tests 2025-07-17 14:12:41 -03:00
NicoR
7b50a2e06d Avoid using core.$ZodTypeDiscriminable for CoDiscriminatedUnions 2025-07-17 14:12:40 -03:00
NicoR
43dabccb57 [WIP] Discriminable CoValue schemas no longer extend $ZodDiscriminatedUnion 2025-07-17 14:12:40 -03:00
NicoR
b6d04f56ef Preserve catchall type info in CoMapSchema 2025-07-17 14:12:40 -03:00
NicoR
628195b678 Remove unused types from test 2025-07-17 14:12:40 -03:00
NicoR
9a5d769717 Use CoValue schema types (instead of Zod's) in circular references 2025-07-17 14:12:40 -03:00
NicoR
e30a3f66bf CoValue schemas are no longer Zod schemas 2025-07-17 14:12:40 -03:00
NicoR
6327fce933 Refactor CoValue instances & Zod primitives type inference 2025-07-17 14:12:40 -03:00
NicoR
a650da4184 Fix bug with deeply nested discriminated unions 2025-07-17 14:12:40 -03:00
NicoR
6e4a94f6ce Avoid accessing CoDiscriminatedUnion Zod internals directly 2025-07-17 14:12:40 -03:00
NicoR
b73bec64bc Rename AnyCoSchemas to CoreCoValueSchemas 2025-07-17 14:12:40 -03:00
NicoR
50ae2f47c2 Remove CoValue schema cache 2025-07-17 14:12:40 -03:00
NicoR
724d8e7f30 Drop support for z.discriminatedUnion of CoValue schemas 2025-07-17 14:12:40 -03:00
NicoR
7b285ab110 Modify CoMap schema to no longer extend ZodObject 2025-07-17 14:12:40 -03:00
NicoR
01ac9b8c4c Rewrite tests that access Zod internals 2025-07-17 14:12:40 -03:00
NicoR
4e2e1ac73e Convert CoValue schemas into interfaces 2025-07-17 14:12:40 -03:00
NicoR
94960c1f65 Convert CoValue schemas into a discriminated union 2025-07-17 14:12:40 -03:00
NicoR
b5af58347b Add getZodSchema method to CoValue schemas 2025-07-17 14:12:40 -03:00
NicoR
46a84558c5 Clean up InstanceOfSchema types 2025-07-17 14:12:40 -03:00
NicoR
f93566c045 Replace references to z.core.$ZodType with AnyZodSchema 2025-07-17 14:12:39 -03:00
NicoR
d97ed603a3 Tighten CoOptionalSchema inner type 2025-07-17 14:12:39 -03:00
NicoR
8d33103182 Rename zodSchemaToCoSchema to coreSchemaToCoSchema 2025-07-17 14:12:39 -03:00
NicoR
aaa1ff978b Organize Schema Union types 2025-07-17 14:12:39 -03:00
NicoR
82655ea7a7 Stop exporting zodSchemaToCoSchema 2025-07-17 14:12:39 -03:00
NicoR
8afe3a2e02 Remove unnecessary usages of zodSchemaToCoSchema 2025-07-17 14:12:39 -03:00
NicoR
ae2adcbd15 Extract functions to create CoreCoSchemas 2025-07-17 14:12:39 -03:00
NicoR
eb0460d330 Revert https://github.com/garden-co/jazz/pull/2651 2025-07-17 14:12:30 -03:00
Guido D'Orsi
55cb83e6e0 Merge pull request #2652 from garden-co/changeset-release/main
Version Packages
2025-07-17 15:26:20 +02:00
github-actions[bot]
6290088fec Version Packages 2025-07-17 13:06:49 +00:00
Guido D'Orsi
b9c17b37db Merge pull request #2651 from garden-co/fix/optional-load
fix: load failures when loading a missing ref declared with z.optional and Schema.optional
2025-07-17 15:04:36 +02:00
Guido D'Orsi
6c76ff8fbf fix: load of missing z.optional and Schema.optional doesn't fail 2025-07-17 14:54:33 +02:00
Guido D'Orsi
3c6a2a6092 Merge pull request #2649 from garden-co/changeset-release/main
Version Packages
2025-07-16 13:16:14 +02:00
github-actions[bot]
e8a950e61a Version Packages 2025-07-16 10:15:20 +00:00
Guido D'Orsi
e2cbf035de Merge pull request #2648 from garden-co/fix/onAnonymous-stuck
fix: fix stuck authentication when using onAnonymousAccountDiscarded with a storage
2025-07-16 12:13:11 +02:00
Matteo Manchi
47599b6307 chore(cojson): new removeStorage method exposed on LocalNode 2025-07-16 11:19:35 +02:00
Guido D'Orsi
901d0762ee Merge pull request #2647 from garden-co/fix/GCO-646-uncaught-from-load-promise
fix: catch errors from CoValue loading and treat it as "unavailable"
2025-07-16 09:18:43 +02:00
Guido D'Orsi
d1c1b0c5cc fix: fix stuck authentication when using onAnonymousAccountDiscarded with a storage 2025-07-15 22:17:51 +02:00
Matteo Manchi
cf4ad7285d fix(jazz-tools/tools): catch errors from CoValue loading and treat it as "unavailable" 2025-07-15 21:12:21 +02:00
Guido D'Orsi
255a947ea6 feat: add error management 2025-07-15 20:22:04 +02:00
Guido D'Orsi
530a263d35 chore: add vercel.json 2025-07-15 18:42:05 +02:00
Guido D'Orsi
2983c7bd58 fix: ignore builds for server-side-validation 2025-07-15 18:41:12 +02:00
Guido D'Orsi
745020b7a8 chore: refactor code 2025-07-15 18:06:37 +02:00
Nico Rainhart
ab6328f767 Merge pull request #2645 from garden-co/changeset-release/main
Version Packages
2025-07-15 12:23:00 -03:00
github-actions[bot]
e0555debde Version Packages 2025-07-15 15:12:46 +00:00
Guido D'Orsi
247f4556e7 change the changeset to a patch 2025-07-15 17:10:44 +02:00
Nico Rainhart
7903c737f4 Merge pull request #2627 from garden-co/feat/co-optional-and-discriminatedUnion
feat: add `co.optional` and `co.discriminatedUnion`
2025-07-15 12:01:47 -03:00
NicoR
6145da5525 Update changeset 2025-07-15 11:50:55 -03:00
Nico Rainhart
fc0a2e77a3 Merge pull request #2629 from garden-co/feat/improve-zod-functions-type-safety
feat: prevent using Zod functions with CoValue schemas
2025-07-15 09:21:38 -03:00
NicoR
334fbbbb7f Comment back z.record and z.intersection until we support them 2025-07-15 09:12:18 -03:00
NicoR
eaac1e6580 Keep z.optional and z.discriminatedUnion compatible with covalues 2025-07-14 22:44:02 -03:00
Guido D'Orsi
114898d8a9 Merge pull request #2643 from garden-co/changeset-release/main
Version Packages
2025-07-14 21:25:21 +02:00
Guido D'Orsi
991aebf7a7 feat: only accept init payload and improve auth checks 2025-07-14 21:00:08 +02:00
NicoR
cbc3f0cc65 Improve changeset message 2025-07-14 15:25:04 -03:00
NicoR
29c487e288 Remove new usages of zodSchemaToCoSchema 2025-07-14 15:13:51 -03:00
NicoR
0b0590a364 Merge remote-tracking branch 'origin/main' into feat/co-optional-and-discriminatedUnion 2025-07-14 15:11:31 -03:00
NicoR
1eb01997d8 Remove unused imports 2025-07-14 14:57:42 -03:00
Nico Rainhart
0dc8d511a1 Merge pull request #2636 from garden-co/feat/zod-jazz-schema-boundary
refactor: consolidate boundary between Zod schemas, CoValue schemas & CoValue classes
2025-07-14 14:43:36 -03:00
Guido D'Orsi
9b75880b10 feat: bestEffortResolution on export 2025-07-14 18:53:44 +02:00
github-actions[bot]
962213c712 Version Packages 2025-07-14 15:04:25 +00:00
Guido D'Orsi
427df8fcbb Merge pull request #2644 from garden-co/chore/lefthook-autoinstall
chore: auto-install lefthook using `postinstall` script
2025-07-14 17:02:16 +02:00
Guido D'Orsi
98fe72ed42 fix: push players data on joinGameRequest 2025-07-14 15:00:47 +02:00
Guido D'Orsi
1f5c81c2ea feat: nicer example UI 2025-07-14 14:45:16 +02:00
Matteo Manchi
c40aad55dc chore: auto-install lefthook using postinstall script 2025-07-14 14:38:48 +02:00
Guido D'Orsi
fc41aa165b feat: support CoMapInit as payload for send/response 2025-07-14 14:29:20 +02:00
Guido D'Orsi
dfca5926de Merge pull request #2640 from garden-co/GCO-621-Adds-documentation-for-descriminatedUnion-workaround
Closes GCO-621 - Add documentation for discriminated union workaround
2025-07-14 12:50:16 +02:00
Guido D'Orsi
9815ec61f0 feat: export the z.ZodDiscriminatedUnion type and improve the recursive types docs 2025-07-14 11:14:59 +02:00
Guido D'Orsi
fca60d213e Merge pull request #2641 from garden-co/GCO-655-user-id-on-unauthorized
Expose current Account id in unauthorized error message
2025-07-14 11:12:49 +02:00
Matteo Manchi
b4fdab475b chore: add changeset 2025-07-14 11:03:59 +02:00
Guido D'Orsi
2b043abffa Merge pull request #2637 from garden-co/feat/discriminated-union-load-subscribe
feat: add load & subscribe to Discriminated union schemas
2025-07-14 10:53:33 +02:00
Matteo Manchi
958c122c36 chore(jazz-tools/tools): expose current user id in unauthorized error message 2025-07-12 21:01:44 +02:00
Margaret Culotta
5842838371 Add examples but remove twoslash because of limitations in how Twoslash parses advanced TypeScript types 2025-07-11 14:36:07 -05:00
NicoR
e136e1b696 Remove unnecessary comment 2025-07-11 16:01:38 -03:00
NicoR
2475a46578 Remove resolve type for when loading/subscribing to discriminated unions 2025-07-11 15:45:23 -03:00
Guido D'Orsi
41466ea399 feat: simplify the API 2025-07-11 20:01:30 +02:00
NicoR
44f653a64b Format and reorder imports 2025-07-11 14:00:55 -03:00
NicoR
f8437042a6 Add CoDiscriminatedUnionSchema.subscribe 2025-07-11 13:38:08 -03:00
Guido D'Orsi
3b91594d10 feat: migrate the jazz-paper-scissors to request & nextjs and rename it to server-side-validation 2025-07-11 18:11:47 +02:00
Margaret Culotta
acd908fbc2 add docs for recursive connection 2025-07-11 10:41:50 -05:00
Margaret Culotta
4e61d1d191 add docs for recursive connections 2025-07-11 10:37:31 -05:00
NicoR
db23582b4c Fix "Expression produces a union type that is too complex to represent" error 2025-07-11 12:27:31 -03:00
NicoR
4b0b6d8a69 Use Add CoDiscriminatedUnionSchema.load in existing tests 2025-07-11 12:21:31 -03:00
NicoR
d450b394fa Tighten CoDiscriminatedUnionSchema type 2025-07-11 12:12:56 -03:00
NicoR
0abc96e400 Add CoDiscriminatedUnionSchema.load 2025-07-11 11:17:44 -03:00
NicoR
7562354b29 Fix e2e test error 2025-07-10 21:44:54 -03:00
NicoR
6c085a3919 Fix imports 2025-07-10 17:29:33 -03:00
NicoR
6afff848bc Clean up CoValue schema definitions 2025-07-10 16:49:19 -03:00
NicoR
47059845cc Make co.discriminatedUnion tests type-tests 2025-07-10 16:09:41 -03:00
NicoR
a1735a8232 Update tests that expected CoValue class instead of schema 2025-07-10 16:00:56 -03:00
NicoR
1f5750d8c4 Support nesting CoValue classes inside CoValue schemas 2025-07-10 15:24:57 -03:00
NicoR
f756ce26b5 Fix CoListSchema 2025-07-10 14:46:22 -03:00
NicoR
84f5bdda74 Rename AnyCoSchema.getCoSchema to getCoValueClass 2025-07-10 14:41:54 -03:00
NicoR
ee7aefa97c Tighten Account schema type when inferring from Zod schema 2025-07-10 14:32:27 -03:00
NicoR
b0895981ba Add CoDiscriminatedUnionSchema 2025-07-10 14:31:58 -03:00
NicoR
94f636b2ee Fix CoOptionalSchema 2025-07-10 14:05:30 -03:00
NicoR
331ab070f6 Fix CoMapSchema.catchall 2025-07-10 13:38:42 -03:00
NicoR
13e73adfb9 Refactor zodSchemaToCoSchema to return CoValue schema instead of class 2025-07-10 12:38:23 -03:00
Guido D'Orsi
265a6405af feat: implement the payload/response schema spec 2025-07-10 16:56:30 +02:00
Guido D'Orsi
9f6079b6c6 Merge pull request #2634 from garden-co/changeset-release/main
Version Packages
2025-07-10 16:14:58 +02:00
github-actions[bot]
4033d78fa6 Version Packages 2025-07-10 14:02:44 +00:00
Guido D'Orsi
83af94c850 Merge pull request #2575 from garden-co/feat/storage-api
feat: storage as load/store API
2025-07-10 16:00:33 +02:00
Guido D'Orsi
70fe856713 fix: don't wait for File streaming on SubscriptionScope 2025-07-10 14:54:00 +02:00
Guido D'Orsi
42e4afc42b test: add tests for markErrored 2025-07-10 14:35:51 +02:00
Guido D'Orsi
0e6797b222 chore: update lockfile 2025-07-10 14:28:10 +02:00
Guido D'Orsi
3634eaf8e9 chore: remove error logged on verify failures 2025-07-10 14:27:56 +02:00
Guido D'Orsi
58dfda3d0f Merge remote-tracking branch 'origin/main' into feat/storage-api 2025-07-10 14:16:16 +02:00
Guido D'Orsi
d304b0bcb5 Merge pull request #2622 from garden-co/feat/wait-for-streaming
feat: wait for the full streaming before return values in load and subscribe
2025-07-10 14:10:01 +02:00
Guido D'Orsi
44f5a3f5a2 test: add tests for loading/subscribing to large coValues 2025-07-10 14:06:20 +02:00
Sammii
ebb3ce1c25 Merge pull request #2623 from garden-co/feat/design-system-shadcn-integration
Feat/design system shadcn integration
2025-07-10 11:18:28 +01:00
Sammii
a67bba0dcf ensuring height consistency between buttons, inputs and dropdowns 2025-07-10 11:12:23 +01:00
Guido D'Orsi
4a72c26e42 chore: simplify toAddTransactions and tracking content from storage 2025-07-10 11:24:21 +02:00
Guido D'Orsi
084cb5936d perf: increase the ws messages batching to 5ms 2025-07-10 10:49:48 +02:00
NicoR
f1552b8262 [WIP] FieldSchema refactor 2025-07-09 17:20:51 -03:00
NicoR
6826ad8e45 Add CoOptionalSchema 2025-07-09 17:09:26 -03:00
NicoR
7c1b757b62 Reuse DefaultProfileShape wherever possible 2025-07-09 17:09:26 -03:00
NicoR
326e1734a4 Fix import order 2025-07-09 17:09:10 -03:00
NicoR
0cf027c91b Revert changes on z.object and z.strictObject 2025-07-09 17:09:10 -03:00
NicoR
e358881b76 Prevent using z.record with CoValue schemas 2025-07-09 17:09:10 -03:00
NicoR
cee8010918 Prevent using z.intersection with CoValue schemas 2025-07-09 17:09:10 -03:00
NicoR
cc877139ef Prevent using z.strictObject with CoValue schemas 2025-07-09 17:09:10 -03:00
NicoR
56a9b89538 Prevent using z.tuple with CoValue schemas 2025-07-09 17:09:10 -03:00
NicoR
199c463e28 Prevent using z.array with CoValue schemas 2025-07-09 17:09:10 -03:00
NicoR
50e523d19c Prevent using z.union with CoValue schemas 2025-07-09 17:09:10 -03:00
NicoR
796ea24288 Prevent using z.discriminatedUnion with CoValue schemas 2025-07-09 17:09:10 -03:00
NicoR
c8be86e823 Add tests 2025-07-09 17:09:10 -03:00
NicoR
41b7054aab Prevent using z.optional and z.object with CoValue schemas 2025-07-09 17:09:10 -03:00
NicoR
101adcd024 More updates to docs and examples 2025-07-09 17:08:59 -03:00
NicoR
a2854aeec9 Fix import order 2025-07-09 16:24:45 -03:00
NicoR
bdc9aee689 Add changeset 2025-07-09 16:11:55 -03:00
NicoR
2f53ae0ab8 Update docs 2025-07-09 16:09:03 -03:00
NicoR
f76c05448c Update examples 2025-07-09 16:08:54 -03:00
NicoR
585e7e8177 Add tests for co.optional and co.discriminatedUnion 2025-07-09 15:30:05 -03:00
NicoR
82d8d1d873 Add co.discriminatedUnion 2025-07-09 15:30:05 -03:00
NicoR
2c523c86ff Replace usages of z.optional with CoSchemas with a new co.optional 2025-07-09 15:30:05 -03:00
NicoR
6616668d4a Add pnpm check command to jazz-tools 2025-07-09 15:30:05 -03:00
Guido D'Orsi
a4d23d527b feat: add content import/export to the experimental_request and move into tools 2025-07-09 18:43:53 +02:00
Nico Rainhart
8a3be85e97 Merge pull request #2601 from garden-co/fix/chat-rn-example-not-running
Update pinned react version to 19.1.0
2025-07-09 09:44:49 -03:00
Brad Anderson
1a7f2b7379 fix: chat-rn build issues for android 2025-07-08 17:32:31 -04:00
Guido D'Orsi
caac82dffd chore: enable lazy load on ProjectScreen 2025-07-08 20:33:49 +02:00
Guido D'Orsi
27b48378e5 feat: wait for the full streaming before return values in load and subscribe 2025-07-08 20:19:24 +02:00
Guido D'Orsi
cfd3c3ca5c Merge remote-tracking branch 'origin/main' into feat/storage-api 2025-07-08 19:36:25 +02:00
Guido D'Orsi
41f26b7a4f chore: streamingTarget -> expectContentUntil 2025-07-08 19:18:37 +02:00
Guido D'Orsi
c57ebb1cea feat: add the streamingTarget information only on the first content message 2025-07-08 19:10:17 +02:00
Guido D'Orsi
259aded5cc chore: changeset 2025-07-08 19:10:12 +02:00
Guido D'Orsi
1f5e091dd7 Merge pull request #2602 from garden-co/fix/remove-storage-peers
feat: refactor Peer communication and schedule incoming messages on sync
2025-07-08 18:52:25 +02:00
Guido D'Orsi
bbb1c44977 fix: reduce delay on batch to 0 and add a config for incoming messages scheduling budget 2025-07-08 18:38:08 +02:00
Guido D'Orsi
4327ecbfdf Merge pull request #2619 from garden-co/changeset-release/main
Version Packages
2025-07-08 17:34:26 +02:00
Guido D'Orsi
114c10bc77 Merge pull request #2621 from garden-co/fix/invalid-signature
fix: fixes InvalidSignature errors that could happen during streaming
2025-07-08 17:33:18 +02:00
Guido D'Orsi
cecdf29721 test: add tests for invalid signatures coming from stale data updates 2025-07-08 17:32:23 +02:00
Sammii
bd717fc0d7 updating buttons for default default 2025-07-08 16:04:50 +01:00
Guido D'Orsi
739fff68b3 fix: fixes InvalidSignature errors that could happen during streaming 2025-07-08 16:50:30 +02:00
Sammii
d49cab0afa improving design systems integration with shadcn vars 2025-07-08 13:56:49 +01:00
Guido D'Orsi
ffebb4fdaf chore: remove console.log 2025-07-08 14:40:46 +02:00
github-actions[bot]
32565f0e53 Version Packages 2025-07-08 12:20:12 +00:00
Sammii
61a5889bea Merge pull request #2615 from garden-co/fix/team-update 2025-07-08 13:18:08 +01:00
Sammii
82bd3e1ea6 adding nico 2025-07-08 12:00:09 +01:00
Sammii
b800a6fba2 Merge pull request #2401 from garden-co/feat/snippet-improvements
Feat/snippet improvements
2025-07-08 11:54:03 +01:00
Sammii
1b6dbfdfff adjusting side nav item design 2025-07-08 11:45:29 +01:00
Sammii
061a70f1b3 responsive design for dropdown select 2025-07-08 11:45:18 +01:00
Sammii
f1c1e0dafd adding div's profile link 2025-07-08 10:43:54 +01:00
Guido D'Orsi
c3912fdb37 Merge pull request #2618 from garden-co/fix/inspector-element
fix: simplify definition of the AccountSchema type
2025-07-07 22:19:49 +02:00
Guido D'Orsi
356bfa4860 docs: add jsDoc for coAccountDefiner 2025-07-07 19:50:41 +02:00
Guido D'Orsi
38446668c4 fix: simplify definition of the AccountSchema type 2025-07-07 19:44:21 +02:00
Brad Anderson
e2bb3b8015 fix: chat-rn-expo works, canary bump 2025-07-07 12:33:12 -04:00
Guido D'Orsi
11dcfd703d Merge pull request #2616 from garden-co/changeset-release/main
Version Packages
2025-07-07 18:16:58 +02:00
Brad Anderson
0b09d23bd1 fix: chat-rn works w properly-hoisted RN dep 2025-07-07 12:09:16 -04:00
github-actions[bot]
879b726537 Version Packages 2025-07-07 16:06:32 +00:00
Guido D'Orsi
66bbd03262 Merge pull request #2614 from garden-co/fix/inspector-element
fix: react bundling in jazz-tools/inspector/register-custom-element
2025-07-07 18:04:30 +02:00
Guido D'Orsi
c09b63698f fix: react bundling in jazz-tools/inspector/register-custom-element 2025-07-07 18:03:18 +02:00
Sammii
bed7db0a33 team page updates 2025-07-07 16:51:22 +01:00
NicoR
8ff3e234c1 Upgrade examples' expo version to 54.0.0-canary 2025-07-07 12:44:21 -03:00
Sammii
296da5a5c4 design amends 2025-07-07 16:40:30 +01:00
Guido D'Orsi
700a4f1ba1 fix: restore sync url in todo main 2025-07-07 16:46:18 +02:00
Guido D'Orsi
6f6663d825 test: cover ws.terminate 2025-07-07 16:28:46 +02:00
Guido D'Orsi
844cdc907f Merge pull request #2612 from garden-co/chore/playwright-tests
perf(ci): batch the e2e tests execution in 2 workflow runs
2025-07-07 16:02:45 +02:00
Guido D'Orsi
9e32d4cb92 perf(ci): batch the e2e tests execution in 2 workflow runs 2025-07-07 16:01:12 +02:00
Guido D'Orsi
85dc6ba148 feat: add metrics on incoming messages and storage streaming operations 2025-07-07 15:41:33 +02:00
Sammii
16c4d27e00 code tidy 2025-07-07 14:21:24 +01:00
Sammii
69170fe0e0 style amendments 2025-07-07 14:14:28 +01:00
Sammii
a646ba54b3 component refactor 2025-07-07 14:14:16 +01:00
Sammii
45d60fc3c8 get started snippet select improvements 2025-07-07 14:02:22 +01:00
Sammii
6f0c399ccd Merge branch 'main' into feat/snippet-improvements 2025-07-07 13:50:59 +01:00
Guido D'Orsi
40e1ca7cb1 Merge pull request #2606 from garden-co/changeset-release/main
Version Packages
2025-07-07 11:30:02 +02:00
github-actions[bot]
80cf21e453 Version Packages 2025-07-07 09:27:27 +00:00
Guido D'Orsi
48c8a3d219 Merge pull request #2603 from garden-co/PR-template-v2-simplify
simplify PR template for ease of use
2025-07-07 11:25:24 +02:00
Guido D'Orsi
31bb1201fc Merge pull request #2611 from garden-co/gio/update-lockfile
chore: update lockfile
2025-07-07 11:15:25 +02:00
Giordano Ricci
08d1b05607 chore: update lockfile 2025-07-07 09:47:51 +01:00
Guido D'Orsi
d64a14210d Merge pull request #2608 from jeffgca/main
Error: Loading PostCSS Plugin failed: Cannot find module '@tailwindcss/postcss'
2025-07-07 10:35:06 +02:00
Guido D'Orsi
7e53d33e9b Merge pull request #2609 from jeffgca/user_age_calc_fix
User age calc fix
2025-07-07 10:34:31 +02:00
Jeff Griffiths
ea2b39cc30 fixed off-by-one error 2025-07-04 21:27:54 -07:00
Jeff Griffiths
6b835f95cf enhanced getUserAge to calculate the user's age in a more precise way. 2025-07-04 21:14:32 -07:00
Jeff Griffiths
a229ae5f70 changed postcss dependency to the tailwind plugin instead. 2025-07-04 20:49:46 -07:00
Giordano Ricci
84fdc1d8fd Merge pull request #2605 from garden-co/gio/cancel-pending-workflows-on-push 2025-07-04 17:19:27 +01:00
Guido D'Orsi
9b1d52d183 chore: document IncomingMessagesQueue 2025-07-04 18:15:08 +02:00
Giordano Ricci
14a8b32522 differentiate workflows 2025-07-04 17:10:44 +01:00
Guido D'Orsi
ddc09a0d6b Merge pull request #2604 from garden-co/gio/get-only-direct-members
feat: allow to get only the direct members of a group
2025-07-04 18:09:55 +02:00
Giordano Ricci
3b45a3f2fd chore: cancel pending workflows on push 2025-07-04 17:05:45 +01:00
Giordano Ricci
9034a45da0 forgot the role 2025-07-04 16:51:07 +01:00
Guido D'Orsi
6247fac6c5 Merge remote-tracking branch 'origin/feat/storage-api' into fix/remove-storage-peers 2025-07-04 17:49:17 +02:00
Giordano Ricci
a5ceaffb0c changeset, usemethod instead of getter, reuse logic 2025-07-04 16:47:19 +01:00
Giordano Ricci
dcee2f9b4e better test 2025-07-04 16:18:53 +01:00
Guido D'Orsi
f27a2c541e chore: cleanup code and add tests 2025-07-04 17:18:17 +02:00
Giordano Ricci
83fdc504ff feat: add directMembers get to get only the direct members of a given group 2025-07-04 16:07:30 +01:00
Guido D'Orsi
2317a23fd4 chore: refactor createWebSocketPeer 2025-07-04 16:26:08 +02:00
Guido D'Orsi
a34c0675cd Merge pull request #2599 from garden-co/changeset-release/main
Version Packages
2025-07-04 14:36:26 +02:00
Margaret Culotta
5a8a62b4a3 simplify PR template for ease of use 2025-07-02 13:23:56 -05:00
github-actions[bot]
325a554bd1 Version Packages 2025-07-02 17:07:22 +00:00
Guido D'Orsi
7422943e83 Merge pull request #2600 from garden-co/fix/react-native-peer-dependencies
Make all React Native deps in `jazz-tools` optional peer dependencies
2025-07-02 19:05:22 +02:00
NicoR
23bfea5861 Add changeset 2025-07-02 13:50:45 -03:00
NicoR
605a54eb11 Make react-native-fast-encoder an optional peer dependency 2025-07-02 13:49:04 -03:00
Brad Anderson
a7aaee51e6 Merge pull request #2587 from garden-co/feat/rn-betterauth
feat: add RN BetterAuth
2025-07-02 12:24:38 -04:00
Brad Anderson
4b8983858a chore: changeset 2025-07-02 12:07:25 -04:00
Brad Anderson
8a8c4d11e1 fix: small cleanup 2025-07-02 11:56:40 -04:00
Guido D'Orsi
26994684d7 feat: refactor Peer communication and schedule incoming messages on sync 2025-07-02 17:45:28 +02:00
NicoR
14a5e036a4 Update homepage to react 19.1.0 2025-07-02 11:03:40 -03:00
NicoR
5b1c1ca522 Update all examples to react 19.1.0 2025-07-02 10:44:31 -03:00
NicoR
a9c8458c51 Update chat-rn's Podfile.lock 2025-07-02 09:38:56 -03:00
NicoR
5f31d6cbe1 Update pinned react version 2025-07-02 09:38:38 -03:00
Guido D'Orsi
b774bb345d chore: changeset 2025-07-02 10:52:44 +02:00
Guido D'Orsi
7fd891d7b9 chore: fix formatting 2025-07-02 10:52:10 +02:00
Guido D'Orsi
27cac4a6d7 Merge pull request #2596 from satendra03/main
fix #1914
2025-07-02 10:51:23 +02:00
Brad Anderson
2b71ef1181 fix: PR feedback 2025-07-01 21:46:25 -04:00
NicoR
ae169c7b3a Revert change for react-native-fast-encoder 2025-07-01 13:57:17 -03:00
NicoR
d888c99d9a Add expo-sqlite dependency to Expo Project setup docs 2025-07-01 13:53:01 -03:00
NicoR
0b54917f19 Make all React Native deps in jazz-tools optional peer dependencies 2025-07-01 12:21:38 -03:00
Guido D'Orsi
c87b215b75 Merge pull request #2594 from garden-co/fix-RNQuickCrypto-type-error
fix: `RNQuickCrypto` type error
2025-07-01 17:15:01 +02:00
NicoR
e4ba23cbef Add changeset 2025-07-01 12:13:40 -03:00
Brad Anderson
98c005a6e0 feat: more RN BetterAuth 2025-07-01 08:49:13 -04:00
Guido D'Orsi
477fd8a62d feat: simple backpressure for sync storage 2025-07-01 12:55:56 +02:00
Guido D'Orsi
90999ee709 Merge pull request #2593 from garden-co/fix/remove-storage-peers
chore: remove storage peer
2025-07-01 12:48:34 +02:00
Guido D'Orsi
38065f0cdf Merge pull request #2590 from garden-co/fix/storage-streaming
fix: server subscription when streaming from storage
2025-07-01 12:47:52 +02:00
Guido D'Orsi
c77d16cdb3 chore: cleanup code 2025-07-01 12:31:24 +02:00
Guido D'Orsi
9410084e6a chore: cleanup code
Co-authored-by: Nico Rainhart <nmrainhart@gmail.com>
2025-07-01 12:30:08 +02:00
Guido D'Orsi
8528db4de4 Merge pull request #2595 from garden-co/fix/make-jazz-tools-rn-deps-peer-dependencies
fix: make `react-native-nitro-modules` and `react-native-quick-crypto` optional peer dependencies
2025-07-01 11:30:14 +02:00
satendra03
e0fe5a20b7 fix #1914 2025-07-01 04:21:26 +05:30
NicoR
e16e4d53d1 Keep file extension in relative import 2025-06-30 16:52:09 -03:00
NicoR
d904fae506 fix: make react-native-quick-crypto an optional peer dependency 2025-06-30 15:44:04 -03:00
NicoR
f67c0b3db3 fix: make react-native-nitro-modules an optional peer dependency 2025-06-30 15:43:30 -03:00
NicoR
283d7c6bf0 fix: RNQuickCrypto type error 2025-06-30 15:13:10 -03:00
Guido D'Orsi
e67c5838a9 chore: remove storage peer 2025-06-30 18:15:11 +02:00
Guido D'Orsi
0e7a7dbbc0 Merge pull request #2591 from garden-co/fix/ci-e2e-exit-code
fix(ci): listen to e2e-rn-test's exit code
2025-06-30 17:53:34 +02:00
Matteo Manchi
63c69b6b95 fix(ci): listen to e2e-rn-test's exit code 2025-06-30 17:41:57 +02:00
Guido D'Orsi
a141cbc7f7 chore: rename back to AvailableCoValueCore to facilitate review 2025-06-30 16:40:37 +02:00
Guido D'Orsi
6a5352cf3a test: remove TODO on browser integration tests 2025-06-30 15:31:38 +02:00
Guido D'Orsi
27762637ee fix: streaming from storage now correctly send the target known state in the load requests 2025-06-30 15:28:08 +02:00
Guido D'Orsi
dcebe34891 chore: remove unused id param from storage.store 2025-06-27 19:59:56 +02:00
Guido D'Orsi
99d510815f Merge remote-tracking branch 'origin/main' into feat/storage-api 2025-06-27 19:40:12 +02:00
Guido D'Orsi
928962c08b chore: add comments and improve tests 2025-06-27 19:37:43 +02:00
Guido D'Orsi
cdadd6db1d chore: revert CoJsonIDBTransaction 2025-06-27 18:58:36 +02:00
Guido D'Orsi
d45b8ae70b feat: improve the loading and add content streaming tests 2025-06-27 18:57:45 +02:00
Guido D'Orsi
445a58c864 chore: centralize loadFromStorage logic 2025-06-27 18:29:05 +02:00
Guido D'Orsi
1895b474ea Merge remote-tracking branch 'origin/main' into feat/storage-api 2025-06-27 18:15:49 +02:00
Guido D'Orsi
fd02627069 Merge pull request #2583 from garden-co/changeset-release/main
Version Packages
2025-06-27 18:14:31 +02:00
github-actions[bot]
827adc991d Version Packages 2025-06-27 16:01:01 +00:00
Guido D'Orsi
651b69e5af Merge pull request #2582 from garden-co/fix/circular-deps-sync
fix: fix sync with circular deps
2025-06-27 17:57:32 +02:00
Guido D'Orsi
277e4d49e8 fix: fix sync with circular deps 2025-06-27 17:52:02 +02:00
Guido D'Orsi
8990ff39a5 fix: initialize async sqlite db 2025-06-27 17:26:52 +02:00
Guido D'Orsi
71e4c97255 chore: update lockfile 2025-06-27 17:21:56 +02:00
Guido D'Orsi
577e960e28 Merge remote-tracking branch 'origin/main' into feat/storage-api 2025-06-27 17:19:07 +02:00
Guido D'Orsi
f232f75d40 feat: performance testing app 2025-06-27 17:18:57 +02:00
Guido D'Orsi
e1a7f829b4 feat: move storage inside cojson and add more tests 2025-06-26 18:18:30 +02:00
Guido D'Orsi
f82177b9da feat: indexed db 2025-06-25 15:26:08 +02:00
Guido D'Orsi
c1c553bad0 feat: storage as load/store API 2025-06-25 11:53:14 +02:00
Sammii
588ea02f63 refactoring Framework Select 2025-06-24 16:09:01 +01:00
Sammii
ddc69f2268 apply track on copy click to new snippet select component 2025-06-24 11:56:39 +01:00
Sammii
7c62689319 Merge branch 'main' into feat/snippet-improvements 2025-06-24 11:52:03 +01:00
Sammii
df7011167c making active dropdown item text primary 2025-06-02 17:14:45 +01:00
Sammii
28a785acb0 letting dropdown items be editable 2025-06-02 17:10:57 +01:00
Sammii
3ee557bfbe adding routerPush prop to framework select 2025-06-02 17:10:30 +01:00
Sammii
af94255166 updating HeroSection 2025-06-02 16:50:12 +01:00
Sammii
4a0dea3f75 create NpxCreateJazzApp.mdx 2025-06-02 16:49:46 +01:00
Sammii
6a42bc9655 creating GetStartedSnippetSelect component 2025-06-02 16:49:17 +01:00
Sammii
c6c8a7f6b7 amending Framework select 2025-06-02 16:48:41 +01:00
Sammii
133dd0e26d make dropdown classes last so you can edit them 2025-06-02 16:43:07 +01:00
Sammii
815339272f alter Feature Card styling 2025-06-02 14:47:30 +01:00
Sammii
9c1f340029 add new size to code group and amend copy button to be icon only on small 2025-06-02 13:54:29 +01:00
Sammii
b72ea9608d add new icon for clipboard success 2025-06-02 13:54:09 +01:00
523 changed files with 32653 additions and 12912 deletions

View File

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

View File

@@ -1,24 +1,23 @@
### What this Does
Brief summary of the change, ideally framed in user or product terms.
# 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 -->
### Why Are We Doing This?
Link to the shaped pitch or explain what problem it solves.
## Manual testing instructions
### Scope / Boundaries
Includes:
- [x] Core feature functionality
- [x] Tests or validation steps
<!-- Add any actions required to manually test the changes -->
Do NOT include:
- [ ] Related stretch features or follow-ups
## Tests
### Testing Instructions
How a reviewer or QA can verify behavior, offer step-by-step instructions if possible. Screenshots or recordings are welcome.
- [ ] 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
### Known Issues / Open Questions (if any)
- [ ] Note anything youd like review on or decided async
### Related Links
- GitHub issue
- Linear pitch
- Design links or references
## 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

View File

@@ -1,5 +1,11 @@
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:
@@ -16,7 +22,7 @@ jobs:
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: 1.9.4
version: 2.1.3
- name: Run Biome
run: biome ci .

View File

@@ -1,5 +1,11 @@
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]
@@ -61,7 +67,7 @@ jobs:
disable-animations: true
working-directory: ./examples/chat-rn-expo/
# killall due to this issue: https://github.com/ReactiveCircus/android-emulator-runner/issues/385
script: ./test/e2e/run.sh && killall -INT crashpad_handler || true
script: ./test/e2e/run.sh && ( killall -INT crashpad_handler || true )
- name: Copy Maestro Output
if: steps.e2e_test.outcome != 'success'

View File

@@ -1,5 +1,11 @@
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

@@ -1,46 +0,0 @@
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,5 +1,11 @@
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"]
@@ -13,21 +19,7 @@ jobs:
continue-on-error: true
strategy:
matrix:
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"
]
shard: ["1/2", "2/2"]
steps:
- uses: actions/checkout@v4
@@ -37,25 +29,130 @@ 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
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
- 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"
"examples/server-worker-http"
"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

View File

@@ -1,4 +1,11 @@
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,5 +1,11 @@
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

@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"$schema": "https://biomejs.dev/schemas/2.1.3/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
@@ -7,39 +7,35 @@
},
"files": {
"ignoreUnknown": false,
"ignore": [
"jazz-tools.json",
"**/ios/**",
"**/android/**",
"tests/jazz-svelte/src/**",
"examples/*svelte*/**",
"starters/*svelte*/**",
"examples/jazz-paper-scissors/src/routeTree.gen.ts",
"homepage/homepage/**",
"**/package.json"
"includes": [
"**",
"!**/jazz-tools.json",
"!**/ios/**",
"!**/android/**",
"!**/tests/jazz-svelte/src/**",
"!**/examples/**/*svelte*/**",
"!**/starters/**/*svelte*/**",
"!**/examples/server-worker-inbox/src/routeTree.gen.ts",
"!**/homepage/homepage/**",
"!**/package.json",
"!**/*svelte*/**"
]
},
"formatter": {
"enabled": true,
"indentStyle": "space"
},
"organizeImports": {
"enabled": true
},
"assist": { "actions": { "source": { "organizeImports": "off" } } },
"linter": {
"enabled": false,
"rules": {
"recommended": true,
"correctness": {
"useExhaustiveDependencies": "off",
"useImportExtensions": {
"level": "error",
"options": {
"suggestedExtensions": {
"ts": {
"module": "js",
"component": "jsx"
}
}
"forceJsExtensions": true
}
}
}
@@ -47,7 +43,7 @@
},
"overrides": [
{
"include": ["packages/**/src/**"],
"includes": ["**/packages/**/src/**"],
"linter": {
"enabled": true,
"rules": {
@@ -56,7 +52,10 @@
}
},
{
"include": ["packages/cojson-storage*/**", "cojson-transport-ws/**"],
"includes": [
"**/packages/cojson/src/storage/**/*/**",
"**/cojson-transport-ws/**"
],
"linter": {
"enabled": true,
"rules": {
@@ -65,7 +64,7 @@
}
},
{
"include": ["**/tests/**"],
"includes": ["**/tests/**"],
"linter": {
"rules": {
"correctness": {
@@ -75,7 +74,7 @@
"noNonNullAssertion": "off"
},
"suspicious": {
"noExplicitAny": "info"
"noExplicitAny": "off"
}
}
}

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": "~53.0.9",
"expo": "54.0.0-canary-20250701-6a945c5",
"expo-clipboard": "^7.1.4",
"expo-secure-store": "~14.2.3",
"expo-sqlite": "~15.2.10",
"jazz-tools": "workspace:*",
"react": "19.0.0",
"react-native": "0.79.2",
"react": "19.1.0",
"react-native": "0.80.0",
"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: 5d41e1df061200130dd326e55cdfdf94b0289c6e
ReactCodegen: d82f538f70f00484d418803f74b5a0ea09cc8689
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.2",
"@react-native-clipboard/clipboard": "1.16.3",
"@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

@@ -76,7 +76,9 @@ export function ChatScreen({ navigation }: { navigation: any }) {
const renderMessageItem = ({
item,
}: { item: Loaded<typeof Message, { text: true }> }) => {
}: {
item: Loaded<typeof Message, { text: true }>;
}) => {
const isMe = item._edits?.text?.by?.isMe;
return (
<View

View File

@@ -3,11 +3,7 @@ import React from "react";
import { Text } from "react-native";
import { Chat } from "./schema";
export function HandleInviteScreen({
navigation,
}: {
navigation: any;
}) {
export function HandleInviteScreen({ navigation }: { navigation: any }) {
useAcceptInviteNative({
invitedObjectSchema: Chat,
onAccept: async (chatId) => {

View File

@@ -1,5 +1,161 @@
# passkey-svelte
## 0.0.112
### Patch Changes
- Updated dependencies [67e0968]
- Updated dependencies [2c8120d]
- jazz-tools@0.16.6
## 0.0.111
### Patch Changes
- Updated dependencies [3cd1586]
- Updated dependencies [33ebbf0]
- jazz-tools@0.16.5
## 0.0.110
### Patch Changes
- Updated dependencies [16764f6]
- jazz-tools@0.16.4
## 0.0.109
### Patch Changes
- Updated dependencies [43d3511]
- jazz-tools@0.16.3
## 0.0.108
### Patch Changes
- jazz-tools@0.16.2
## 0.0.107
### Patch Changes
- Updated dependencies [c62abef]
- jazz-tools@0.16.1
## 0.0.106
### Patch Changes
- 2bbb07b: Introduce a cleaner separation between Zod and CoValue schemas:
- Zod schemas and CoValue schemas are fully separated. Zod schemas can only be composed with other Zod schemas. CoValue schemas can be composed with either Zod or other CoValue schemas.
- `z.optional()` and `z.discriminatedUnion()` no longer work with CoValue schemas. Use `co.optional()` and `co.discriminatedUnion()` instead.
- Internal schema access is now simpler. You no longer need to use Zods `.def` to access internals. Use properties like `CoMapSchema.shape`, `CoListSchema.element`, and `CoOptionalSchema.innerType` directly.
- CoValue schema types are now namespaced under `co.`. Non-namespaced exports have been removed
- CoMap schemas no longer incorrectly inherit from Zod. Previously, methods like `.extend()` and `.partial()` appeared available but could cause unexpected behavior. These methods are now disabled. In their place, `.optional()` has been added, and more Zod-like methods will be introduced in future releases.
- Upgraded Zod from `3.25.28` to `3.25.76`.
- Removed deprecated `withHelpers` method from CoValue schemas
- Removed deprecated `createCoValueObservable` function
- Updated dependencies [c09dcdf]
- Updated dependencies [2bbb07b]
- jazz-tools@0.16.0
## 0.0.105
### Patch Changes
- Updated dependencies [9633d01]
- Updated dependencies [4beafb7]
- jazz-tools@0.15.16
## 0.0.104
### Patch Changes
- Updated dependencies [3fe53a3]
- jazz-tools@0.15.15
## 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
- jazz-tools@0.15.4
## 0.0.92
### Patch Changes

View File

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

View File

@@ -1,8 +1,8 @@
import { co, z } from 'jazz-tools';
import { co } from 'jazz-tools';
export const Message = co.map({
text: co.plainText(),
image: z.optional(co.image())
image: co.optional(co.image())
});
export const Chat = co.list(Message);

View File

@@ -15,16 +15,16 @@
"clsx": "^2.0.0",
"hash-slash": "workspace:*",
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"zod": "3.25.28"
"lucide-react": "^0.536.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"zod": "3.25.76"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.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,6 +1,6 @@
import { Account, co } from "jazz-tools";
import { Account } from "jazz-tools";
import { createImage, useAccount, useCoState } from "jazz-tools/react";
import { useState } from "react";
import { useEffect, useState } from "react";
import { Chat, Message } from "./schema.ts";
import {
BubbleBody,
@@ -15,14 +15,17 @@ import {
TextInput,
} from "./ui.tsx";
export function ChatScreen(props: { chatID: string }) {
const chat = useCoState(Chat, props.chatID, {
resolve: { $each: { text: true } },
});
const { me } = useAccount();
const [showNLastMessages, setShowNLastMessages] = useState(30);
const INITIAL_MESSAGES_TO_SHOW = 30;
if (!chat)
export function ChatScreen(props: { chatID: string }) {
const chat = useCoState(Chat, props.chatID);
const { me } = useAccount();
const [showNLastMessages, setShowNLastMessages] = useState(
INITIAL_MESSAGES_TO_SHOW,
);
const isLoading = useMessagesPreload(props.chatID);
if (!chat || isLoading)
return (
<div className="flex-1 flex justify-center items-center">Loading...</div>
);
@@ -41,7 +44,7 @@ export function ChatScreen(props: { chatID: string }) {
chat.push(
Message.create(
{
text: co.plainText().create(file.name, chat._owner),
text: file.name,
image: image,
},
chat._owner,
@@ -59,9 +62,14 @@ export function ChatScreen(props: { chatID: string }) {
<ChatBody>
{chat.length > 0 ? (
chat
// We call slice before reverse to avoid mutating the original array
.slice(-showNLastMessages)
.reverse() // this plus flex-col-reverse on ChatBody gives us scroll-to-bottom behavior
.map((msg) => <ChatBubble me={me} msg={msg} key={msg.id} />)
// Reverse plus flex-col-reverse on ChatBody gives us scroll-to-bottom behavior
.reverse()
.map(
(msg) =>
msg?.text && <ChatBubble me={me} msg={msg} key={msg.id} />,
)
) : (
<EmptyChatMessage />
)}
@@ -80,12 +88,7 @@ export function ChatScreen(props: { chatID: string }) {
<TextInput
onSubmit={(text) => {
chat.push(
Message.create(
{ text: co.plainText().create(text, chat._owner) },
chat._owner,
),
);
chat.push(Message.create({ text }, chat._owner));
}}
/>
</InputBar>
@@ -93,10 +96,7 @@ export function ChatScreen(props: { chatID: string }) {
);
}
function ChatBubble(props: {
me: Account;
msg: co.loaded<typeof Message, { text: true }>;
}) {
function ChatBubble(props: { me: Account; msg: Message }) {
if (!props.me.canRead(props.msg) || !props.msg.text?.toString()) {
return (
<BubbleContainer fromMe={false}>
@@ -126,3 +126,35 @@ function ChatBubble(props: {
</BubbleContainer>
);
}
/**
* Warms the local cache with the initial messages to load only the initial messages
* and avoid flickering
*/
function useMessagesPreload(chatID: string) {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
preloadChatMessages(chatID).finally(() => {
setIsLoading(false);
});
}, [chatID]);
return isLoading;
}
async function preloadChatMessages(chatID: string) {
const chat = await Chat.load(chatID);
if (!chat?._refs) return;
const promises = [];
for (const msg of Array.from(chat._refs)
.reverse()
.slice(0, INITIAL_MESSAGES_TO_SHOW)) {
promises.push(Message.load(msg.id, { resolve: { text: true } }));
}
await Promise.all(promises);
}

View File

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

View File

@@ -112,7 +112,9 @@ export function InputBar(props: { children: React.ReactNode }) {
export function ImageInput({
onImageChange,
}: { onImageChange?: (event: React.ChangeEvent<HTMLInputElement>) => void }) {
}: {
onImageChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
}) {
const inputRef = useRef<HTMLInputElement>(null);
const onUploadClick = () => {

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": "~53.0.9",
"expo": "54.0.0-canary-20250701-6a945c5",
"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.0.0",
"react-native": "0.79.2",
"react": "19.1.0",
"react-native": "0.80.0",
"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

@@ -10,7 +10,9 @@ import {
export function SignInScreen({
setPage,
}: { setPage: (page: "sign-in" | "sign-up") => void }) {
}: {
setPage: (page: "sign-in" | "sign-up") => void;
}) {
const { signIn, setActive, isLoaded } = useSignIn();
const [emailAddress, setEmailAddress] = useState("");

View File

@@ -10,7 +10,9 @@ import {
export function SignUpScreen({
setPage,
}: { setPage: (page: "sign-in" | "sign-up") => void }) {
}: {
setPage: (page: "sign-in" | "sign-up") => void;
}) {
const { isLoaded, signUp, setActive } = useSignUp();
const [emailAddress, setEmailAddress] = React.useState("");

View File

@@ -14,17 +14,17 @@
"dependencies": {
"@clerk/clerk-react": "^5.4.1",
"jazz-tools": "workspace:*",
"react": "19.0.0",
"react-dom": "19.0.0"
"react": "19.1.0",
"react-dom": "19.1.0"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@biomejs/biome": "1.9.4",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.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.0.0",
"react-dom": "19.0.0"
"react": "19.1.0",
"react-dom": "19.1.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"is-ci": "^3.0.1",

View File

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

View File

@@ -12,16 +12,16 @@
"dependencies": {
"hash-slash": "workspace:*",
"jazz-tools": "workspace:*",
"react": "19.0.0",
"react-dom": "19.0.0"
"react": "19.1.0",
"react-dom": "19.1.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.0.0",
"@types/react-dom": "19.0.0",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"is-ci": "^3.0.1",

View File

@@ -1,5 +1,4 @@
import { useIframeHashRouter } from "hash-slash";
import { Loaded } from "jazz-tools";
import { useAccount, useCoState } from "jazz-tools/react";
import { useState } from "react";
import { Errors } from "./Errors.tsx";
@@ -9,7 +8,7 @@ import {
BubbleTeaOrder,
DraftBubbleTeaOrder,
JazzAccount,
ListOfBubbleTeaAddOns,
validateDraftOrder,
} from "./schema.ts";
export function CreateOrder() {
@@ -21,20 +20,19 @@ export function CreateOrder() {
if (!me?.root) return;
const onSave = (draft: Loaded<typeof DraftBubbleTeaOrder>) => {
// validate if the draft is a valid order
const validation = DraftBubbleTeaOrder.validate(draft);
const onSave = (draft: DraftBubbleTeaOrder) => {
const validation = validateDraftOrder(draft);
setErrors(validation.errors);
if (validation.errors.length > 0) {
return;
}
// turn the draft into a real order
me.root.orders.push(draft as Loaded<typeof BubbleTeaOrder>);
me.root.orders.push(draft as BubbleTeaOrder);
// reset the draft
me.root.draft = DraftBubbleTeaOrder.create({
addOns: ListOfBubbleTeaAddOns.create([]),
addOns: [],
});
router.navigate("/");
@@ -60,7 +58,7 @@ function CreateOrderForm({
onSave,
}: {
id: string;
onSave: (draft: Loaded<typeof DraftBubbleTeaOrder>) => void;
onSave: (draft: DraftBubbleTeaOrder) => void;
}) {
const draft = useCoState(DraftBubbleTeaOrder, id, {
resolve: { addOns: true, instructions: true },

View File

@@ -1,11 +1,11 @@
import { useAccount } from "jazz-tools/react";
import { DraftBubbleTeaOrder, JazzAccount } from "./schema";
import { JazzAccount, hasChanges } from "./schema";
export function DraftIndicator() {
const { me } = useAccount(JazzAccount, {
resolve: { root: { draft: true } },
});
if (DraftBubbleTeaOrder.hasChanges(me?.root.draft)) {
if (hasChanges(me?.root.draft)) {
return (
<div className="absolute -top-1 -right-1 bg-blue-500 border-2 border-white w-3 h-3 rounded-full dark:border-stone-925">
<span className="sr-only">You have a draft</span>

View File

@@ -1,4 +1,4 @@
import { CoPlainText, Loaded } from "jazz-tools";
import { CoPlainText } from "jazz-tools";
import {
BubbleTeaAddOnTypes,
BubbleTeaBaseTeaTypes,
@@ -10,7 +10,7 @@ export function OrderForm({
order,
onSave,
}: {
order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>;
order: BubbleTeaOrder | DraftBubbleTeaOrder;
onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}) {
// Handles updates to the instructions field of the order.

View File

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

View File

@@ -1,4 +1,4 @@
import { Loaded, co, z } from "jazz-tools";
import { co, z } from "jazz-tools";
export const BubbleTeaAddOnTypes = [
"Pearl",
@@ -15,52 +15,46 @@ export const BubbleTeaBaseTeaTypes = [
"Thai",
] as const;
export const ListOfBubbleTeaAddOns = co
.list(z.literal([...BubbleTeaAddOnTypes]))
.withHelpers((Self) => ({
hasChanges(list?: Loaded<typeof Self> | null) {
return list && Object.entries(list._raw.insertions).length > 0;
},
}));
export const ListOfBubbleTeaAddOns = co.list(
z.literal([...BubbleTeaAddOnTypes]),
);
export type ListOfBubbleTeaAddOns = co.loaded<typeof ListOfBubbleTeaAddOns>;
function hasAddOnsChanges(list?: ListOfBubbleTeaAddOns | 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()),
instructions: co.optional(co.plainText()),
});
export type BubbleTeaOrder = co.loaded<typeof BubbleTeaOrder>;
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()),
})
.withHelpers((Self) => ({
hasChanges(order: Loaded<typeof Self> | undefined) {
return (
!!order &&
(Object.keys(order._edits).length > 1 ||
ListOfBubbleTeaAddOns.hasChanges(order.addOns))
);
},
export const DraftBubbleTeaOrder = BubbleTeaOrder.partial();
export type DraftBubbleTeaOrder = co.loaded<typeof DraftBubbleTeaOrder>;
validate(order: Loaded<typeof Self>) {
const errors: string[] = [];
export function validateDraftOrder(order: DraftBubbleTeaOrder) {
const errors: string[] = [];
if (!order.baseTea) {
errors.push("Please select your preferred base tea.");
}
if (!order.deliveryDate) {
errors.push("Plese select a delivery date.");
}
if (!order.baseTea) {
errors.push("Please select your preferred base tea.");
}
if (!order.deliveryDate) {
errors.push("Plese select a delivery date.");
}
return { errors };
},
}));
return { errors };
}
export function hasChanges(order?: DraftBubbleTeaOrder | null) {
return (
!!order &&
(Object.keys(order._edits).length > 1 || hasAddOnsChanges(order.addOns))
);
}
/** The root is an app-specific per-user private `CoMap`
* where you can store top-level objects for that user */
@@ -76,15 +70,9 @@ export const JazzAccount = co
})
.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.root = AccountRoot.create(
{ draft: { addOns: [], instructions: "" }, orders: [] },
account,
);
account.root = AccountRoot.create({ draft, orders }, account);
}
});

View File

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

View File

@@ -1,7 +1,7 @@
import { co, z } from "jazz-tools";
import { co } from "jazz-tools";
export const JazzProfile = co.profile({
image: z.optional(co.image()),
image: co.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.0.0",
"react-dom": "19.0.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-use": "^17.4.0"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@vitejs/plugin-react-swc": "^3.10.1",
"postcss": "^8.4.40",
"tailwindcss": "^4.1.10",

View File

@@ -11,9 +11,9 @@ export const Issue = co.map({
status: z.enum(["open", "closed"]),
labels: co.list(z.string()),
reactions: ReactionsList,
file: z.optional(co.fileStream()),
image: z.optional(co.image()),
lead: z.optional(co.account()),
file: co.optional(co.fileStream()),
image: co.optional(co.image()),
lead: co.optional(co.account()),
});
export const Project = co.map({

View File

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

View File

@@ -0,0 +1 @@
export const apiKey = "jazz-nextjs@garden.co";

View File

@@ -1,12 +1,13 @@
import { JazzInspector } from "jazz-tools/inspector";
import { JazzReactProvider } from "jazz-tools/react";
import { apiKey } from "./apiKey";
export function Jazz({ children }: { children: React.ReactNode }) {
return (
<JazzReactProvider
enableSSR
sync={{
peer: `wss://cloud.jazz.tools/`,
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
}}
>
{children}

View File

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

View File

@@ -12,7 +12,11 @@ function Avatar({
name,
color,
active,
}: { name: string; color: string; active: boolean }) {
}: {
name: string;
color: string;
active: boolean;
}) {
return (
<span
title={name}

View File

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

View File

@@ -47,7 +47,9 @@ button {
font-family: inherit;
background-color: transparent;
cursor: pointer;
transition: all 0.05s ease, border-color 0.1s ease;
transition:
all 0.05s ease,
border-color 0.1s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
button:hover {
@@ -93,8 +95,9 @@ button:active {
background-color: white;
padding: 2rem;
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px
rgba(0, 0, 0, 0.06);
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
width: 28rem;
}

View File

@@ -22,9 +22,9 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"lucide-react": "^0.536.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"tailwind-merge": "^1.14.0"
@@ -32,12 +32,13 @@
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@vitejs/plugin-react-swc": "^3.10.1",
"postcss": "^8.4.27",
"tailwindcss": "^4.1.10",
"typescript": "5.6.2",
"vite": "^6.3.5"
"vite": "^6.3.5",
"vite-plugin-pwa": "^1.0.2"
}
}

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: z.optional(MusicTrack),
activeTrack: co.optional(MusicTrack),
activePlaylist: Playlist,
exampleDataLoaded: z.optional(z.boolean()),
@@ -84,15 +84,14 @@ export const MusicaAccount = co
* You can use it to set up the account root and any other initial CoValues you need.
*/
if (account.root === undefined) {
const tracks = co.list(MusicTrack).create([]);
const rootPlaylist = Playlist.create({
tracks,
tracks: [],
title: "",
});
account.root = MusicaAccountRoot.create({
rootPlaylist,
playlists: co.list(Playlist).create([]),
playlists: [],
activeTrack: undefined,
activePlaylist: rootPlaylist,
exampleDataLoaded: false,

View File

@@ -15,7 +15,7 @@ import { SidebarProvider } from "@/components/ui/sidebar";
import { JazzReactProvider } from "jazz-tools/react";
import { onAnonymousAccountDiscarded } from "./4_actions";
import { KeyboardListener } from "./components/PlayerControls";
import { useUploadExampleData } from "./lib/useUploadExampleData";
import { usePrepareAppState } from "./lib/usePrepareAppState";
/**
* Walkthrough: The top-level provider `<JazzReactProvider/>`
@@ -31,7 +31,7 @@ import { useUploadExampleData } from "./lib/useUploadExampleData";
function Main() {
const mediaPlayer = useMediaPlayer();
useUploadExampleData();
const isReady = usePrepareAppState(mediaPlayer);
const router = createHashRouter([
{
@@ -48,6 +48,8 @@ function Main() {
},
]);
if (!isReady) return null;
return (
<>
<RouterProvider router={router} />

View File

@@ -11,6 +11,7 @@ import { uploadMusicTracks } from "./4_actions";
import { MediaPlayer } from "./5_useMediaPlayer";
import { FileUploadButton } from "./components/FileUploadButton";
import { MusicTrackRow } from "./components/MusicTrackRow";
import { PlayerControls } from "./components/PlayerControls";
import { PlaylistTitleInput } from "./components/PlaylistTitleInput";
import { SidePanel } from "./components/SidePanel";
import { Button } from "./components/ui/button";
@@ -42,7 +43,11 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
const playlistId = params.playlistId ?? me?.root._refs.rootPlaylist.id;
const playlist = useCoState(Playlist, playlistId, {
resolve: { tracks: true },
resolve: {
tracks: {
$each: true,
},
},
});
const isRootPlaylist = !params.playlistId;
@@ -64,16 +69,16 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
const isAuthenticated = useIsAuthenticated();
return (
<SidebarInset className="flex flex-col h-screen text-gray-800 bg-blue-50">
<SidebarInset className="flex flex-col h-screen text-gray-800">
<div className="flex flex-1 overflow-hidden">
<SidePanel mediaPlayer={mediaPlayer} />
<main className="flex-1 p-6 overflow-y-auto overflow-x-hidden">
<SidebarTrigger />
<div className="flex items-center justify-between mb-6">
<SidePanel />
<main className="flex-1 px-2 py-4 md:px-6 overflow-y-auto overflow-x-hidden relative sm:h-[calc(100vh-80px)] bg-white h-[calc(100vh-165px)]">
<SidebarTrigger className="md:hidden" />
<div className="flex flex-row items-center justify-between mb-4 pl-1 md:pl-10 pr-2 md:pr-0 mt-2 md:mt-0 w-full">
{isRootPlaylist ? (
<h1 className="text-2xl font-bold text-blue-800">All tracks</h1>
) : (
<PlaylistTitleInput playlistId={playlistId} />
<PlaylistTitleInput className="w-full" playlistId={playlistId} />
)}
<div className="flex items-center space-x-4">
{isRootPlaylist && (
@@ -90,14 +95,14 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
)}
</div>
</div>
<ul className="flex flex-col max-w-full">
<ul className="flex flex-col max-w-full sm:gap-1">
{playlist?.tracks?.map(
(track) =>
(track, index) =>
track && (
<MusicTrackRow
trackId={track.id}
key={track.id}
isLoading={mediaPlayer.loading === track.id}
index={index}
isPlaying={
mediaPlayer.activeTrackId === track.id &&
isActivePlaylist &&
@@ -106,12 +111,12 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
onClick={() => {
mediaPlayer.setActiveTrack(track, playlist);
}}
showAddToPlaylist={isRootPlaylist}
/>
),
)}
</ul>
</main>
<PlayerControls mediaPlayer={mediaPlayer} />
</div>
</SidebarInset>
);

View File

@@ -1,11 +1,6 @@
import { getAudioFileData } from "@/lib/audio/getAudioFileData";
import { FileStream, Group, co } from "jazz-tools";
import {
MusicTrack,
MusicTrackWaveform,
MusicaAccount,
Playlist,
} from "./1_schema";
import { FileStream, Group } from "jazz-tools";
import { MusicTrack, MusicaAccount, Playlist } from "./1_schema";
/**
* Walkthrough: Actions
@@ -51,7 +46,7 @@ export async function uploadMusicTracks(
{
file: fileStream,
duration: data.duration,
waveform: MusicTrackWaveform.create({ data: data.waveform }, group),
waveform: { data: data.waveform },
title: file.name,
isExampleTrack,
},
@@ -73,18 +68,10 @@ export async function createNewPlaylist() {
},
});
// Since playlists are meant to be shared we associate them
// to a group which will contain the keys required to get
// access to the "owned" values
const playlistGroup = Group.create();
const playlist = Playlist.create(
{
title: "New Playlist",
tracks: co.list(MusicTrack).create([], playlistGroup),
},
playlistGroup,
);
const playlist = Playlist.create({
title: "New Playlist",
tracks: [],
});
// Again, we associate the new playlist to the
// user by pushing it into the playlists CoList
@@ -129,7 +116,7 @@ export async function removeTrackFromPlaylist(
if (track._owner._type === "Group" && playlist._owner._type === "Group") {
const trackGroup = track._owner;
await trackGroup.removeMember(playlist._owner);
trackGroup.removeMember(playlist._owner);
const index =
playlist.tracks?.findIndex(

View File

@@ -5,6 +5,7 @@ import { FileStream } from "jazz-tools";
import { useAccount } from "jazz-tools/react";
import { useRef, useState } from "react";
import { updateActivePlaylist, updateActiveTrack } from "./4_actions";
import { useAudioManager } from "./lib/audio/AudioManager";
import { getNextTrack, getPrevTrack } from "./lib/getters";
export function useMediaPlayer() {
@@ -12,6 +13,7 @@ export function useMediaPlayer() {
resolve: { root: true },
});
const audioManager = useAudioManager();
const playState = usePlayState();
const playMedia = usePlayMedia();
@@ -24,8 +26,10 @@ export function useMediaPlayer() {
async function loadTrack(track: MusicTrack) {
lastLoadedTrackId.current = track.id;
audioManager.unloadCurrentAudio();
setLoading(track.id);
updateActiveTrack(track);
const file = await FileStream.loadAsBlob(track._refs.file!.id); // TODO: see if we can avoid !
@@ -40,8 +44,6 @@ export function useMediaPlayer() {
return;
}
updateActiveTrack(track);
await playMedia(file);
setLoading(null);
@@ -85,6 +87,7 @@ export function useMediaPlayer() {
playNextTrack,
playPrevTrack,
loading,
loadTrack,
};
}

View File

@@ -0,0 +1,59 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
interface ConfirmDialogProps {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description: string;
confirmText?: string;
cancelText?: string;
onConfirm: () => void;
variant?: "default" | "destructive";
}
export function ConfirmDialog({
isOpen,
onOpenChange,
title,
description,
confirmText = "Confirm",
cancelText = "Cancel",
onConfirm,
variant = "destructive",
}: ConfirmDialogProps) {
function handleConfirm() {
onConfirm();
onOpenChange(false);
}
function handleCancel() {
onOpenChange(false);
}
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={handleCancel}>
{cancelText}
</Button>
<Button variant={variant} onClick={handleConfirm}>
{confirmText}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -9,31 +9,41 @@ import {
import { cn } from "@/lib/utils";
import { Loaded } from "jazz-tools";
import { useAccount, useCoState } from "jazz-tools/react";
import { MoreHorizontal } from "lucide-react";
import { Fragment } from "react/jsx-runtime";
import { MusicTrackTitleInput } from "./MusicTrackTitleInput";
import { MoreHorizontal, Pause, Play } from "lucide-react";
import { Fragment, useCallback, useState } from "react";
import { EditTrackDialog } from "./RenameTrackDialog";
import { Waveform } from "./Waveform";
import { Button } from "./ui/button";
function isPartOfThePlaylist(
trackId: string,
playlist: Loaded<typeof Playlist, { tracks: true }>,
) {
return Array.from(playlist.tracks._refs).some((t) => t.id === trackId);
}
export function MusicTrackRow({
trackId,
isLoading,
isPlaying,
onClick,
showAddToPlaylist,
index,
}: {
trackId: string;
isLoading: boolean;
isPlaying: boolean;
onClick: (track: Loaded<typeof MusicTrack>) => void;
showAddToPlaylist: boolean;
index: number;
}) {
const track = useCoState(MusicTrack, trackId);
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const { me } = useAccount(MusicaAccount, {
resolve: { root: { playlists: { $each: true } } },
resolve: { root: { playlists: { $each: { tracks: true } } } },
});
const playlists = me?.root.playlists ?? [];
const isActiveTrack = trackId === me?.root._refs.activeTrack?.id;
function handleTrackClick() {
if (!track) return;
@@ -60,71 +70,118 @@ export function MusicTrackRow({
}
}
function handleEdit() {
setIsEditDialogOpen(true);
}
const handleContextMenu = useCallback((e: React.MouseEvent) => {
e.preventDefault();
setIsDropdownOpen(true);
}, []);
const showWaveform = isHovered || isActiveTrack;
return (
<li
className={"flex gap-1 hover:bg-slate-200 group py-2 px-2 cursor-pointer"}
onClick={handleTrackClick}
className={cn(
"flex gap-1 hover:bg-slate-200 group py-2 cursor-pointer rounded-lg",
isActiveTrack && "bg-slate-200",
)}
onMouseEnter={() => {
setIsHovered(true);
}}
onMouseLeave={() => {
setIsHovered(false);
}}
>
<button
className={cn(
"flex items-center justify-center bg-transparent w-8 h-8 ",
!isPlaying && "group-hover:bg-slate-300 rounded-full",
"flex items-center justify-center bg-transparent w-8 h-8 transition-opacity cursor-pointer",
// Show play button on hover or when active, hide otherwise
"md:opacity-0 opacity-50 group-hover:opacity-100",
isActiveTrack && "md:opacity-100 opacity-100",
)}
onClick={handleTrackClick}
aria-label={`${isPlaying ? "Pause" : "Play"} ${track?.title}`}
>
{isLoading ? (
<div className="animate-spin">߷</div>
) : isPlaying ? (
"⏸️"
{isPlaying ? (
<Pause height={16} width={16} fill="currentColor" />
) : (
"▶️"
<Play height={16} width={16} fill="currentColor" />
)}
</button>
<MusicTrackTitleInput trackId={trackId} />
{/* Show track index when play button is hidden - hidden on mobile */}
<div
className={cn(
"hidden md:flex items-center justify-center w-8 h-8 text-sm text-gray-500 font-mono transition-opacity",
)}
>
{index + 1}
</div>
<button
onContextMenu={handleContextMenu}
onClick={handleTrackClick}
className="flex items-center overflow-hidden text-ellipsis whitespace-nowrap cursor-pointer flex-1 min-w-0"
>
{track?.title}
</button>
{/* Waveform that appears on hover */}
{track && showWaveform && (
<div className="flex-1 min-w-0 px-2 items-center hidden md:flex">
<Waveform
track={track}
height={20}
className="opacity-70 w-full"
showProgress={isActiveTrack}
/>
</div>
)}
<div onClick={(evt) => evt.stopPropagation()}>
{showAddToPlaylist && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="h-8 w-8 p-0"
aria-label={`Open ${track?.title} menu`}
>
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
key={`delete`}
onSelect={async () => {
if (!track) return;
deleteTrack();
}}
>
Delete
</DropdownMenuItem>
{playlists.map((playlist, index) => (
<Fragment key={index}>
<DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="h-8 w-8 p-0"
aria-label={`Open ${track?.title} menu`}
>
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onSelect={handleEdit}>Edit</DropdownMenuItem>
{playlists.map((playlist, playlistIndex) => (
<Fragment key={playlistIndex}>
{isPartOfThePlaylist(trackId, playlist) ? (
<DropdownMenuItem
key={`add-${index}`}
onSelect={() => handleAddToPlaylist(playlist)}
>
Add to {playlist.title}
</DropdownMenuItem>
<DropdownMenuItem
key={`remove-${index}`}
key={`remove-${playlistIndex}`}
onSelect={() => handleRemoveFromPlaylist(playlist)}
>
Remove from {playlist.title}
</DropdownMenuItem>
</Fragment>
))}
</DropdownMenuContent>
</DropdownMenu>
)}
) : (
<DropdownMenuItem
key={`add-${playlistIndex}`}
onSelect={() => handleAddToPlaylist(playlist)}
>
Add to {playlist.title}
</DropdownMenuItem>
)}
</Fragment>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
{track && isEditDialogOpen && (
<EditTrackDialog
track={track}
isOpen={isEditDialogOpen}
onOpenChange={setIsEditDialogOpen}
onDelete={deleteTrack}
/>
)}
</li>
);
}

View File

@@ -5,7 +5,8 @@ import { usePlayState } from "@/lib/audio/usePlayState";
import { useKeyboardListener } from "@/lib/useKeyboardListener";
import { useAccount, useCoState } from "jazz-tools/react";
import { Pause, Play, SkipBack, SkipForward } from "lucide-react";
import { Waveform } from "./Waveform";
import WaveformCanvas from "./WaveformCanvas";
import { Button } from "./ui/button";
export function PlayerControls({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
const playState = usePlayState();
@@ -15,51 +16,61 @@ export function PlayerControls({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
resolve: { root: { activePlaylist: true } },
}).me?.root.activePlaylist;
const activeTrack = useCoState(MusicTrack, mediaPlayer.activeTrackId, {
resolve: { waveform: true },
});
const activeTrack = useCoState(MusicTrack, mediaPlayer.activeTrackId);
if (!activeTrack) return null;
const activeTrackTitle = activeTrack.title;
return (
<footer className="flex items-center justify-between p-4 gap-4 bg-white border-t border-gray-200 fixed bottom-0 left-0 right-0 w-full">
<div className="flex justify-center items-center space-x-2">
<div className="flex items-center space-x-4">
<button
<footer className="flex flex-wrap sm:flex-nowrap items-center justify-between pt-4 p-2 sm:p-4 gap-4 sm:gap-4 bg-white border-t border-gray-200 absolute bottom-0 left-0 right-0 w-full z-50">
{/* Player Controls - Always on top */}
<div className="flex justify-center items-center space-x-1 sm:space-x-2 flex-shrink-0 w-full sm:w-auto order-1 sm:order-none">
<div className="flex items-center space-x-2">
<Button
variant="ghost"
size="icon"
onClick={mediaPlayer.playPrevTrack}
className="text-blue-600 hover:text-blue-800"
aria-label="Previous track"
>
<SkipBack size={20} />
</button>
<button
<SkipBack className="h-5 w-5" fill="currentColor" />
</Button>
<Button
size="icon"
onClick={playState.toggle}
className="w-[42px] h-[42px] flex items-center justify-center bg-blue-600 rounded-full text-white hover:bg-blue-700"
className="bg-blue-600 text-white hover:bg-blue-700"
aria-label={isPlaying ? "Pause active track" : "Play active track"}
>
{isPlaying ? (
<Pause size={24} fill="currentColor" />
<Pause className="h-5 w-5" fill="currentColor" />
) : (
<Play size={24} fill="currentColor" />
<Play className="h-5 w-5" fill="currentColor" />
)}
</button>
<button
</Button>
<Button
variant="ghost"
size="icon"
onClick={mediaPlayer.playNextTrack}
className="text-blue-600 hover:text-blue-800"
aria-label="Next track"
>
<SkipForward size={20} />
</button>
<SkipForward className="h-5 w-5" fill="currentColor" />
</Button>
</div>
</div>
<div className=" sm:hidden md:flex flex-col flex-shrink-1 items-center w-[75%]">
<Waveform track={activeTrack} height={30} />
</div>
<div className="flex flex-col items-end gap-1 text-right min-w-fit w-[25%]">
<h4 className="font-medium text-blue-800">{activeTrackTitle}</h4>
<p className="text-sm text-gray-600">
{/* Waveform - Below controls on mobile, between controls and info on desktop */}
<WaveformCanvas
className="order-1 sm:order-none"
track={activeTrack}
height={50}
/>
{/* Track Info - Below waveform on mobile, on the right on desktop */}
<div className="flex flex-col gap-1 min-w-fit sm:flex-shrink-0 text-center w-full sm:text-right items-center sm:items-end sm:w-auto order-0 sm:order-none">
<h4 className="font-medium text-blue-800 text-base sm:text-base truncate max-w-80 sm:max-w-80">
{activeTrackTitle}
</h4>
<p className="hidden sm:block text-xs sm:text-sm text-gray-600 truncate sm:max-w-80">
{activePlaylist?.title || "All tracks"}
</p>
</div>
@@ -69,7 +80,9 @@ export function PlayerControls({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
export function KeyboardListener({
mediaPlayer,
}: { mediaPlayer: MediaPlayer }) {
}: {
mediaPlayer: MediaPlayer;
}) {
const playState = usePlayState();
useMediaEndListener(mediaPlayer.playNextTrack);

View File

@@ -1,12 +1,15 @@
import { Playlist } from "@/1_schema";
import { updatePlaylistTitle } from "@/4_actions";
import { cn } from "@/lib/utils";
import { useCoState } from "jazz-tools/react";
import { ChangeEvent, useState } from "react";
export function PlaylistTitleInput({
playlistId,
className,
}: {
playlistId: string | undefined;
className?: string;
}) {
const playlist = useCoState(Playlist, playlistId);
const [isEditing, setIsEditing] = useState(false);
@@ -33,7 +36,10 @@ export function PlaylistTitleInput({
<input
value={inputValue}
onChange={handleTitleChange}
className="text-2xl font-bold text-blue-800 bg-transparent"
className={cn(
"text-2xl font-bold text-blue-800 bg-transparent",
className,
)}
onFocus={handleFoucsIn}
onBlur={handleFocusOut}
aria-label={`Playlist title`}

View File

@@ -0,0 +1,108 @@
import { MusicTrack } from "@/1_schema";
import { updateMusicTrackTitle } from "@/4_actions";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Loaded } from "jazz-tools";
import { useState } from "react";
import { ConfirmDialog } from "./ConfirmDialog";
interface EditTrackDialogProps {
track: Loaded<typeof MusicTrack>;
isOpen: boolean;
onOpenChange: (open: boolean) => void;
onDelete: () => void;
}
export function EditTrackDialog({
track,
isOpen,
onOpenChange,
onDelete,
}: EditTrackDialogProps) {
const [newTitle, setNewTitle] = useState(track.title);
const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false);
function handleSave() {
if (track && newTitle.trim()) {
updateMusicTrackTitle(track, newTitle.trim());
onOpenChange(false);
}
}
function handleCancel() {
setNewTitle(track?.title || "");
onOpenChange(false);
}
function handleDeleteClick() {
setIsDeleteConfirmOpen(true);
}
function handleDeleteConfirm() {
onDelete();
onOpenChange(false);
}
function handleKeyDown(event: React.KeyboardEvent) {
if (event.key === "Enter") {
handleSave();
} else if (event.key === "Escape") {
handleCancel();
}
}
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Track</DialogTitle>
<DialogDescription>Edit "{track?.title}".</DialogDescription>
</DialogHeader>
<form className="py-4" onSubmit={handleSave}>
<Input
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Enter track name..."
autoFocus
/>
</form>
<DialogFooter className="flex justify-between">
<Button
variant="destructive"
onClick={handleDeleteClick}
className="mr-auto"
>
Delete Track
</Button>
<div className="flex gap-2">
<Button variant="outline" onClick={handleCancel}>
Cancel
</Button>
<Button onClick={handleSave} disabled={!newTitle.trim()}>
Save
</Button>
</div>
</DialogFooter>
</DialogContent>
<ConfirmDialog
isOpen={isDeleteConfirmOpen}
onOpenChange={setIsDeleteConfirmOpen}
title="Delete Track"
description={`Are you sure you want to delete "${track.title}"? This action cannot be undone.`}
confirmText="Delete"
cancelText="Cancel"
onConfirm={handleDeleteConfirm}
variant="destructive"
/>
</Dialog>
);
}

View File

@@ -1,10 +1,8 @@
import { MusicTrack, MusicaAccount } from "@/1_schema";
import { MusicaAccount } from "@/1_schema";
import { createNewPlaylist, deletePlaylist } from "@/4_actions";
import { MediaPlayer } from "@/5_useMediaPlayer";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
@@ -14,22 +12,18 @@ import {
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar";
import { usePlayState } from "@/lib/audio/usePlayState";
import { useAccount, useCoState } from "jazz-tools/react";
import { Home, Music, Pause, Play, Plus, Trash2 } from "lucide-react";
import { useAccount } from "jazz-tools/react";
import { Home, Music, Plus, Trash2 } from "lucide-react";
import { useNavigate, useParams } from "react-router";
import { AuthButton } from "./AuthButton";
export function SidePanel({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
export function SidePanel() {
const { playlistId } = useParams();
const navigate = useNavigate();
const { me } = useAccount(MusicaAccount, {
resolve: { root: { playlists: { $each: true } } },
});
const playState = usePlayState();
const isPlaying = playState.value === "play";
function handleAllTracksClick() {
navigate(`/`);
}
@@ -50,12 +44,6 @@ export function SidePanel({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
navigate(`/playlist/${playlist.id}`);
}
const activeTrack = useCoState(MusicTrack, mediaPlayer.activeTrackId, {
resolve: { waveform: true },
});
const activeTrackTitle = activeTrack?.title;
return (
<Sidebar>
<SidebarHeader>
@@ -137,29 +125,6 @@ export function SidePanel({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
{activeTrack && (
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem className="flex justify-end">
<SidebarMenuButton
onClick={playState.toggle}
aria-label={
isPlaying ? "Pause active track" : "Play active track"
}
>
<div className="w-[28px] h-[28px] flex items-center justify-center bg-blue-600 rounded-full text-white hover:bg-blue-700">
{isPlaying ? (
<Pause size={16} fill="currentColor" />
) : (
<Play size={16} fill="currentColor" />
)}
</div>
<span>{activeTrackTitle}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
)}
</Sidebar>
);
}

View File

@@ -7,6 +7,8 @@ import { useCoState } from "jazz-tools/react";
export function Waveform(props: {
track: Loaded<typeof MusicTrack>;
height: number;
className?: string;
showProgress?: boolean;
}) {
const { track, height } = props;
const waveformData = useCoState(
@@ -28,29 +30,24 @@ export function Waveform(props: {
}
const barCount = waveformData.length;
const activeBar = Math.ceil(barCount * (currentTime.value / duration));
function seek(i: number) {
currentTime.setValue((i / barCount) * duration);
}
const activeBar = props.showProgress
? Math.ceil(barCount * (currentTime.value / duration))
: -1;
return (
<div
className="flex justify-center items-end w-full"
className={cn("flex justify-center items-end w-full", props.className)}
style={{
height,
gap: 1,
}}
>
{waveformData.map((value, i) => (
<button
type="button"
key={i}
onClick={() => seek(i)}
className={cn(
"w-1 transition-colors rounded-none rounded-t-lg min-h-1",
activeBar >= i ? "bg-gray-500" : "bg-gray-300",
"hover:bg-black hover:border hover:border-solid hover:border-black",
activeBar >= i ? "bg-gray-800" : "bg-gray-400",
"focus-visible:outline-black focus:outline-hidden",
)}
style={{

View File

@@ -0,0 +1,282 @@
"use client";
import { MusicTrack, MusicTrackWaveform } from "@/1_schema";
import { AudioManager, useAudioManager } from "@/lib/audio/AudioManager";
import {
getPlayerCurrentTime,
setPlayerCurrentTime,
subscribeToPlayerCurrentTime,
usePlayerCurrentTime,
} from "@/lib/audio/usePlayerCurrentTime";
import { cn } from "@/lib/utils";
import { Loaded } from "jazz-tools";
import type React from "react";
import { useEffect, useRef } from "react";
type Props = {
track: Loaded<typeof MusicTrack>;
height?: number;
barColor?: string;
progressColor?: string;
backgroundColor?: string;
className?: string;
};
const DEFAULT_HEIGHT = 96;
// Downsample PCM into N peaks (abs max in window)
function buildPeaks(channelData: number[], samples: number): Float32Array {
const length = channelData.length;
if (channelData.length < samples) {
// Create a peaks array that interpolates the channelData
const interpolatedPeaks = new Float32Array(samples);
for (let i = 0; i < samples; i++) {
const index = Math.floor(i * (length / samples));
interpolatedPeaks[i] = channelData[index];
}
return interpolatedPeaks;
}
const blockSize = Math.floor(length / samples);
const peaks = new Float32Array(samples);
for (let i = 0; i < samples; i++) {
const start = i * blockSize;
let end = start + blockSize;
if (end > length) end = length;
let max = 0;
for (let j = start; j < end; j++) {
const v = Math.abs(channelData[j]);
if (v > max) max = v;
}
peaks[i] = max;
}
return peaks;
}
type DrawWaveformCanvasProps = {
canvas: HTMLCanvasElement;
waveformData: number[] | undefined;
duration: number;
currentTime: number;
barColor?: string;
progressColor?: string;
backgroundColor?: string;
isAnimating: boolean;
animationProgress: number;
progress: number;
};
function drawWaveform(props: DrawWaveformCanvasProps) {
const {
canvas,
waveformData,
isAnimating,
animationProgress,
barColor = "hsl(215, 16%, 47%)",
progressColor = "hsl(142, 71%, 45%)",
backgroundColor = "transparent",
progress,
} = props;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const dpr = window.devicePixelRatio || 1;
const cssWidth = canvas.clientWidth;
const cssHeight = canvas.clientHeight;
canvas.width = Math.floor(cssWidth * dpr);
canvas.height = Math.floor(cssHeight * dpr);
ctx.scale(dpr, dpr);
// Background
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, cssWidth, cssHeight);
if (!waveformData || !waveformData.length) {
// Draw placeholder line
ctx.strokeStyle = "hsl(215, 20%, 65%)";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(0, cssHeight / 2);
ctx.lineTo(cssWidth, cssHeight / 2);
ctx.stroke();
return;
}
const midY = cssHeight / 2;
const barWidth = 2; // px
const gap = 1;
const totalBars = Math.floor(cssWidth / (barWidth + gap));
const ds = buildPeaks(waveformData, totalBars);
const draw = (color: string, untilBar: number, start = 0) => {
ctx.fillStyle = color;
for (let i = start; i < untilBar; i++) {
const v = ds[i] || 0;
const h = Math.max(2, v * (cssHeight - 8)); // margin
const x = i * (barWidth + gap);
// Apply staggered animation
if (isAnimating) {
const barProgress = Math.max(0, Math.min(1, animationProgress / 0.2));
const animatedHeight = h * barProgress;
ctx.globalAlpha = barProgress;
ctx.fillRect(x, midY - animatedHeight / 2, barWidth, animatedHeight);
} else {
ctx.fillRect(x, midY - h / 2, barWidth, h);
}
}
};
// Progress overlay
const progressBars = Math.floor(
totalBars * Math.max(0, Math.min(1, progress || 0)),
);
draw(progressColor, progressBars);
// Base waveform
draw(barColor, totalBars, progressBars);
}
type WaveformCanvasProps = {
audioManager: AudioManager;
canvas: HTMLCanvasElement;
waveformId: string;
duration: number;
barColor?: string;
progressColor?: string;
backgroundColor?: string;
};
async function renderWaveform(props: WaveformCanvasProps) {
const { audioManager, canvas, waveformId, duration } = props;
let mounted = true;
let currentTime = getPlayerCurrentTime(audioManager);
let waveformData: undefined | number[] = undefined;
let isAnimating = true;
const startTime = performance.now();
let animationProgress = 0;
const animationDuration = 800;
function draw() {
const progress = currentTime / duration;
drawWaveform({
canvas,
waveformData,
duration,
currentTime,
isAnimating,
animationProgress,
progress,
});
}
const animate = (currentTime: number) => {
if (!mounted) return;
const elapsed = currentTime - startTime;
animationProgress = Math.min(elapsed / animationDuration, 1);
if (animationProgress < 1) {
requestAnimationFrame(animate);
} else {
isAnimating = false;
}
draw();
};
requestAnimationFrame(animate);
const unsubscribeFromCurrentTime = subscribeToPlayerCurrentTime(
audioManager,
(time) => {
currentTime = time;
draw();
},
);
const unsubscribeFromWaveform = MusicTrackWaveform.subscribe(
waveformId,
{},
(newResult) => {
waveformData = newResult.data;
draw();
},
);
return () => {
mounted = false;
unsubscribeFromCurrentTime();
unsubscribeFromWaveform();
};
}
export default function WaveformCanvas({
track,
height = DEFAULT_HEIGHT,
barColor, // muted-foreground-ish
progressColor, // green
backgroundColor,
className,
}: Props) {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const audioManager = useAudioManager();
const duration = track.duration;
const waveformId = track._refs.waveform?.id;
// Animation effect
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
if (!waveformId) return;
renderWaveform({
audioManager,
canvas,
waveformId,
duration,
barColor,
progressColor,
backgroundColor,
});
}, [audioManager, canvasRef, waveformId, duration]);
const onPointer = (e: React.PointerEvent<HTMLCanvasElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
const ratio = (e.clientX - rect.left) / rect.width;
const time = Math.max(0, Math.min(1, ratio)) * duration;
setPlayerCurrentTime(audioManager, time);
};
const currentTime = usePlayerCurrentTime();
const progress = currentTime.value / duration;
return (
<div className={cn("w-full", className)}>
<div
className="w-full rounded-md bg-background"
style={{ height }}
role="slider"
aria-label="Waveform scrubber"
aria-valuenow={Math.round((progress || 0) * 100)}
aria-valuemin={0}
aria-valuemax={100}
>
<canvas
ref={canvasRef}
className="w-full h-full rounded-md cursor-pointer"
onPointerDown={onPointer}
onPointerMove={(e) => {
if (e.buttons === 1) onPointer(e);
}}
/>
</div>
</div>
);
}

View File

@@ -532,7 +532,8 @@ const sidebarMenuButtonVariants = cva(
{
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
default:
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground cursor-pointer",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},

View File

@@ -2,6 +2,12 @@
@custom-variant dark (&:is(.dark *));
html {
overflow: hidden;
position: relative;
background-color: hsl(0 0% 99%);
}
:root {
--background: hsl(0 0% 100%);
--foreground: hsl(20 14.3% 4.1%);

View File

@@ -15,6 +15,7 @@ export class AudioManager {
if (this.audioObjectURL) {
URL.revokeObjectURL(this.audioObjectURL);
this.audioObjectURL = null;
this.mediaElement.src = "";
}
}

View File

@@ -1,22 +1,14 @@
import { useLayoutEffect, useState } from "react";
import { useAudioManager } from "./AudioManager";
import { AudioManager, useAudioManager } from "./AudioManager";
export function usePlayerCurrentTime() {
const audioManager = useAudioManager();
const [value, setValue] = useState<number>(0);
useLayoutEffect(() => {
setValue(audioManager.mediaElement.currentTime);
setValue(getPlayerCurrentTime(audioManager));
const onTimeUpdate = () => {
setValue(audioManager.mediaElement.currentTime);
};
audioManager.mediaElement.addEventListener("timeupdate", onTimeUpdate);
return () => {
audioManager.mediaElement.removeEventListener("timeupdate", onTimeUpdate);
};
return subscribeToPlayerCurrentTime(audioManager, setValue);
}, [audioManager]);
function setCurrentTime(time: number) {
@@ -31,3 +23,26 @@ export function usePlayerCurrentTime() {
setValue: setCurrentTime,
};
}
export function setPlayerCurrentTime(audioManager: AudioManager, time: number) {
audioManager.mediaElement.currentTime = time;
}
export function getPlayerCurrentTime(audioManager: AudioManager): number {
return audioManager.mediaElement.currentTime;
}
export function subscribeToPlayerCurrentTime(
audioManager: AudioManager,
callback: (time: number) => void,
) {
const onTimeUpdate = () => {
callback(audioManager.mediaElement.currentTime);
};
audioManager.mediaElement.addEventListener("timeupdate", onTimeUpdate);
return () => {
audioManager.mediaElement.removeEventListener("timeupdate", onTimeUpdate);
};
}

View File

@@ -0,0 +1,54 @@
import { MusicaAccount, MusicaAccountRoot } from "@/1_schema";
import { MediaPlayer } from "@/5_useMediaPlayer";
import { co } from "jazz-tools";
import { useAccount } from "jazz-tools/react";
import { useEffect, useState } from "react";
import { uploadMusicTracks } from "../4_actions";
export function usePrepareAppState(mediaPlayer: MediaPlayer) {
const [isReady, setIsReady] = useState(false);
const { agent } = useAccount();
useEffect(() => {
loadInitialData(mediaPlayer).then(() => {
setIsReady(true);
});
}, [agent]);
return isReady;
}
async function loadInitialData(mediaPlayer: MediaPlayer) {
const me = await MusicaAccount.getMe().ensureLoaded({
resolve: {
root: {
rootPlaylist: { tracks: { $each: true } },
activeTrack: true,
activePlaylist: true,
},
},
});
uploadOnboardingData(me.root);
// Load the active track in the AudioManager
if (me.root.activeTrack) {
mediaPlayer.loadTrack(me.root.activeTrack);
}
}
async function uploadOnboardingData(root: co.loaded<typeof MusicaAccountRoot>) {
if (root.exampleDataLoaded) return;
root.exampleDataLoaded = true;
try {
const trackFile = await (await fetch("/example.mp3")).blob();
await uploadMusicTracks([new File([trackFile], "Example song")], true);
} catch (error) {
root.exampleDataLoaded = false;
throw error;
}
}

View File

@@ -1,31 +0,0 @@
import { MusicaAccount } from "@/1_schema";
import { useAccount } from "jazz-tools/react";
import { useEffect } from "react";
import { uploadMusicTracks } from "../4_actions";
export function useUploadExampleData() {
const { agent } = useAccount();
useEffect(() => {
uploadOnboardingData();
}, [agent]);
}
async function uploadOnboardingData() {
const me = await MusicaAccount.getMe().ensureLoaded({
resolve: { root: true },
});
if (me.root.exampleDataLoaded) return;
me.root.exampleDataLoaded = true;
try {
const trackFile = await (await fetch("/example.mp3")).blob();
await uploadMusicTracks([new File([trackFile], "Example song")], true);
} catch (error) {
me.root.exampleDataLoaded = false;
throw error;
}
}

View File

@@ -55,10 +55,20 @@ export class HomePage {
async editTrackTitle(trackTitle: string, newTitle: string) {
await this.page
.getByRole("textbox", {
name: `Edit track title: ${trackTitle}`,
.getByRole("button", {
name: `Open ${trackTitle} menu`,
})
.fill(newTitle);
.click();
await this.page
.getByRole("menuitem", {
name: `Edit`,
})
.click();
await this.page.getByPlaceholder("Enter track name...").fill(newTitle);
await this.page.getByRole("button", { name: "Save" }).click();
}
async createPlaylist() {

View File

@@ -1,10 +1,21 @@
import path from "path";
import react from "@vitejs/plugin-react-swc";
import { defineConfig } from "vite";
import { VitePWA } from "vite-plugin-pwa";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [
react(),
VitePWA({
registerType: "autoUpdate",
strategies: "generateSW",
workbox: {
globPatterns: ["**/*.{js,css,html,ico,png,svg}"],
maximumFileSizeToCacheInBytes: 1024 * 1024 * 5,
},
}),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),

View File

@@ -13,9 +13,9 @@
},
"dependencies": {
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"lucide-react": "^0.536.0",
"react": "19.1.0",
"react-dom": "19.1.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.0.0",
"@types/react-dom": "19.0.0",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"postcss": "^8.4.40",

View File

@@ -7,6 +7,7 @@ import {
JazzAccount,
Organization,
Project,
validateDraftOrganization,
} from "../schema.ts";
import { Errors } from "./Errors.tsx";
import { OrganizationForm } from "./OrganizationForm.tsx";
@@ -21,8 +22,7 @@ export function CreateOrganization() {
if (!me?.root?.organizations) return;
const onSave = (draft: Loaded<typeof DraftOrganization>) => {
// validate if the draft is a valid organization
const validation = DraftOrganization.validate(draft);
const validation = validateDraftOrganization(draft);
setErrors(validation.errors);
if (validation.errors.length > 0) {
return;

View File

@@ -6,7 +6,9 @@ import { Organization } from "../schema.ts";
export function InviteLink({
organization,
}: { organization: Loaded<typeof Organization> }) {
}: {
organization: Loaded<typeof Organization>;
}) {
let [copyCount, setCopyCount] = useState(0);
let copied = copyCount > 0;

View File

@@ -4,7 +4,9 @@ import { Organization } from "../schema.ts";
export function OrganizationMembers({
organization,
}: { organization: Loaded<typeof Organization> }) {
}: {
organization: Loaded<typeof Organization>;
}) {
const group = organization._owner.castAs(Group);
return (
@@ -25,7 +27,11 @@ function MemberItem({
account,
role,
group,
}: { account: Account; role: string; group: Group }) {
}: {
account: Account;
role: string;
group: Group;
}) {
const { me } = useAccount();
const canRemoveMember = group.myRole() === "admin" && account.id !== me?.id;

View File

@@ -10,24 +10,24 @@ export const Organization = co.map({
projects: co.list(Project),
});
export const DraftOrganization = co
.map({
name: z.optional(z.string()),
projects: co.list(Project),
})
.withHelpers((Self) => ({
validate(org: Loaded<typeof Self>) {
const errors: string[] = [];
export const DraftOrganization = co.map({
name: z.optional(z.string()),
projects: co.list(Project),
});
if (!org.name) {
errors.push("Please enter a name.");
}
export function validateDraftOrganization(
org: Loaded<typeof DraftOrganization>,
) {
const errors: string[] = [];
return {
errors,
};
},
}));
if (!org.name) {
errors.push("Please enter a name.");
}
return {
errors,
};
}
export const JazzAccountRoot = co.map({
organizations: co.list(Organization),

View File

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

View File

@@ -79,8 +79,9 @@ main {
background-color: white;
padding: 2rem;
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px
rgba(0, 0, 0, 0.06);
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
width: 28rem;
}

View File

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

View File

@@ -35,7 +35,9 @@ export function ReactionsScreen(props: { id: string }) {
const ReactionButtons = ({
reactions,
}: { reactions: Loaded<typeof Reactions> }) => (
}: {
reactions: Loaded<typeof Reactions>;
}) => (
<div className="reaction-buttons">
{ReactionTypes.map((reactionType) => (
<button
@@ -56,7 +58,9 @@ const ReactionButtons = ({
const ReactionOverview = ({
reactions,
}: { reactions: Loaded<typeof Reactions> }) => (
}: {
reactions: Loaded<typeof Reactions>;
}) => (
<>
{Object.values(reactions.perAccount).map((reaction) => (
<div key={reaction.by?.id} className="reaction-row">

View File

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

View File

@@ -17,7 +17,7 @@ createRoot(document.getElementById("root")!).render(
<StrictMode>
<JazzReactProvider
sync={{
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
peer: `ws://localhost:4200/?key=${apiKey}`,
}}
AccountSchema={JazzAccount}
>

View File

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

47
examples/server-worker-http/.gitignore vendored Normal file
View File

@@ -0,0 +1,47 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -0,0 +1,105 @@
# Rock Paper Scissors with Jazz HTTP API
This example demonstrates how to use the **Jazz HTTP API** with **Next.js** to implement updates in a trusted environment. The application implements a multiplayer Rock Paper Scissors game where the server validates game actions and reveals player intentions only after both players have made their moves.
## 🎯 Key Concepts Demonstrated
### Trusted Environment Updates
- **Server-side validation**: All game actions are validated by the server before being applied
- **Secure reveal mechanism**: Player choices are hidden until both players have acted, built with Jazz group permissions
## 🚀 Getting Started
### Prerequisites
- Node.js 20+
- pnpm (recommended) or npm
### Installation
1. **Install dependencies**:
```bash
pnpm install
```
2. **Generate environment variables**:
```bash
pnpm generate-env
```
3. **Start the development server**:
```bash
pnpm dev
```
4. **Open your browser** to [http://localhost:3000](http://localhost:3000)
### Environment Setup
The `generate-env.ts` script creates the necessary environment variables for Jazz:
- `NEXT_PUBLIC_JAZZ_WORKER_ACCOUNT`: Server worker account ID
- `JAZZ_WORKER_SECRET`: Server worker secret key
## 🔧 Key Implementation Details
### Server API Definition
```typescript
const playRequest = experimental_defineRequest({
url: "/api/play",
workerId,
request: {
schema: {
game: Game,
selection: z.literal(["rock", "paper", "scissors"]),
},
resolve: {
game: {
player1: { account: true, playSelection: { group: true } },
player2: { account: true, playSelection: { group: true } },
},
},
},
response: {
schema: { game: Game },
resolve: { game: true },
},
});
```
### Secure Move Handling
```typescript
// Create restricted group for the move
const group = Group.create({ owner: jazzServerAccount.worker });
group.addMember(madeBy, "reader");
// Store move with restricted access
const playSelection = PlaySelection.create(
{ value: selection, group },
group,
);
// Reveal moves only after both players have acted
if (player1PlaySelection && player2PlaySelection) {
player1PlaySelection.group.addMember(game.player2.account, "reader");
player2PlaySelection.group.addMember(game.player1.account, "reader");
}
```
## 🎯 Use Cases
This pattern is ideal for applications requiring:
- **Fair play guarantees**: Preventing cheating in games
- **Simultaneous reveals**: Auctions, voting, or sealed-bid systems
- **Trusted computation**: Server-side validation of complex business logic
- **Real-time collaboration**: Multi-user applications with strict rules
## 📚 Learn More
- [Jazz Documentation](https://jazz.tools/docs)
- [Next.js App Router](https://nextjs.org/docs/app)
- [Groups & Permissions](https://jazz.tools/docs/react/groups/intro)
- [HTTP API with experimental_defineRequest](https://jazz.tools/docs/react/server-side/http-requests)
## 🤝 Contributing
This example is part of the Jazz framework. Feel free to submit issues and enhancement requests!

View File

@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View File

@@ -0,0 +1,26 @@
import * as fs from "fs";
import { createWorkerAccount } from "jazz-run/createWorkerAccount";
if (fs.existsSync(".env")) {
process.exit(0);
}
async function main() {
const account = await createWorkerAccount({
name: "jazz-paper-scissors-worker",
peer: "wss://cloud.jazz.tools/?key=jazz-paper-scissors@garden.co",
});
fs.writeFileSync(
".env",
`
JAZZ_WORKER_ACCOUNT=${account.accountID}
NEXT_PUBLIC_JAZZ_WORKER_ACCOUNT=${account.accountID}
JAZZ_WORKER_SECRET=${account.agentSecret}
`,
);
process.exit(0);
}
main();

View File

@@ -0,0 +1,5 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {};
export default nextConfig;

View File

@@ -0,0 +1,40 @@
{
"name": "server-worker-http",
"type": "module",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"generate-env": "tsx generate-env.ts",
"build": "pnpm generate-env && next build",
"start": "next start",
"lint": "next lint",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
},
"dependencies": {
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/postcss": "^4.1.11",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"jazz-tools": "workspace:*",
"lucide-react": "^0.525.0",
"next": "15.3.2",
"next-themes": "^0.4.6",
"postcss": "^8.5.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"sonner": "^2.0.3",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.11"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"jazz-run": "workspace:*",
"tsx": "^4.19.3",
"tw-animate-css": "^1.2.5",
"typescript": "^5"
}
}

View File

@@ -0,0 +1,55 @@
import { defineConfig, devices } from "@playwright/test";
import isCI from "is-ci";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: isCI,
/* Retry on CI only */
retries: isCI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: isCI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
timeout: 20_000,
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:3000/",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
permissions: ["clipboard-read", "clipboard-write"],
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
/* Run your local dev server before starting the tests */
webServer: [
{
command: "pnpm start",
url: "http://localhost:3000/",
reuseExistingServer: !isCI,
},
],
});

View File

@@ -0,0 +1,2 @@
const config = { plugins: { "@tailwindcss/postcss": {} } };
export default config;

View File

@@ -0,0 +1 @@
export const apiKey = "server-side-validation@garden.co";

View File

@@ -0,0 +1,23 @@
import { jazzServerAccount } from "@/jazzServerAccount";
import { WaitingRoom } from "@/schema";
import { serverApi } from "@/serverApi";
import { Group } from "jazz-tools";
export async function POST(request: Request) {
const response = await serverApi.createGame.handle(
request,
jazzServerAccount.worker,
async (_, madeBy) => {
const waitingRoom = WaitingRoom.create(
{ creator: madeBy },
Group.create(jazzServerAccount.worker).makePublic(),
);
return {
waitingRoom,
};
},
);
return response;
}

View File

@@ -0,0 +1,49 @@
import { jazzServerAccount } from "@/jazzServerAccount";
import { Game, createGameState } from "@/schema";
import { serverApi } from "@/serverApi";
import { Account, Group, JazzRequestError } from "jazz-tools";
export async function POST(request: Request) {
return serverApi.joinGame.handle(
request,
jazzServerAccount.worker,
async ({ waitingRoom }, madeBy) => {
if (madeBy.id === waitingRoom.creator.id) {
throw new JazzRequestError("You can't join your own waiting room", 400);
}
waitingRoom.game = createGame({
account1: waitingRoom.creator,
account2: madeBy,
worker: jazzServerAccount.worker,
});
return {
waitingRoom,
};
},
);
}
interface CreateGameParams {
account1: Account;
account2: Account;
worker: Account;
}
function createGame({ account1, account2, worker }: CreateGameParams) {
const gameGroup = Group.create({ owner: worker });
gameGroup.addMember(account1, "reader");
gameGroup.addMember(account2, "reader");
const game = Game.create(
{
...createGameState({ account1, account2, worker }),
player1Score: 0,
player2Score: 0,
},
gameGroup,
);
return game;
}

View File

@@ -0,0 +1,35 @@
import { jazzServerAccount } from "@/jazzServerAccount";
import { createGameState } from "@/schema";
import { serverApi } from "@/serverApi";
import { JazzRequestError } from "jazz-tools";
export async function POST(request: Request) {
const response = await serverApi.newGame.handle(
request,
jazzServerAccount.worker,
async ({ game }, madeBy) => {
const isPlayer1 = game.player1.account.id === madeBy.id;
const isPlayer2 = game.player2.account.id === madeBy.id;
if (!isPlayer1 && !isPlayer2) {
throw new JazzRequestError("You are not a player in this game", 400);
}
if (game.outcome) {
game.applyDiff(
createGameState({
account1: game.player1.account,
account2: game.player2.account,
worker: jazzServerAccount.worker,
}),
);
}
return {
game,
};
},
);
return response;
}

View File

@@ -0,0 +1,89 @@
import { jazzServerAccount } from "@/jazzServerAccount";
import { PlaySelection } from "@/schema";
import { serverApi } from "@/serverApi";
import { Group, JazzRequestError } from "jazz-tools";
export async function POST(request: Request) {
const response = await serverApi.play.handle(
request,
jazzServerAccount.worker,
async ({ game, selection }, madeBy) => {
const isPlayer1 = game.player1.account.id === madeBy.id;
const isPlayer2 = game.player2.account.id === madeBy.id;
if (!isPlayer1 && !isPlayer2) {
throw new JazzRequestError("You are not a player in this game", 400);
}
const group = Group.create({ owner: jazzServerAccount.worker });
group.addMember(madeBy, "reader");
if (isPlayer1 && game.player1.playSelection) {
throw new JazzRequestError("You have already played", 400);
} else if (isPlayer2 && game.player2.playSelection) {
throw new JazzRequestError("You have already played", 400);
}
const playSelection = PlaySelection.create(
{ value: selection, group },
group,
);
if (isPlayer1) {
game.player1.playSelection = playSelection;
} else {
game.player2.playSelection = playSelection;
}
if (game.player1.playSelection && game.player2.playSelection) {
game.outcome = determineOutcome(
game.player1.playSelection.value,
game.player2.playSelection.value,
);
// Reveal the play selections to the other player
game.player1.playSelection.group.addMember(
game.player2.account,
"reader",
);
game.player2.playSelection.group.addMember(
game.player1.account,
"reader",
);
if (game.outcome === "player1") {
game.player1Score++;
} else if (game.outcome === "player2") {
game.player2Score++;
}
}
return {
game,
result: "success",
};
},
);
return response;
}
/**
* Given a player selections, returns the winner of the current game.
*/
function determineOutcome(
player1Choice: "rock" | "paper" | "scissors",
player2Choice: "rock" | "paper" | "scissors",
) {
if (player1Choice === player2Choice) {
return "draw";
} else if (
(player1Choice === "rock" && player2Choice === "scissors") ||
(player1Choice === "paper" && player2Choice === "rock") ||
(player1Choice === "scissors" && player2Choice === "paper")
) {
return "player1";
} else {
return "player2";
}
}

View File

@@ -0,0 +1,388 @@
"use client";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Game, PlaySelection, PlayerState } from "@/schema";
import { serverApi } from "@/serverApi";
import { Group, isJazzRequestError } from "jazz-tools";
import { useAccount, useCoState } from "jazz-tools/react";
import {
CheckCircle,
Clock,
Gamepad2,
Minus,
Sparkles,
Trophy,
Users,
XCircle,
} from "lucide-react";
import { useParams } from "next/navigation";
import { useState } from "react";
import { toast } from "sonner";
const playIcon = (
selection: "rock" | "paper" | "scissors" | undefined,
size: "sm" | "lg" = "sm",
) => {
const emojiSize = size === "lg" ? "text-4xl" : "text-2xl";
switch (selection) {
case "rock":
return (
<div className="flex items-center gap-2">
<span className={`${emojiSize} `} style={{ animationDelay: "0ms" }}>
🪨
</span>
<span className="font-semibold">Rock</span>
</div>
);
case "paper":
return (
<div className="flex items-center gap-2">
<span className={`${emojiSize} `} style={{ animationDelay: "150ms" }}>
📄
</span>
<span className="font-semibold">Paper</span>
</div>
);
case "scissors":
return (
<div className="flex items-center gap-2">
<span className={`${emojiSize} `} style={{ animationDelay: "300ms" }}>
</span>
<span className="font-semibold">Scissors</span>
</div>
);
default:
return (
<div className="flex items-center gap-2 text-muted-foreground">
<Clock className={size === "lg" ? "w-8 h-8" : "w-6 h-6"} />
<span>Waiting for selection</span>
</div>
);
}
};
const getOutcomeIcon = (outcome: string | undefined, player: string) => {
if (outcome === player) {
return <Trophy className="w-6 h-6 text-yellow-500" />;
} else if (outcome === "draw") {
return <Minus className="w-6 h-6 text-blue-500" />;
} else {
return <XCircle className="w-6 h-6 text-red-500" />;
}
};
export default function RouteComponent() {
const params = useParams<{ id: string }>();
const game = useCoState(Game, params.id, {
resolve: {
player1State: {
$onError: null,
},
player2State: {
$onError: null,
},
player1: {
account: true,
playSelection: {
$onError: null,
},
},
player2: {
account: true,
playSelection: {
$onError: null,
},
},
},
});
const isPlayer1 = game?.player1?.account?.isMe;
const player = isPlayer1 ? "player1" : "player2";
if (!game) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center">
<div className="animate-pulse">
<Gamepad2 className="w-12 h-12 text-muted-foreground" />
</div>
</div>
);
}
const gameComplete = game.outcome !== undefined;
const opponent = isPlayer1 ? "player2" : "player1";
const currentPlayer = game[player];
const opponentPlayer = game[opponent];
const currentPlayerState = game[isPlayer1 ? "player1State" : "player2State"];
const opponentSelection = opponentPlayer?.playSelection;
const opponentHasSelected = Boolean(opponentPlayer._refs.playSelection);
const handleSelection = (selection: "rock" | "paper" | "scissors") => {
if (!currentPlayerState) return;
if (currentPlayerState.submitted) return;
currentPlayerState.currentSelection = selection;
};
const handleSubmit = async (
playSelection: "rock" | "paper" | "scissors" | undefined,
) => {
if (!playSelection) return;
if (!currentPlayerState) return;
currentPlayerState.submitted = true;
try {
await serverApi.play.send({
game,
selection: playSelection,
});
} catch (error) {
currentPlayerState.submitted = false;
if (isJazzRequestError(error)) {
toast.error(error.message);
} else {
console.error(error);
toast.error("An unexpected error occurred");
}
}
};
const handleNewGame = async () => {
if (!currentPlayerState) return;
currentPlayerState.resetRequested = true;
try {
await serverApi.newGame.send({
game,
});
} catch (error) {
currentPlayerState.resetRequested = false;
if (isJazzRequestError(error)) {
toast.error(error.message);
} else {
console.error(error);
toast.error("An unexpected error occurred");
}
}
};
const playSelection = currentPlayerState?.currentSelection;
const submitDisabled =
!currentPlayerState?.currentSelection || currentPlayerState?.submitted;
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 py-8 px-4">
<div className="max-w-4xl mx-auto">
{/* Header */}
<div className="text-center mb-8">
<div className="flex items-center justify-center gap-2 mb-4">
<Gamepad2 className="w-8 h-8 text-primary" />
<h1 className="text-4xl font-bold bg-gradient-to-r from-primary to-purple-600 bg-clip-text text-transparent">
Rock, Paper, Scissors
</h1>
<Sparkles className="w-6 h-6 text-yellow-500" />
</div>
{/* Player Info */}
<div className="flex items-center justify-center gap-4 mb-6">
<Badge variant="secondary" className="px-4 py-2">
<Users className="w-4 h-4 mr-2" />
{isPlayer1 ? "Player 1" : "Player 2"}
</Badge>
</div>
{/* Score Board */}
<Card className="inline-block bg-white/80 backdrop-blur-sm border-0 shadow-lg">
<CardContent className="p-4">
<div className="flex items-center gap-6 text-2xl font-bold">
<div className="flex items-center gap-2">
<span className="text-blue-600">Player 1</span>
<span className="text-3xl">{game?.player1Score ?? 0}</span>
</div>
<div className="text-muted-foreground">-</div>
<div className="flex items-center gap-2">
<span className="text-3xl">{game?.player2Score ?? 0}</span>
<span className="text-purple-600">Player 2</span>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Game Outcome */}
{gameComplete && (
<Card className="mb-8 bg-gradient-to-r from-yellow-50 to-orange-50 border-yellow-200">
<CardContent className="p-6 text-center">
<div className="flex items-center justify-center gap-3 mb-4">
{getOutcomeIcon(game.outcome, player)}
<h2 className="text-2xl font-bold">
{game?.outcome === player
? "🎉 You Win! 🎉"
: game?.outcome === "draw"
? "🤝 It's a Draw! 🤝"
: "😔 You Lose! 😔"}
</h2>
</div>
<Button
disabled={currentPlayerState?.resetRequested}
onClick={handleNewGame}
className="bg-gradient-to-r from-primary to-purple-600 hover:from-primary/90 hover:to-purple-600/90 text-white"
size="lg"
>
<Gamepad2 className="w-5 h-5 mr-2" />
Start New Game
</Button>
</CardContent>
</Card>
)}
{/* Game Board */}
<div className="grid md:grid-cols-2 gap-8">
{/* Your Selection */}
<Card className="bg-white/80 backdrop-blur-sm border-0 shadow-lg">
<CardHeader className="text-center pb-4">
<CardTitle className="text-xl font-semibold text-blue-600">
Your Selection
</CardTitle>
</CardHeader>
<CardContent className="text-center">
<div className="mb-6">{playIcon(playSelection, "lg")}</div>
{!gameComplete && (
<>
{/* Choice Buttons */}
<div className="grid grid-cols-3 gap-4 mb-6">
<Button
variant={playSelection === "rock" ? "default" : "outline"}
size="lg"
className={`h-20 transition-all duration-200 ${
playSelection === "rock"
? "bg-gradient-to-br from-gray-400 to-gray-600 text-white shadow-lg scale-105"
: "hover:scale-105"
} text-3xl`}
onClick={() => handleSelection("rock")}
aria-label="Select Rock"
aria-selected={playSelection === "rock"}
>
🪨
</Button>
<Button
variant={
playSelection === "paper" ? "default" : "outline"
}
size="lg"
className={`h-20 transition-all duration-200 ${
playSelection === "paper"
? "bg-gradient-to-br from-blue-400 to-blue-600 text-white shadow-lg scale-105"
: "hover:scale-105"
} text-3xl`}
onClick={() => handleSelection("paper")}
aria-label="Select Paper"
aria-selected={playSelection === "paper"}
>
📄
</Button>
<Button
variant={
playSelection === "scissors" ? "default" : "outline"
}
size="lg"
className={`h-20 transition-all duration-200 ${
playSelection === "scissors"
? "bg-gradient-to-br from-red-400 to-red-600 text-white shadow-lg scale-105"
: "hover:scale-105"
} text-3xl`}
onClick={() => handleSelection("scissors")}
aria-label="Select Scissors"
aria-selected={playSelection === "scissors"}
>
</Button>
</div>
{/* Submit Button */}
<Button
disabled={submitDisabled}
onClick={() => handleSubmit(playSelection)}
className="w-full bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white font-semibold py-3 text-lg"
size="lg"
>
{currentPlayerState?.submitted ? (
<>
<CheckCircle className="w-5 h-5 mr-2" />
Selection Made!
</>
) : (
<>
<Gamepad2 className="w-5 h-5 mr-2" />
Make Your Move!
</>
)}
</Button>
</>
)}
</CardContent>
</Card>
{/* Opponent Selection */}
<Card className="bg-white/80 backdrop-blur-sm border-0 shadow-lg">
<CardHeader className="text-center pb-4">
<CardTitle className="text-xl font-semibold text-purple-600">
Opponent's Selection
</CardTitle>
</CardHeader>
<CardContent className="text-center">
{opponentSelection && (
<div className="mb-6">
{playIcon(opponentSelection?.value, "lg")}
</div>
)}
{!opponentSelection && !gameComplete ? (
opponentHasSelected ? (
<div className="text-muted-foreground">
<Clock className="w-8 h-8 mx-auto mb-2 animate-pulse" />
<p>The opponent has made their move</p>
</div>
) : (
<div className="text-muted-foreground">
<Clock className="w-8 h-8 mx-auto mb-2 animate-pulse" />
<p>Waiting for opponent...</p>
</div>
)
) : null}
</CardContent>
</Card>
</div>
{/* Game Status */}
{!gameComplete && (
<Card className="mt-8 bg-white/60 backdrop-blur-sm border-0">
<CardContent className="p-4 text-center">
<div className="flex items-center justify-center gap-2 text-muted-foreground">
<Clock className="w-4 h-4" />
<span className="text-sm">
{currentPlayer?.playSelection && !opponentSelection
? "Waiting for opponent to make their move..."
: "Make your selection to start the game"}
</span>
</div>
</CardContent>
</Card>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,120 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--radius: 0.625rem;
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -0,0 +1,37 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { Toaster } from "@/components/ui/sonner";
import { Jazz } from "../jazz";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Jazz Server Side Validation",
description: "An example of server side validation with Jazz",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<Jazz>{children}</Jazz>
<Toaster />
</body>
</html>
);
}

View File

@@ -0,0 +1,97 @@
"use client";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { serverApi } from "@/serverApi";
import { useRouter } from "next/navigation";
import { useState } from "react";
export default function HomeComponent() {
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const onNewGameClick = async () => {
setIsLoading(true);
const { waitingRoom } = await serverApi.createGame.send({});
router.push(`/waiting-room/${waitingRoom.id}`);
};
return (
<div className="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-900 flex flex-col items-center justify-center p-4 relative overflow-hidden">
{/* Background decoration */}
<div className="absolute inset-0 overflow-hidden">
<div className="absolute -top-40 -right-40 w-80 h-80 bg-white/5 rounded-full blur-3xl"></div>
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-white/5 rounded-full blur-3xl"></div>
</div>
{/* Main content */}
<div className="relative z-10 text-center space-y-8 max-w-2xl mx-auto">
{/* Game title and emojis */}
<div className="space-y-4">
<div className="flex justify-center items-center space-x-4 text-6xl mb-6">
<span className="animate-bounce" style={{ animationDelay: "0ms" }}>
🪨
</span>
<span
className="animate-bounce"
style={{ animationDelay: "150ms" }}
>
📄
</span>
<span
className="animate-bounce"
style={{ animationDelay: "300ms" }}
>
</span>
</div>
<h1 className="text-5xl font-bold text-white mb-2">
Rock, Paper, Scissors
</h1>
<p className="text-xl text-gray-300 max-w-md mx-auto">
Challenge your friends in this classic multiplayer game powered by
Jazz
</p>
</div>
{/* Game card */}
<Card className="w-full max-w-md mx-auto bg-white/10 backdrop-blur-lg border-white/20 shadow-2xl">
<CardHeader className="text-center pb-4">
<CardTitle className="text-2xl font-bold text-white">
Ready to Play?
</CardTitle>
<p className="text-gray-300 text-sm mt-2">
Create a new game and invite your friends
</p>
</CardHeader>
<CardContent className="space-y-4">
<Button
onClick={onNewGameClick}
disabled={isLoading}
className="w-full h-12 text-lg font-semibold bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 border-0 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105"
>
{isLoading ? (
<div className="flex items-center space-x-2">
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin"></div>
<span>Creating game...</span>
</div>
) : (
<div className="flex items-center space-x-2">
<span>🎮</span>
<span>Start New Game</span>
</div>
)}
</Button>
</CardContent>
</Card>
{/* Footer */}
<div className="text-center text-gray-400 text-sm mt-12">
<p>Built with Jazz Framework</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,173 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { WaitingRoom } from "@/schema";
import { serverApi } from "@/serverApi";
import { Account, JazzRequestError, co } from "jazz-tools";
import { useCoState } from "jazz-tools/react-core";
import { ClipboardCopyIcon, Loader2Icon } from "lucide-react";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { toast } from "sonner";
function useWindowLocation() {
const [location, setLocation] = useState<string>("");
useEffect(() => {
setLocation(window.location.href);
}, []);
return location;
}
async function askToJoinGame(
waitingRoom: co.loaded<typeof WaitingRoom, { creator: true }>,
) {
if (waitingRoom.creator.isMe) {
return null;
}
try {
await serverApi.joinGame.send({
waitingRoom,
});
} catch (error) {
if (error instanceof JazzRequestError) {
toast.error(error.message);
} else {
toast.error("An unexpected error occurred");
}
}
}
export default function RouteComponent() {
const params = useParams<{ id: string }>();
const searchParams = useSearchParams();
const waitingRoom = useCoState(WaitingRoom, params.id, {
resolve: {
creator: true,
game: true,
},
});
const [copied, setCopied] = useState(false);
const router = useRouter();
const location = useWindowLocation();
const joinLocation = location + "?join=true";
const isJoining = !waitingRoom
? searchParams.get("join") === "true"
: Account.getMe().id !== waitingRoom.creator.id;
useEffect(() => {
if (!waitingRoom) {
return;
}
askToJoinGame(waitingRoom);
}, [waitingRoom?.id]);
useEffect(() => {
if (!waitingRoom?.game?.id) {
return;
}
router.push(`/game/${waitingRoom.game.id}`);
}, [waitingRoom?.game?.id]);
const onCopyClick = () => {
navigator.clipboard.writeText(joinLocation);
setCopied(true);
toast.success("Link copied to clipboard!");
};
return (
<div className="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-900 flex flex-col items-center justify-center p-4 relative overflow-hidden">
{/* Background decoration */}
<div className="absolute inset-0 overflow-hidden">
<div className="absolute -top-40 -right-40 w-80 h-80 bg-white/5 rounded-full blur-3xl"></div>
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-white/5 rounded-full blur-3xl"></div>
</div>
{/* Main content */}
<div className="relative z-10 text-center space-y-8 max-w-2xl mx-auto">
{/* Game title and emojis */}
<div className="space-y-4">
<div className="flex justify-center items-center space-x-4 text-6xl mb-6">
<span className="animate-bounce" style={{ animationDelay: "0ms" }}>
🪨
</span>
<span
className="animate-bounce"
style={{ animationDelay: "150ms" }}
>
📄
</span>
<span
className="animate-bounce"
style={{ animationDelay: "300ms" }}
>
</span>
</div>
<h1 className="text-5xl font-bold text-white mb-2">Waiting Room</h1>
{!isJoining && (
<p className="text-xl text-gray-300 max-w-md mx-auto">
Share this link with your friend to join the game
</p>
)}
</div>
{/* Waiting room card */}
<Card className="w-full max-w-md mx-auto bg-white/10 backdrop-blur-lg border-white/20 shadow-2xl">
<CardHeader className="text-center pb-4">
<CardTitle className="text-2xl font-bold text-white flex items-center justify-center">
<Loader2Icon className="animate-spin inline h-8 w-8 mr-3" />
{isJoining ? "Joining the game" : "Waiting for opponent"}
</CardTitle>
<CardDescription className="text-gray-300 text-sm mt-2">
The game will automatically start once{" "}
{isJoining ? "ready" : "they join"}
</CardDescription>
</CardHeader>
{!isJoining && (
<CardContent className="space-y-4">
<div className="flex">
<Input
className="w-full border-white/20 bg-white/5 text-white placeholder:text-gray-400 rounded-e-none focus:border-white/40 focus:ring-white/20"
readOnly
value={joinLocation}
/>
<Button
onClick={onCopyClick}
className="rounded-s-none w-25 bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 border-0 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105"
>
{copied ? (
"Copied!"
) : (
<>
<ClipboardCopyIcon className="w-5 h-5" />
Copy
</>
)}
</Button>
</div>
</CardContent>
)}
</Card>
{/* Footer */}
<div className="text-center text-gray-400 text-sm mt-12">
<p>Built with Jazz Framework</p>
</div>
</div>
</div>
);
}

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